diff options
581 files changed, 14923 insertions, 6009 deletions
diff --git a/api/current.txt b/api/current.txt index 1e6bf2c91711..e6b44f1bc169 100644 --- a/api/current.txt +++ b/api/current.txt @@ -102,7 +102,7 @@ package android { field public static final String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT"; field public static final String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS"; field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY"; - field public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS"; + field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS"; field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR"; field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG"; field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS"; @@ -291,6 +291,7 @@ package android { field public static final int allowBackup = 16843392; // 0x1010280 field public static final int allowClearUserData = 16842757; // 0x1010005 field public static final int allowEmbedded = 16843765; // 0x10103f5 + field public static final int allowExternalStorageSandbox = 16844201; // 0x10105a9 field public static final int allowParallelSyncs = 16843570; // 0x1010332 field public static final int allowSingleTap = 16843353; // 0x1010259 field public static final int allowTaskReparenting = 16843268; // 0x1010204 @@ -11534,107 +11535,107 @@ package android.content.pm { public abstract class PackageManager { ctor public PackageManager(); - method @Deprecated public abstract void addPackageToPreferred(String); - method public abstract boolean addPermission(android.content.pm.PermissionInfo); - method public abstract boolean addPermissionAsync(android.content.pm.PermissionInfo); - method @Deprecated public abstract void addPreferredActivity(android.content.IntentFilter, int, android.content.ComponentName[], android.content.ComponentName); + method @Deprecated public abstract void addPackageToPreferred(@NonNull String); + method public abstract boolean addPermission(@NonNull android.content.pm.PermissionInfo); + method public abstract boolean addPermissionAsync(@NonNull android.content.pm.PermissionInfo); + method @Deprecated public abstract void addPreferredActivity(@NonNull android.content.IntentFilter, int, @Nullable android.content.ComponentName[], @NonNull android.content.ComponentName); method public abstract boolean canRequestPackageInstalls(); - method public abstract String[] canonicalToCurrentPackageNames(String[]); - method @CheckResult public abstract int checkPermission(String, String); - method @CheckResult public abstract int checkSignatures(String, String); + method public abstract String[] canonicalToCurrentPackageNames(@NonNull String[]); + method @CheckResult public abstract int checkPermission(@NonNull String, @NonNull String); + method @CheckResult public abstract int checkSignatures(@NonNull String, @NonNull String); method @CheckResult public abstract int checkSignatures(int, int); method public abstract void clearInstantAppCookie(); - method @Deprecated public abstract void clearPackagePreferredActivities(String); - method public abstract String[] currentToCanonicalPackageNames(String[]); + method @Deprecated public abstract void clearPackagePreferredActivities(@NonNull String); + method public abstract String[] currentToCanonicalPackageNames(@NonNull String[]); method public abstract void extendVerificationTimeout(int, int, long); - method public abstract android.graphics.drawable.Drawable getActivityBanner(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.graphics.drawable.Drawable getActivityBanner(android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.graphics.drawable.Drawable getActivityIcon(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.graphics.drawable.Drawable getActivityIcon(android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.content.pm.ActivityInfo getActivityInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.graphics.drawable.Drawable getActivityLogo(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.graphics.drawable.Drawable getActivityLogo(android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int); - method public abstract android.graphics.drawable.Drawable getApplicationBanner(android.content.pm.ApplicationInfo); - method public abstract android.graphics.drawable.Drawable getApplicationBanner(String) throws android.content.pm.PackageManager.NameNotFoundException; + method @Nullable public abstract android.graphics.drawable.Drawable getActivityBanner(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; + method @Nullable public abstract android.graphics.drawable.Drawable getActivityBanner(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.graphics.drawable.Drawable getActivityIcon(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.graphics.drawable.Drawable getActivityIcon(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @Nullable public abstract android.graphics.drawable.Drawable getActivityLogo(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; + method @Nullable public abstract android.graphics.drawable.Drawable getActivityLogo(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int); + method @Nullable public abstract android.graphics.drawable.Drawable getApplicationBanner(@NonNull android.content.pm.ApplicationInfo); + method @Nullable public abstract android.graphics.drawable.Drawable getApplicationBanner(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract int getApplicationEnabledSetting(@NonNull String); - method public abstract android.graphics.drawable.Drawable getApplicationIcon(android.content.pm.ApplicationInfo); - method public abstract android.graphics.drawable.Drawable getApplicationIcon(String) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.content.pm.ApplicationInfo getApplicationInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract CharSequence getApplicationLabel(android.content.pm.ApplicationInfo); - method public abstract android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo); - method public abstract android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.graphics.drawable.Drawable getApplicationIcon(@NonNull android.content.pm.ApplicationInfo); + method @NonNull public abstract android.graphics.drawable.Drawable getApplicationIcon(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo); + method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo); + method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int); method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName); - method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon(); - method public abstract android.graphics.drawable.Drawable getDrawable(String, @DrawableRes int, android.content.pm.ApplicationInfo); - method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); + method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon(); + method @Nullable public abstract android.graphics.drawable.Drawable getDrawable(@NonNull String, @DrawableRes int, @Nullable android.content.pm.ApplicationInfo); + method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int); method @NonNull public java.util.List<android.content.pm.ModuleInfo> getInstalledModules(int); - method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); - method @Nullable public abstract String getInstallerPackageName(String); + method @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int); + method @Nullable public abstract String getInstallerPackageName(@NonNull String); method @NonNull public abstract byte[] getInstantAppCookie(); method public abstract int getInstantAppCookieMaxBytes(); - method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method @Nullable public abstract android.content.Intent getLaunchIntentForPackage(@NonNull String); method @Nullable public abstract android.content.Intent getLeanbackLaunchIntentForPackage(@NonNull String); - method public android.content.pm.ModuleInfo getModuleInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public android.content.pm.ModuleInfo getModuleInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; method @Nullable public abstract String getNameForUid(int); - method public android.content.pm.PackageInfo getPackageArchiveInfo(String, int); + method @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, int); method public abstract int[] getPackageGids(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract int[] getPackageGids(String, int) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.content.pm.PackageInfo getPackageInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException; + method public abstract int[] getPackageGids(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public abstract android.content.pm.PackageInstaller getPackageInstaller(); - method public abstract int getPackageUid(String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method public abstract int getPackageUid(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; method @Nullable public abstract String[] getPackagesForUid(int); - method public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(String[], int); - method public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.content.pm.PermissionInfo getPermissionInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated public abstract int getPreferredActivities(@NonNull java.util.List<android.content.IntentFilter>, @NonNull java.util.List<android.content.ComponentName>, String); - method @Deprecated public abstract java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int); - method public abstract android.content.pm.ProviderInfo getProviderInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.content.pm.ActivityInfo getReceiverInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.content.res.Resources getResourcesForActivity(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.content.res.Resources getResourcesForApplication(String) throws android.content.pm.PackageManager.NameNotFoundException; - method public abstract android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], int); + method @NonNull public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method public abstract android.content.pm.PermissionInfo getPermissionInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @Deprecated public abstract int getPreferredActivities(@NonNull java.util.List<android.content.IntentFilter>, @NonNull java.util.List<android.content.ComponentName>, @Nullable String); + method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int); + method @NonNull public abstract android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.content.res.Resources getResourcesForActivity(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public abstract android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int); method @Nullable public android.os.Bundle getSuspendedPackageAppExtras(); method public boolean getSyntheticAppDetailsActivityEnabled(@NonNull String); - method public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures(); - method public abstract String[] getSystemSharedLibraryNames(); - method public abstract CharSequence getText(String, @StringRes int, android.content.pm.ApplicationInfo); - method public abstract android.graphics.drawable.Drawable getUserBadgedDrawableForDensity(android.graphics.drawable.Drawable, android.os.UserHandle, android.graphics.Rect, int); - method public abstract android.graphics.drawable.Drawable getUserBadgedIcon(android.graphics.drawable.Drawable, android.os.UserHandle); - method public abstract CharSequence getUserBadgedLabel(CharSequence, android.os.UserHandle); - method public abstract android.content.res.XmlResourceParser getXml(String, @XmlRes int, android.content.pm.ApplicationInfo); - method public boolean hasSigningCertificate(String, byte[], int); - method public boolean hasSigningCertificate(int, byte[], int); - method public abstract boolean hasSystemFeature(String); - method public abstract boolean hasSystemFeature(String, int); + method @NonNull public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures(); + method @Nullable public abstract String[] getSystemSharedLibraryNames(); + method @Nullable public abstract CharSequence getText(@NonNull String, @StringRes int, @Nullable android.content.pm.ApplicationInfo); + method @NonNull public abstract android.graphics.drawable.Drawable getUserBadgedDrawableForDensity(@NonNull android.graphics.drawable.Drawable, @NonNull android.os.UserHandle, @Nullable android.graphics.Rect, int); + method @NonNull public abstract android.graphics.drawable.Drawable getUserBadgedIcon(@NonNull android.graphics.drawable.Drawable, @NonNull android.os.UserHandle); + method @NonNull public abstract CharSequence getUserBadgedLabel(@NonNull CharSequence, @NonNull android.os.UserHandle); + method @Nullable public abstract android.content.res.XmlResourceParser getXml(@NonNull String, @XmlRes int, @Nullable android.content.pm.ApplicationInfo); + method public boolean hasSigningCertificate(@NonNull String, @NonNull byte[], int); + method public boolean hasSigningCertificate(int, @NonNull byte[], int); + method public abstract boolean hasSystemFeature(@NonNull String); + method public abstract boolean hasSystemFeature(@NonNull String, int); method public abstract boolean isInstantApp(); - method public abstract boolean isInstantApp(String); - method public boolean isPackageSuspended(String) throws android.content.pm.PackageManager.NameNotFoundException; + method public abstract boolean isInstantApp(@NonNull String); + method public boolean isPackageSuspended(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean isPackageSuspended(); method @CheckResult public abstract boolean isPermissionRevokedByPolicy(@NonNull String, @NonNull String); method public abstract boolean isSafeMode(); - method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int); - method public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(String, int, int); - method public abstract java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(String, int); - method public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(android.content.Intent, int); - method public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable android.content.Intent[], android.content.Intent, int); - method public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(android.content.Intent, int); - method public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(android.content.Intent, int); - method public abstract java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(String, int) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated public abstract void removePackageFromPreferred(String); - method public abstract void removePermission(String); - method public abstract android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int); - method public abstract android.content.pm.ProviderInfo resolveContentProvider(String, int); - method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int); + method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int); + method @NonNull public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, int); + method @NonNull public abstract java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(@NonNull String, int); + method @Nullable public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, int); + method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable android.content.Intent[], @NonNull android.content.Intent, int); + method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, int); + method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, int); + method @NonNull public abstract java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @Deprecated public abstract void removePackageFromPreferred(@NonNull String); + method public abstract void removePermission(@NonNull String); + method @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int); + method @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int); + method @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int); method public abstract void setApplicationCategoryHint(@NonNull String, int); method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int); method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(@NonNull android.content.ComponentName, int, int); - method public abstract void setInstallerPackageName(String, String); + method public abstract void setInstallerPackageName(@NonNull String, @Nullable String); method public abstract void updateInstantAppCookie(@Nullable byte[]); method public abstract void verifyPendingInstall(int, int); field public static final int CERT_INPUT_RAW_X509 = 0; // 0x0 @@ -15388,8 +15389,8 @@ package android.graphics.drawable { method public boolean setState(@NonNull int[]); method public void setTint(@ColorInt int); method public void setTintList(@Nullable android.content.res.ColorStateList); - method @Deprecated public void setTintMode(@NonNull android.graphics.PorterDuff.Mode); - method public void setTintMode(@NonNull android.graphics.BlendMode); + method @Deprecated public void setTintMode(@Nullable android.graphics.PorterDuff.Mode); + method public void setTintMode(@Nullable android.graphics.BlendMode); method public boolean setVisible(boolean, boolean); method public void unscheduleSelf(@NonNull Runnable); } @@ -17057,6 +17058,7 @@ package android.hardware.camera2 { public class CaptureFailure { method public long getFrameNumber(); + method @Nullable public String getPhysicalCameraId(); method public int getReason(); method @NonNull public android.hardware.camera2.CaptureRequest getRequest(); method public int getSequenceId(); @@ -23279,6 +23281,7 @@ package android.media { method @Deprecated public boolean registerRemoteController(android.media.RemoteController); method @Deprecated public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int); method public int requestAudioFocus(@NonNull android.media.AudioFocusRequest); + method public void setAllowedCapturePolicy(int); method @Deprecated public void setBluetoothA2dpOn(boolean); method public void setBluetoothScoOn(boolean); method public void setMicrophoneMute(boolean); @@ -28755,6 +28758,7 @@ package android.net { method @NonNull public static android.net.DnsResolver getInstance(); method public <T> void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.AnswerCallback<T>); method public <T> void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.AnswerCallback<T>); + method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.InetAddressAnswerCallback); field public static final int CLASS_IN = 1; // 0x1 field public static final int FLAG_EMPTY = 0; // 0x0 field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4 @@ -28789,11 +28793,11 @@ package android.net { } public final class IpPrefix implements android.os.Parcelable { - method public boolean contains(java.net.InetAddress); + method public boolean contains(@NonNull java.net.InetAddress); method public int describeContents(); - method public java.net.InetAddress getAddress(); - method public int getPrefixLength(); - method public byte[] getRawAddress(); + 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; } @@ -28866,7 +28870,7 @@ package android.net { method public int describeContents(); method public java.net.InetAddress getAddress(); method public int getFlags(); - method public int getPrefixLength(); + 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; @@ -29136,9 +29140,9 @@ package android.net { public final class RouteInfo implements android.os.Parcelable { method public int describeContents(); - method public android.net.IpPrefix getDestination(); - method public java.net.InetAddress getGateway(); - method public String getInterface(); + 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); @@ -34657,9 +34661,11 @@ package android.os { method @NonNull public static java.io.File getRootDirectory(); method @Deprecated public static String getStorageState(java.io.File); method public static boolean isExternalStorageEmulated(); - method public static boolean isExternalStorageEmulated(java.io.File); + method public static boolean isExternalStorageEmulated(@NonNull java.io.File); method public static boolean isExternalStorageRemovable(); - method public static boolean isExternalStorageRemovable(java.io.File); + method public static boolean isExternalStorageRemovable(@NonNull java.io.File); + method public static boolean isExternalStorageSandboxed(); + method public static boolean isExternalStorageSandboxed(@NonNull java.io.File); field public static String DIRECTORY_ALARMS; field public static String DIRECTORY_AUDIOBOOKS; field public static String DIRECTORY_DCIM; @@ -42529,6 +42535,7 @@ package android.system { method public static int getpid(); method public static int getppid(); method public static java.net.SocketAddress getsockname(java.io.FileDescriptor) throws android.system.ErrnoException; + method @NonNull public static android.system.StructTimeval getsockoptTimeval(@NonNull java.io.FileDescriptor, int, int) throws android.system.ErrnoException; method public static int gettid(); method public static int getuid(); method public static byte[] getxattr(String, String) throws android.system.ErrnoException; @@ -42579,6 +42586,7 @@ package android.system { method @Deprecated public static void setgid(int) throws android.system.ErrnoException; method public static int setsid() throws android.system.ErrnoException; method public static void setsockoptInt(java.io.FileDescriptor, int, int, int) throws android.system.ErrnoException; + method public static void setsockoptTimeval(@NonNull java.io.FileDescriptor, int, int, @NonNull android.system.StructTimeval) throws android.system.ErrnoException; method @Deprecated public static void setuid(int) throws android.system.ErrnoException; method public static void setxattr(String, String, byte[], int) throws android.system.ErrnoException; method public static void shutdown(java.io.FileDescriptor, int) throws android.system.ErrnoException; @@ -42784,6 +42792,10 @@ package android.system { field public static final int F_SETOWN; field public static final int F_UNLCK; field public static final int F_WRLCK; + field public static final int ICMP6_ECHO_REPLY; + field public static final int ICMP6_ECHO_REQUEST; + field public static final int ICMP_ECHO; + field public static final int ICMP_ECHOREPLY; field public static final int IFA_F_DADFAILED; field public static final int IFA_F_DEPRECATED; field public static final int IFA_F_HOMEADDRESS; @@ -43156,6 +43168,13 @@ package android.system { field public final long tv_sec; } + public final class StructTimeval { + method @NonNull public static android.system.StructTimeval fromMillis(long); + method public long toMillis(); + field public final long tv_sec; + field public final long tv_usec; + } + public final class StructUtsname { ctor public StructUtsname(String, String, String, String, String); field public final String machine; @@ -52311,7 +52330,7 @@ package android.view.accessibility { method public java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getActionList(); method @Deprecated public int getActions(); method public java.util.List<java.lang.String> getAvailableExtraData(); - method public void getBoundsInParent(android.graphics.Rect); + method @Deprecated public void getBoundsInParent(android.graphics.Rect); method public void getBoundsInScreen(android.graphics.Rect); method public android.view.accessibility.AccessibilityNodeInfo getChild(int); method public int getChildCount(); @@ -52380,7 +52399,7 @@ package android.view.accessibility { method public boolean removeChild(android.view.View, int); method public void setAccessibilityFocused(boolean); method public void setAvailableExtraData(java.util.List<java.lang.String>); - method public void setBoundsInParent(android.graphics.Rect); + method @Deprecated public void setBoundsInParent(android.graphics.Rect); method public void setBoundsInScreen(android.graphics.Rect); method public void setCanOpenPopup(boolean); method public void setCheckable(boolean); diff --git a/api/system-current.txt b/api/system-current.txt index 0dd82500869f..a1b182697345 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -119,6 +119,7 @@ package android { field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS"; field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE"; field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE"; + field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING"; field public static final String NETWORK_MANAGED_PROVISIONING = "android.permission.NETWORK_MANAGED_PROVISIONING"; field public static final String NETWORK_SCAN = "android.permission.NETWORK_SCAN"; field public static final String NETWORK_SETUP_WIZARD = "android.permission.NETWORK_SETUP_WIZARD"; @@ -565,7 +566,7 @@ package android.app { method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException; method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException; method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean removeConfiguration(long); - method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException; + method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) @NonNull public long[] setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException; method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setBroadcastSubscriber(android.app.PendingIntent, long, long) throws android.app.StatsManager.StatsUnavailableException; method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent); method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setDataFetchOperation(long, android.app.PendingIntent); @@ -1258,9 +1259,9 @@ package android.bluetooth { public final class BluetoothDevice implements android.os.Parcelable { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public String getMetadata(int); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean getSilenceMode(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isInSilenceMode(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, String); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int); @@ -1269,7 +1270,6 @@ package android.bluetooth { field public static final int ACCESS_REJECTED = 2; // 0x2 field public static final int ACCESS_UNKNOWN = 0; // 0x0 field public static final String ACTION_SILENCE_MODE_CHANGED = "android.bluetooth.device.action.SILENCE_MODE_CHANGED"; - field public static final String EXTRA_SILENCE_ENABLED = "android.bluetooth.device.extra.SILENCE_ENABLED"; field public static final int METADATA_COMPANION_APP = 4; // 0x4 field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10 field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3 @@ -1458,14 +1458,14 @@ package android.content.om { public final class OverlayInfo implements android.os.Parcelable { method public int describeContents(); + method @Nullable public String getCategory(); + method @NonNull public String getPackageName(); + method @Nullable public String getTargetOverlayableName(); + method @Nullable public String getTargetPackageName(); + method public int getUserId(); method public boolean isEnabled(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayInfo> CREATOR; - field public final String category; - field public final String packageName; - field public final String targetOverlayableName; - field public final String targetPackageName; - field public final int userId; } public class OverlayManager { @@ -1593,46 +1593,46 @@ package android.content.pm { } public abstract class PackageManager { - method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); + method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void addOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener); method public abstract boolean arePermissionsIndividuallyControlled(); - method public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(String); + method @NonNull public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(@NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public android.content.pm.dex.ArtManager getArtManager(); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_SHARED_LIBRARIES) public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String, int); - method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract String getDefaultBrowserPackageNameAsUser(int); + method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract String getDefaultBrowserPackageNameAsUser(int); method @Nullable @RequiresPermission(android.Manifest.permission.SET_HARMFUL_APP_WARNINGS) public CharSequence getHarmfulAppWarning(@NonNull String); method @Nullable public String getIncidentReportApproverPackageName(); - method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int); + method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int); method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract android.graphics.drawable.Drawable getInstantAppIcon(String); - method public abstract android.content.ComponentName getInstantAppInstallerComponent(); - method public abstract android.content.ComponentName getInstantAppResolverSettingsComponent(); + method @Nullable public abstract android.content.ComponentName getInstantAppInstallerComponent(); + method @Nullable public abstract android.content.ComponentName getInstantAppResolverSettingsComponent(); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract java.util.List<android.content.pm.InstantAppInfo> getInstantApps(); - method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String); - method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(String, int); - method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(String, String, @NonNull android.os.UserHandle); + method @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String); + method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int); + method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]); method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); - method @Deprecated public abstract int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException; - method @Deprecated public abstract int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException; - method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, android.os.UserHandle); + method @Deprecated public abstract int installExistingPackage(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @Deprecated public abstract int installExistingPackage(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, int, android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle); - method public abstract void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback); - method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); + method public abstract void registerDexModule(@NonNull String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback); + method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void removeOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener); method @Deprecated public void replacePreferredActivity(@NonNull android.content.IntentFilter, int, @NonNull java.util.List<android.content.ComponentName>, @NonNull android.content.ComponentName); method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public abstract void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method public void sendDeviceCustomizationReadyBroadcast(); - method @RequiresPermission(allOf={android.Manifest.permission.SET_PREFERRED_APPLICATIONS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public abstract boolean setDefaultBrowserPackageNameAsUser(String, int); + method @RequiresPermission(allOf={android.Manifest.permission.SET_PREFERRED_APPLICATIONS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public abstract boolean setDefaultBrowserPackageNameAsUser(@Nullable String, int); method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setDistractingPackageRestrictions(@NonNull String[], int); method @RequiresPermission(android.Manifest.permission.SET_HARMFUL_APP_WARNINGS) public void setHarmfulAppWarning(@NonNull String, @Nullable CharSequence); method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable String); method @Nullable @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo); method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setSyntheticAppDetailsActivityEnabled(@NonNull String, boolean); - method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(String, boolean); - method @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public abstract boolean updateIntentVerificationStatusAsUser(String, int, int); - method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(String, String, @android.content.pm.PackageManager.PermissionFlags int, @android.content.pm.PackageManager.PermissionFlags int, @NonNull android.os.UserHandle); - method @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, java.util.List<java.lang.String>); + method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean); + method @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String, int, int); + method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, @android.content.pm.PackageManager.PermissionFlags int, @android.content.pm.PackageManager.PermissionFlags int, @NonNull android.os.UserHandle); + method @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>); field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS"; field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES"; field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS"; @@ -4061,7 +4061,7 @@ package android.net { } public final class IpPrefix implements android.os.Parcelable { - ctor public IpPrefix(@NonNull java.net.InetAddress, int); + ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); ctor public IpPrefix(@NonNull String); } @@ -4082,8 +4082,8 @@ package android.net { } public class LinkAddress implements android.os.Parcelable { - ctor public LinkAddress(java.net.InetAddress, int, int, int); - ctor public LinkAddress(@NonNull java.net.InetAddress, int); + 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); ctor public LinkAddress(@NonNull String); ctor public LinkAddress(@NonNull String, int, int); method public boolean isGlobalPreferred(); @@ -4264,8 +4264,8 @@ 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(@NonNull android.content.Context); - method @NonNull public static int[] getApfEthTypeBlackList(@NonNull android.content.Context); + 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; diff --git a/api/test-current.txt b/api/test-current.txt index 6f8f310f6a2c..84e641e0145e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -315,7 +315,9 @@ package android.app { } public final class NotificationChannel implements android.os.Parcelable { + method public boolean isImportanceLockedByCriticalDeviceFunction(); method public boolean isImportanceLockedByOEM(); + method public void setImportanceLockedByCriticalDeviceFunction(boolean); method public void setImportanceLockedByOEM(boolean); } @@ -661,26 +663,28 @@ package android.content.pm { public abstract class PackageManager { method public abstract boolean arePermissionsIndividuallyControlled(); - method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public abstract String getDefaultBrowserPackageNameAsUser(int); + method @Nullable @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public abstract String getDefaultBrowserPackageNameAsUser(int); method @Nullable public String getIncidentReportApproverPackageName(); - method public abstract int getInstallReason(String, @NonNull android.os.UserHandle); - method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int); - method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int); + method public abstract int getInstallReason(@NonNull String, @NonNull android.os.UserHandle); + method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int); + method @NonNull @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int); method @Nullable public abstract String[] getNamesForUids(int[]); - method public abstract String getPermissionControllerPackageName(); - method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.REVOKE_RUNTIME_PERMISSIONS", "android.permission.GET_RUNTIME_PERMISSIONS"}) public abstract int getPermissionFlags(String, String, @NonNull android.os.UserHandle); + method @NonNull public abstract String getPermissionControllerPackageName(); + method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.REVOKE_RUNTIME_PERMISSIONS", "android.permission.GET_RUNTIME_PERMISSIONS"}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method @NonNull public abstract String getServicesSystemSharedLibraryPackageName(); method @NonNull public abstract String getSharedSystemSharedLibraryPackageName(); - method public String getWellbeingPackageName(); + method @Nullable public String getWellbeingPackageName(); method @RequiresPermission("android.permission.GRANT_RUNTIME_PERMISSIONS") public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public abstract void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); - method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.REVOKE_RUNTIME_PERMISSIONS"}) public abstract void updatePermissionFlags(String, String, int, int, @NonNull android.os.UserHandle); + method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.REVOKE_RUNTIME_PERMISSIONS"}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, int, int, @NonNull android.os.UserHandle); field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage"; field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption"; field public static final int FLAG_PERMISSION_HIDDEN = 1024; // 0x400 + field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4 field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40 field public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 8; // 0x8 field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80 + field public static final int FLAG_PERMISSION_SYSTEM_FIXED = 16; // 0x10 field public static final int FLAG_PERMISSION_USER_FIXED = 2; // 0x2 field public static final int FLAG_PERMISSION_USER_SET = 1; // 0x1 field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 @@ -1237,7 +1241,7 @@ package android.net { } public final class IpPrefix implements android.os.Parcelable { - ctor public IpPrefix(@NonNull java.net.InetAddress, int); + ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); ctor public IpPrefix(@NonNull String); } @@ -1246,8 +1250,8 @@ package android.net { } public class LinkAddress implements android.os.Parcelable { - ctor public LinkAddress(java.net.InetAddress, int, int, int); - ctor public LinkAddress(@NonNull java.net.InetAddress, int); + 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); ctor public LinkAddress(@NonNull String); ctor public LinkAddress(@NonNull String, int, int); method public boolean isGlobalPreferred(); @@ -1356,8 +1360,8 @@ 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(@NonNull android.content.Context); - method @NonNull public static int[] getApfEthTypeBlackList(@NonNull android.content.Context); + 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; diff --git a/cmds/bmgr/Android.bp b/cmds/bmgr/Android.bp new file mode 100644 index 000000000000..b64923bcbe1b --- /dev/null +++ b/cmds/bmgr/Android.bp @@ -0,0 +1,8 @@ +// Copyright 2007 The Android Open Source Project +// + +java_binary { + name: "bmgr", + wrapper: "bmgr", + srcs: ["**/*.java"], +} diff --git a/cmds/bmgr/Android.mk b/cmds/bmgr/Android.mk deleted file mode 100644 index d520cf2143ee..000000000000 --- a/cmds/bmgr/Android.mk +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2007 The Android Open Source Project -# -LOCAL_PATH:= $(call my-dir) - -include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_MODULE := bmgrlib -LOCAL_MODULE_STEM := bmgr -include $(BUILD_JAVA_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE := bmgr -LOCAL_MODULE_CLASS := EXECUTABLES -LOCAL_SRC_FILES := bmgr -LOCAL_REQUIRED_MODULES := bmgrlib -include $(BUILD_PREBUILT) diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp index 685c0677c8fa..b46c9e357fc4 100644 --- a/cmds/incidentd/src/FdBuffer.cpp +++ b/cmds/incidentd/src/FdBuffer.cpp @@ -31,7 +31,7 @@ namespace os { namespace incidentd { const ssize_t BUFFER_SIZE = 16 * 1024; // 16 KB -const ssize_t MAX_BUFFER_COUNT = 256; // 4 MB max +const ssize_t MAX_BUFFER_COUNT = 1536; // 24 MB max FdBuffer::FdBuffer() :mBuffer(new EncodedBuffer(BUFFER_SIZE)), diff --git a/cmds/media/Android.bp b/cmds/media/Android.bp new file mode 100644 index 000000000000..7879c53684a7 --- /dev/null +++ b/cmds/media/Android.bp @@ -0,0 +1,8 @@ +// Copyright 2013 The Android Open Source Project +// + +java_binary { + name: "media", + wrapper: "media", + srcs: ["**/*.java"], +} diff --git a/cmds/media/Android.mk b/cmds/media/Android.mk deleted file mode 100644 index b9451c596da0..000000000000 --- a/cmds/media/Android.mk +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2013 The Android Open Source Project -# -LOCAL_PATH:= $(call my-dir) - -include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_MODULE := media_cmd -include $(BUILD_JAVA_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE := media -LOCAL_SRC_FILES := media -LOCAL_MODULE_CLASS := EXECUTABLES -LOCAL_MODULE_TAGS := optional -include $(BUILD_PREBUILT) diff --git a/cmds/media/media b/cmds/media/media index 5c0eb2f39c2d..8ada9145a4f7 100755 --- a/cmds/media/media +++ b/cmds/media/media @@ -3,5 +3,5 @@ # shell. # base=/system -export CLASSPATH=$base/framework/media_cmd.jar +export CLASSPATH=$base/framework/media.jar exec app_process $base/bin com.android.commands.media.Media "$@" diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 8cd409e5e8c1..017cb6d9221e 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -81,7 +81,7 @@ cc_defaults { "src/external/StatsPullerManager.cpp", "src/external/puller_util.cpp", "src/logd/LogEvent.cpp", - "src/logd/LogListener.cpp", + "src/logd/LogEventQueue.cpp", "src/matchers/CombinationLogMatchingTracker.cpp", "src/matchers/EventMatcherWizard.cpp", "src/matchers/matcher_util.cpp", @@ -226,6 +226,7 @@ cc_test { "tests/indexed_priority_queue_test.cpp", "tests/LogEntryMatcher_test.cpp", "tests/LogEvent_test.cpp", + "tests/log_event/LogEventQueue_test.cpp", "tests/MetricsManager_test.cpp", "tests/StatsLogProcessor_test.cpp", "tests/StatsService_test.cpp", diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 52ecdc8425af..3bcebd9e3f76 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -65,8 +65,6 @@ constexpr const char* kOpUsage = "android:get_usage_stats"; // for StatsDataDumpProto const int FIELD_ID_REPORTS_LIST = 1; -// for TrainInfo experiment id serialization -const int FIELD_ID_EXPERIMENT_ID = 1; static binder::Status ok() { return binder::Status::ok(); @@ -132,34 +130,36 @@ binder::Status checkDumpAndUsageStats(const String16& packageName) { } \ } -StatsService::StatsService(const sp<Looper>& handlerLooper) - : mAnomalyAlarmMonitor(new AlarmMonitor(MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS, - [](const sp<IStatsCompanionService>& sc, int64_t timeMillis) { - if (sc != nullptr) { - sc->setAnomalyAlarm(timeMillis); - StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged(); - } - }, - [](const sp<IStatsCompanionService>& sc) { - if (sc != nullptr) { - sc->cancelAnomalyAlarm(); - StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged(); - } - })), - mPeriodicAlarmMonitor(new AlarmMonitor(MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS, - [](const sp<IStatsCompanionService>& sc, int64_t timeMillis) { - if (sc != nullptr) { - sc->setAlarmForSubscriberTriggering(timeMillis); - StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged(); - } - }, - [](const sp<IStatsCompanionService>& sc) { - if (sc != nullptr) { - sc->cancelAlarmForSubscriberTriggering(); - StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged(); - } - - })) { +StatsService::StatsService(const sp<Looper>& handlerLooper, shared_ptr<LogEventQueue> queue) + : mAnomalyAlarmMonitor(new AlarmMonitor( + MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS, + [](const sp<IStatsCompanionService>& sc, int64_t timeMillis) { + if (sc != nullptr) { + sc->setAnomalyAlarm(timeMillis); + StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged(); + } + }, + [](const sp<IStatsCompanionService>& sc) { + if (sc != nullptr) { + sc->cancelAnomalyAlarm(); + StatsdStats::getInstance().noteRegisteredAnomalyAlarmChanged(); + } + })), + mPeriodicAlarmMonitor(new AlarmMonitor( + MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS, + [](const sp<IStatsCompanionService>& sc, int64_t timeMillis) { + if (sc != nullptr) { + sc->setAlarmForSubscriberTriggering(timeMillis); + StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged(); + } + }, + [](const sp<IStatsCompanionService>& sc) { + if (sc != nullptr) { + sc->cancelAlarmForSubscriberTriggering(); + StatsdStats::getInstance().noteRegisteredPeriodicAlarmChanged(); + } + })), + mEventQueue(queue) { mUidMap = UidMap::getInstance(); mPullerManager = new StatsPullerManager(); StatsPuller::SetUidMap(mUidMap); @@ -201,11 +201,33 @@ StatsService::StatsService(const sp<Looper>& handlerLooper) mConfigManager->AddListener(mProcessor); init_system_properties(); + + if (mEventQueue != nullptr) { + std::thread pushedEventThread([this] { readLogs(); }); + pushedEventThread.detach(); + } } StatsService::~StatsService() { } +/* Runs on a dedicated thread to process pushed events. */ +void StatsService::readLogs() { + // Read forever..... long live statsd + while (1) { + // Block until an event is available. + auto event = mEventQueue->waitPop(); + // Pass it to StatsLogProcess to all configs/metrics + // At this point, the LogEventQueue is not blocked, so that the socketListener + // can read events from the socket and write to buffer to avoid data drop. + mProcessor->OnLogEvent(event.get()); + // The ShellSubscriber is only used by shell for local debugging. + if (mShellSubscriber != nullptr) { + mShellSubscriber->onLogEvent(*event); + } + } +} + void StatsService::init_system_properties() { mEngBuild = false; const prop_info* buildType = __system_property_find("ro.build.type"); @@ -1009,6 +1031,7 @@ void StatsService::Terminate() { } } +// Test only interface!!! void StatsService::OnLogEvent(LogEvent* event) { mProcessor->OnLogEvent(event); if (mShellSubscriber != nullptr) { @@ -1181,7 +1204,7 @@ Status StatsService::unregisterPullerCallback(int32_t atomTag, const String16& p Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainName, int64_t trainVersionCode, int options, int32_t state, - const std::vector<int64_t>& experimentIds) { + const std::vector<int64_t>& experimentIdsIn) { uid_t uid = IPCThreadState::self()->getCallingUid(); // For testing if (uid == AID_ROOT || uid == AID_SYSTEM || uid == AID_SHELL) { @@ -1201,7 +1224,7 @@ Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& tra bool readTrainInfoSuccess = false; InstallTrainInfo trainInfo; - if (trainVersionCode == -1 || experimentIds.empty() || trainName.size() == 0) { + if (trainVersionCode == -1 || experimentIdsIn.empty() || trainName.size() == 0) { readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfo); } @@ -1209,27 +1232,19 @@ Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& tra trainVersionCode = trainInfo.trainVersionCode; } - vector<uint8_t> experimentIdsProtoBuffer; - if (readTrainInfoSuccess && experimentIds.empty()) { - experimentIdsProtoBuffer = trainInfo.experimentIds; + // Find the right experiment IDs + std::vector<int64_t> experimentIds; + if (readTrainInfoSuccess && experimentIdsIn.empty()) { + experimentIds = trainInfo.experimentIds; } else { - ProtoOutputStream proto; - for (const auto& expId : experimentIds) { - proto.write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_EXPERIMENT_ID, - (long long)expId); - } - - experimentIdsProtoBuffer.resize(proto.size()); - size_t pos = 0; - sp<ProtoReader> reader = proto.data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&(experimentIdsProtoBuffer[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } + experimentIds = experimentIdsIn; } + // Flatten the experiment IDs to proto + vector<uint8_t> experimentIdsProtoBuffer; + writeExperimentIdsToProto(experimentIds, &experimentIdsProtoBuffer); + + // Find the right train name std::string trainNameUtf8; if (readTrainInfoSuccess && trainName.size() == 0) { trainNameUtf8 = trainInfo.trainName; @@ -1244,7 +1259,34 @@ Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& tra LogEvent event(trainNameUtf8, trainVersionCode, requiresStaging, rollbackEnabled, requiresLowLatencyMonitor, state, experimentIdsProtoBuffer, userId); mProcessor->OnLogEvent(&event); - StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIdsProtoBuffer); + StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds); + return Status::ok(); +} + +Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) { + uid_t uid = IPCThreadState::self()->getCallingUid(); + + // Caller must be granted these permissions + if (!checkCallingPermission(String16(kPermissionDump))) { + return exception(binder::Status::EX_SECURITY, + StringPrintf("UID %d lacks permission %s", uid, kPermissionDump)); + } + if (!checkCallingPermission(String16(kPermissionUsage))) { + return exception(binder::Status::EX_SECURITY, + StringPrintf("UID %d lacks permission %s", uid, kPermissionUsage)); + } + // TODO: add verifier permission + + // Read the latest train info + InstallTrainInfo trainInfo; + if (!StorageManager::readTrainInfo(trainInfo)) { + // No train info means no experiment IDs, return an empty list + experimentIdsOut->clear(); + return Status::ok(); + } + + // Copy the experiment IDs to the out vector + experimentIdsOut->assign(trainInfo.experimentIds.begin(), trainInfo.experimentIds.end()); return Status::ok(); } diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index d24565a63054..929d260dcd95 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -22,7 +22,7 @@ #include "anomaly/AlarmMonitor.h" #include "config/ConfigManager.h" #include "external/StatsPullerManager.h" -#include "logd/LogListener.h" +#include "logd/LogEventQueue.h" #include "packages/UidMap.h" #include "shell/ShellSubscriber.h" #include "statscompanion_util.h" @@ -52,11 +52,10 @@ namespace statsd { using android::hardware::Return; class StatsService : public BnStatsManager, - public LogListener, public IStats, public IBinder::DeathRecipient { public: - StatsService(const sp<Looper>& handlerLooper); + StatsService(const sp<Looper>& handlerLooper, std::shared_ptr<LogEventQueue> queue); virtual ~StatsService(); /** The anomaly alarm registered with AlarmManager won't be updated by less than this. */ @@ -92,7 +91,7 @@ public: void Terminate(); /** - * Called by LogReader when there's a log event to process. + * Test ONLY interface. In real world, StatsService reads from LogEventQueue. */ virtual void OnLogEvent(LogEvent* event); @@ -194,6 +193,11 @@ public: int32_t state, const std::vector<int64_t>& experimentIds) override; /** + * Binder call to get registered experiment IDs. + */ + virtual Status getRegisteredExperimentIds(std::vector<int64_t>* expIdsOut); + + /** * Binder call to get SpeakerImpedance atom. */ virtual Return<void> reportSpeakerImpedance(const SpeakerImpedance& speakerImpedance) override; @@ -278,6 +282,9 @@ private: */ void print_cmd_help(int out); + /* Runs on its dedicated thread to process pushed stats event from socket. */ + void readLogs(); + /** * Trigger a broadcast. */ @@ -410,6 +417,8 @@ private: sp<ShellSubscriber> mShellSubscriber; + std::shared_ptr<LogEventQueue> mEventQueue; + FRIEND_TEST(StatsServiceTest, TestAddConfig_simple); FRIEND_TEST(StatsServiceTest, TestAddConfig_empty); FRIEND_TEST(StatsServiceTest, TestAddConfig_invalid); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 5528c33ff158..1ffde972566f 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -187,7 +187,7 @@ message Atom { WifiEnabledStateChanged wifi_enabled_state_changed = 113; WifiRunningStateChanged wifi_running_state_changed = 114; AppCompacted app_compacted = 115; - NetworkDnsEventReported network_dns_event_reported = 116; + NetworkDnsEventReported network_dns_event_reported = 116 [(log_from_module) = "resolv"]; DocsUIPickerLaunchedFromReported docs_ui_picker_launched_from_reported = 117; DocsUIPickResultReported docs_ui_pick_result_reported = 118; DocsUISearchModeReported docs_ui_search_mode_reported = 119; @@ -254,6 +254,7 @@ message Atom { PrivacyIndicatorsInteracted privacy_indicators_interacted = 180; AppInstallOnExternalStorageReported app_install_on_external_storage_reported = 181; NetworkStackReported network_stack_reported = 182; + AppMovedStorageReported app_moved_storage_reported = 183; } // Pulled events will start at field 10000. @@ -3446,6 +3447,30 @@ message PrivacyIndicatorsInteracted { optional string package_name = 2; } +/** + * Logs information about a package that is moved from the internal to external storage and vice + * versa. + * It logs the package name, the type of the external storage where the package is installed + * (if moved to external storage, or UNKNOWN if moved to internal storage), + * and the move type: if it's from internal to external or the other way around. + * + * Logged from: + frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java + */ +message AppMovedStorageReported { + enum MoveType { + UNKNOWN = 0; + TO_EXTERNAL = 1; + TO_INTERNAL = 2; + } + // The type of the external storage. + optional android.stats.storage.ExternalStorageType external_storage_type = 1; + // The type of move. + optional MoveType move_type = 2; + // The name of the package that was moved. + optional string package_name = 3; +} + ////////////////////////////////////////////////////////////////////// // Pulled atoms below this line // ////////////////////////////////////////////////////////////////////// @@ -5867,7 +5892,9 @@ message SystemIonHeapSize { * frameworks/base/packages/NetworkStack/ */ message NetworkStackReported { - optional int32 eventId = 1; + // The id that indicates the event reported from NetworkStack. + optional int32 event_id = 1; + // The data for the reported events. optional android.stats.connectivity.NetworkStackEventData network_stack_event = 2 [(log_mode) = MODE_BYTES]; } diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index 29100aad81aa..74a4c87af7c3 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -50,6 +50,7 @@ const int FIELD_ID_ANOMALY_ALARM_STATS = 9; const int FIELD_ID_PERIODIC_ALARM_STATS = 12; const int FIELD_ID_SYSTEM_SERVER_RESTART = 15; const int FIELD_ID_LOGGER_ERROR_STATS = 16; +const int FIELD_ID_OVERFLOW = 18; const int FIELD_ID_ATOM_STATS_TAG = 1; const int FIELD_ID_ATOM_STATS_COUNT = 2; @@ -64,6 +65,10 @@ const int FIELD_ID_LOG_LOSS_STATS_TAG = 4; const int FIELD_ID_LOG_LOSS_STATS_UID = 5; const int FIELD_ID_LOG_LOSS_STATS_PID = 6; +const int FIELD_ID_OVERFLOW_COUNT = 1; +const int FIELD_ID_OVERFLOW_MAX_HISTORY = 2; +const int FIELD_ID_OVERFLOW_MIN_HISTORY = 3; + const int FIELD_ID_CONFIG_STATS_UID = 1; const int FIELD_ID_CONFIG_STATS_ID = 2; const int FIELD_ID_CONFIG_STATS_CREATION = 3; @@ -235,6 +240,22 @@ void StatsdStats::noteDataDropped(const ConfigKey& key, const size_t totalBytes) noteDataDropped(key, totalBytes, getWallClockSec()); } +void StatsdStats::noteEventQueueOverflow(int64_t oldestEventTimestampNs) { + lock_guard<std::mutex> lock(mLock); + + mOverflowCount++; + + int64_t history = getElapsedRealtimeNs() - oldestEventTimestampNs; + + if (history > mMaxQueueHistoryNs) { + mMaxQueueHistoryNs = history; + } + + if (history < mMinQueueHistoryNs) { + mMinQueueHistoryNs = history; + } +} + void StatsdStats::noteDataDropped(const ConfigKey& key, const size_t totalBytes, int32_t timeSec) { lock_guard<std::mutex> lock(mLock); auto it = mConfigStats.find(key); @@ -534,6 +555,9 @@ void StatsdStats::resetInternalLocked() { mPeriodicAlarmRegisteredStats = 0; mSystemServerRestartSec.clear(); mLogLossStats.clear(); + mOverflowCount = 0; + mMinQueueHistoryNs = kInt64Max; + mMaxQueueHistoryNs = 0; for (auto& config : mConfigStats) { config.second->broadcast_sent_time_sec.clear(); config.second->activation_time_sec.clear(); @@ -726,6 +750,9 @@ void StatsdStats::dumpStats(int out) const { (long long)loss.mWallClockSec, loss.mCount, loss.mLastError, loss.mLastTag, loss.mUid, loss.mPid); } + + dprintf(out, "Event queue overflow: %d; MaxHistoryNs: %lld; MinHistoryNs: %lld\n", + mOverflowCount, (long long)mMaxQueueHistoryNs, (long long)mMinQueueHistoryNs); } void addConfigStatsToProto(const ConfigStats& configStats, ProtoOutputStream* proto) { @@ -904,6 +931,16 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { proto.end(token); } + if (mOverflowCount > 0) { + uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_OVERFLOW); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_OVERFLOW_COUNT, (int32_t)mOverflowCount); + proto.write(FIELD_TYPE_INT64 | FIELD_ID_OVERFLOW_MAX_HISTORY, + (long long)mMaxQueueHistoryNs); + proto.write(FIELD_TYPE_INT64 | FIELD_ID_OVERFLOW_MIN_HISTORY, + (long long)mMinQueueHistoryNs); + proto.end(token); + } + for (const auto& restart : mSystemServerRestartSec) { proto.write(FIELD_TYPE_INT32 | FIELD_ID_SYSTEM_SERVER_RESTART | FIELD_COUNT_REPEATED, restart); diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index 434920ebe8c7..88ecccc32407 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -160,6 +160,8 @@ public: // Max platform atom tag number. static const int32_t kMaxPlatformAtomTag = 100000; + static const int64_t kInt64Max = 0x7fffffffffffffffLL; + /** * Report a new config has been received and report the static stats about the config. * @@ -419,6 +421,10 @@ public: */ void noteBucketUnknownCondition(int64_t metricId); + /* Reports one event has been dropped due to queue overflow, and the oldest event timestamp in + * the queue */ + void noteEventQueueOverflow(int64_t oldestEventTimestampNs); + /** * Reset the historical stats. Including all stats in icebox, and the tracked stats about * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue @@ -522,6 +528,17 @@ private: int32_t mPid; }; + // Max of {(now - oldestEventTimestamp) when overflow happens}. + // This number is helpful to understand how SLOW statsd can be. + int64_t mMaxQueueHistoryNs = 0; + + // Min of {(now - oldestEventTimestamp) when overflow happens}. + // This number is helpful to understand how FAST the events floods to statsd. + int64_t mMinQueueHistoryNs = kInt64Max; + + // Total number of events that are lost due to queue overflow. + int32_t mOverflowCount = 0; + // Timestamps when we detect log loss, and the number of logs lost. std::list<LogLossStats> mLogLossStats; diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index d9f5415463e3..ca874b57170e 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -27,6 +27,9 @@ namespace android { namespace os { namespace statsd { +// for TrainInfo experiment id serialization +const int FIELD_ID_EXPERIMENT_ID = 1; + using namespace android::util; using android::util::ProtoOutputStream; using std::string; @@ -118,6 +121,7 @@ void LogEvent::createLogEvents(const StatsLogEventWrapper& statsLogEventWrapper, LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs) { mLogdTimestampNs = wallClockTimestampNs; + mElapsedTimestampNs = elapsedTimestampNs; mTagId = tagId; mLogUid = 0; mContext = create_android_logger(1937006964); // the event tag shared by all stats logs @@ -241,12 +245,15 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, mValues.push_back( FieldValue(Field(mTagId, getSimpleField(1)), Value(trainInfo.trainVersionCode))); - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainInfo.experimentIds))); + std::vector<uint8_t> experimentIdsProto; + writeExperimentIdsToProto(trainInfo.experimentIds, &experimentIdsProto); + mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(experimentIdsProto))); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), Value(trainInfo.trainName))); mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status))); } -LogEvent::LogEvent(int32_t tagId, int64_t timestampNs) : LogEvent(tagId, timestampNs, 0) {} +LogEvent::LogEvent(int32_t tagId, int64_t timestampNs) : LogEvent(tagId, timestampNs, timestampNs) { +} LogEvent::LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid) { mLogdTimestampNs = timestampNs; @@ -671,6 +678,24 @@ void LogEvent::ToProto(ProtoOutputStream& protoOutput) const { writeFieldValueTreeToStream(mTagId, getValues(), &protoOutput); } +void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut) { + ProtoOutputStream proto; + for (const auto& expId : experimentIds) { + proto.write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_EXPERIMENT_ID, + (long long)expId); + } + + protoOut->resize(proto.size()); + size_t pos = 0; + sp<ProtoReader> reader = proto.data(); + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + std::memcpy(protoOut->data() + pos, reader->readBuffer(), toRead); + pos += toRead; + reader->move(toRead); + } +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 753a9a500c57..531ce299beef 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -60,8 +60,9 @@ struct InstallTrainInfo { int64_t trainVersionCode; std::string trainName; int32_t status; - std::vector<uint8_t> experimentIds; + std::vector<int64_t> experimentIds; }; + /** * Wrapper for the log_msg structure. */ @@ -239,6 +240,8 @@ private: uint32_t mLogUid; }; +void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut); + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/logd/LogEventQueue.cpp b/cmds/statsd/src/logd/LogEventQueue.cpp new file mode 100644 index 000000000000..146464bbe774 --- /dev/null +++ b/cmds/statsd/src/logd/LogEventQueue.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define DEBUG false // STOPSHIP if true +#include "Log.h" + +#include "LogEventQueue.h" + +namespace android { +namespace os { +namespace statsd { + +using std::unique_lock; +using std::unique_ptr; + +unique_ptr<LogEvent> LogEventQueue::waitPop() { + std::unique_lock<std::mutex> lock(mMutex); + + if (mQueue.empty()) { + mCondition.wait(lock, [this] { return !this->mQueue.empty(); }); + } + + unique_ptr<LogEvent> item = std::move(mQueue.front()); + mQueue.pop(); + + return item; +} + +bool LogEventQueue::push(unique_ptr<LogEvent> item, int64_t* oldestTimestampNs) { + bool success; + { + std::unique_lock<std::mutex> lock(mMutex); + if (mQueue.size() < mQueueLimit) { + mQueue.push(std::move(item)); + success = true; + } else { + // safe operation as queue must not be empty. + *oldestTimestampNs = mQueue.front()->GetElapsedTimestampNs(); + success = false; + } + } + + mCondition.notify_one(); + return success; +} + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/logd/LogEventQueue.h b/cmds/statsd/src/logd/LogEventQueue.h new file mode 100644 index 000000000000..b4fd63f119e6 --- /dev/null +++ b/cmds/statsd/src/logd/LogEventQueue.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "LogEvent.h" + +#include <condition_variable> +#include <memory> +#include <mutex> +#include <queue> +#include <thread> + +namespace android { +namespace os { +namespace statsd { + +/** + * A zero copy thread safe queue buffer for producing and consuming LogEvent. + */ +class LogEventQueue { +public: + explicit LogEventQueue(size_t maxSize) : mQueueLimit(maxSize){}; + + /** + * Blocking read one event from the queue. + */ + std::unique_ptr<LogEvent> waitPop(); + + /** + * Puts a LogEvent ptr to the end of the queue. + * Returns false on failure when the queue is full, and output the oldest event timestamp + * in the queue. + */ + bool push(std::unique_ptr<LogEvent> event, int64_t* oldestTimestampNs); + +private: + const size_t mQueueLimit; + std::condition_variable mCondition; + std::mutex mMutex; + std::queue<std::unique_ptr<LogEvent>> mQueue; +}; + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp index eddc86eca798..68082c2dc4d2 100644 --- a/cmds/statsd/src/main.cpp +++ b/cmds/statsd/src/main.cpp @@ -80,8 +80,11 @@ int main(int /*argc*/, char** /*argv*/) { ::android::hardware::configureRpcThreadpool(1 /*threads*/, false /*willJoin*/); + std::shared_ptr<LogEventQueue> eventQueue = + std::make_shared<LogEventQueue>(2000 /*buffer limit. Buffer is NOT pre-allocated*/); + // Create the service - gStatsService = new StatsService(looper); + gStatsService = new StatsService(looper, eventQueue); if (defaultServiceManager()->addService(String16("stats"), gStatsService, false, IServiceManager::DUMP_FLAG_PRIORITY_NORMAL | IServiceManager::DUMP_FLAG_PROTO) != 0) { @@ -101,13 +104,13 @@ int main(int /*argc*/, char** /*argv*/) { gStatsService->Startup(); - sp<StatsSocketListener> socketListener = new StatsSocketListener(gStatsService); + sp<StatsSocketListener> socketListener = new StatsSocketListener(eventQueue); - ALOGI("using statsd socket"); - // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value - if (socketListener->startListener(600)) { - exit(1); - } + ALOGI("Statsd starts to listen to socket."); + // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value + if (socketListener->startListener(600)) { + exit(1); + } // Loop forever -- the reports run on this thread in a handler, and the // binder calls remain responsive in their pool of one thread. diff --git a/cmds/statsd/src/socket/StatsSocketListener.cpp b/cmds/statsd/src/socket/StatsSocketListener.cpp index aed926d74b26..92200f99c3cc 100755 --- a/cmds/statsd/src/socket/StatsSocketListener.cpp +++ b/cmds/statsd/src/socket/StatsSocketListener.cpp @@ -41,8 +41,8 @@ namespace statsd { static const int kLogMsgHeaderSize = 28; -StatsSocketListener::StatsSocketListener(const sp<LogListener>& listener) - : SocketListener(getLogSocket(), false /*start listen*/), mListener(listener) { +StatsSocketListener::StatsSocketListener(std::shared_ptr<LogEventQueue> queue) + : SocketListener(getLogSocket(), false /*start listen*/), mQueue(queue) { } StatsSocketListener::~StatsSocketListener() { @@ -134,10 +134,11 @@ bool StatsSocketListener::onDataAvailable(SocketClient* cli) { msg.entry.uid = cred->uid; memcpy(msg.buf + kLogMsgHeaderSize, ptr, n + 1); - LogEvent event(msg); - // Call the listener - mListener->OnLogEvent(&event); + int64_t oldestTimestamp; + if (!mQueue->push(std::make_unique<LogEvent>(msg), &oldestTimestamp)) { + StatsdStats::getInstance().noteEventQueueOverflow(oldestTimestamp); + } return true; } diff --git a/cmds/statsd/src/socket/StatsSocketListener.h b/cmds/statsd/src/socket/StatsSocketListener.h index b8185d2a32d2..2167a56445b9 100644 --- a/cmds/statsd/src/socket/StatsSocketListener.h +++ b/cmds/statsd/src/socket/StatsSocketListener.h @@ -17,7 +17,7 @@ #include <sysutils/SocketListener.h> #include <utils/RefBase.h> -#include "logd/LogListener.h" +#include "logd/LogEventQueue.h" // DEFAULT_OVERFLOWUID is defined in linux/highuid.h, which is not part of // the uapi headers for userspace to use. This value is filled in on the @@ -35,7 +35,7 @@ namespace statsd { class StatsSocketListener : public SocketListener, public virtual android::RefBase { public: - explicit StatsSocketListener(const sp<LogListener>& listener); + explicit StatsSocketListener(std::shared_ptr<LogEventQueue> queue); virtual ~StatsSocketListener(); @@ -47,7 +47,7 @@ private: /** * Who is going to get the events when they're read. */ - sp<LogListener> mListener; + std::shared_ptr<LogEventQueue> mQueue; }; } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index 967c352562f7..1dfc433cf0e2 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -460,6 +460,14 @@ message StatsdStatsReport { optional int32 pid = 6; } repeated LogLossStats detected_log_loss = 16; + + message EventQueueOverflow { + optional int32 count = 1; + optional int64 max_queue_history_ns = 2; + optional int64 min_queue_history_ns = 3; + } + + optional EventQueueOverflow queue_overflow = 18; } message AlertTriggerDetails { diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 65b183c6fc96..cf8b97494a06 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -36,9 +36,17 @@ using android::util::FIELD_COUNT_REPEATED; using android::util::FIELD_TYPE_MESSAGE; using std::map; +/** + * NOTE: these directories are protected by SELinux, any changes here must also update + * the SELinux policies. + */ #define STATS_DATA_DIR "/data/misc/stats-data" #define STATS_SERVICE_DIR "/data/misc/stats-service" #define TRAIN_INFO_DIR "/data/misc/train-info" +#define TRAIN_INFO_PATH "/data/misc/train-info/train-info.bin" + +// Magic word at the start of the train info file, change this if changing the file format +const uint32_t TRAIN_INFO_FILE_MAGIC = 0xff7447ff; // for ConfigMetricsReportList const int FIELD_ID_REPORTS = 2; @@ -96,27 +104,42 @@ void StorageManager::writeFile(const char* file, const void* buffer, int numByte } bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& trainName, - int32_t status, const std::vector<uint8_t>& experimentIds) { + int32_t status, const std::vector<int64_t>& experimentIds) { std::lock_guard<std::mutex> lock(sTrainInfoMutex); deleteAllFiles(TRAIN_INFO_DIR); - string file_name = StringPrintf("%s/%lld", TRAIN_INFO_DIR, (long long)trainVersionCode); - - int fd = open(file_name.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); + int fd = open(TRAIN_INFO_PATH, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); if (fd == -1) { - VLOG("Attempt to access %s but failed", file_name.c_str()); + VLOG("Attempt to access %s but failed", TRAIN_INFO_PATH); return false; } size_t result; + // Write the magic word + result = write(fd, &TRAIN_INFO_FILE_MAGIC, sizeof(TRAIN_INFO_FILE_MAGIC)); + if (result != sizeof(TRAIN_INFO_FILE_MAGIC)) { + VLOG("Failed to wrtie train info magic"); + close(fd); + return false; + } + + // Write the train version + const size_t trainVersionCodeByteCount = sizeof(trainVersionCode); + result = write(fd, &trainVersionCode, trainVersionCodeByteCount); + if (result != trainVersionCodeByteCount) { + VLOG("Failed to wrtie train version code"); + close(fd); + return false; + } + // Write # of bytes in trainName to file const size_t trainNameSize = trainName.size(); const size_t trainNameSizeByteCount = sizeof(trainNameSize); result = write(fd, (uint8_t*)&trainNameSize, trainNameSizeByteCount); if (result != trainNameSizeByteCount) { - VLOG("Failed to write train name size for %s", file_name.c_str()); + VLOG("Failed to write train name size"); close(fd); return false; } @@ -124,7 +147,7 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& // Write trainName to file result = write(fd, trainName.c_str(), trainNameSize); if (result != trainNameSize) { - VLOG("Failed to write train name for%s", file_name.c_str()); + VLOG("Failed to write train name"); close(fd); return false; } @@ -133,34 +156,38 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& const size_t statusByteCount = sizeof(status); result = write(fd, (uint8_t*)&status, statusByteCount); if (result != statusByteCount) { - VLOG("Failed to write status for %s", file_name.c_str()); + VLOG("Failed to write status"); close(fd); return false; } - // Write experiment id size to file. - const size_t experimentIdSize = experimentIds.size(); - const size_t experimentIdsSizeByteCount = sizeof(experimentIdSize); - result = write(fd, (uint8_t*) &experimentIdSize, experimentIdsSizeByteCount); - if (result != experimentIdsSizeByteCount) { - VLOG("Failed to write experiment id size for %s", file_name.c_str()); + // Write experiment id count to file. + const size_t experimentIdsCount = experimentIds.size(); + const size_t experimentIdsCountByteCount = sizeof(experimentIdsCount); + result = write(fd, (uint8_t*) &experimentIdsCount, experimentIdsCountByteCount); + if (result != experimentIdsCountByteCount) { + VLOG("Failed to write experiment id count"); close(fd); return false; } // Write experimentIds to file - result = write(fd, experimentIds.data(), experimentIds.size()); - if (result == experimentIds.size()) { - VLOG("Successfully wrote %s", file_name.c_str()); - } else { - VLOG("Failed to write experiment ids for %s", file_name.c_str()); - close(fd); - return false; + for (size_t i = 0; i < experimentIdsCount; i++) { + const int64_t experimentId = experimentIds[i]; + const size_t experimentIdByteCount = sizeof(experimentId); + result = write(fd, &experimentId, experimentIdByteCount); + if (result == experimentIdByteCount) { + VLOG("Successfully wrote experiment IDs"); + } else { + VLOG("Failed to write experiment ids"); + close(fd); + return false; + } } result = fchown(fd, AID_STATSD, AID_STATSD); if (result) { - VLOG("Failed to chown %s to statsd", file_name.c_str()); + VLOG("Failed to chown train info file to statsd"); close(fd); return false; } @@ -172,88 +199,96 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) { std::lock_guard<std::mutex> lock(sTrainInfoMutex); - unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir); - - if (dir == NULL) { - VLOG("Directory does not exist: %s", TRAIN_INFO_DIR); + int fd = open(TRAIN_INFO_PATH, O_RDONLY | O_CLOEXEC); + if (fd == -1) { + VLOG("Failed to open train-info.bin"); return false; } - dirent* de; - while ((de = readdir(dir.get()))) { - char* name = de->d_name; - if (name[0] == '.') { - continue; - } - - size_t result; + // Read the magic word + uint32_t magic; + size_t result = read(fd, &magic, sizeof(magic)); + if (result != sizeof(magic)) { + VLOG("Failed to read train info magic"); + close(fd); + return false; + } - trainInfo.trainVersionCode = StrToInt64(name); - string fullPath = StringPrintf("%s/%s", TRAIN_INFO_DIR, name); - int fd = open(fullPath.c_str(), O_RDONLY | O_CLOEXEC); - if (fd == -1) { - return false; - } + if (magic != TRAIN_INFO_FILE_MAGIC) { + VLOG("Train info magic was 0x%08x, expected 0x%08x", magic, TRAIN_INFO_FILE_MAGIC); + close(fd); + return false; + } - // Read # of bytes taken by trainName in the file. - size_t trainNameSize; - result = read(fd, &trainNameSize, sizeof(size_t)); - if (result != sizeof(size_t)) { - VLOG("Failed to read train name size from file %s", fullPath.c_str()); - close(fd); - return false; - } + // Read the train version code + const size_t trainVersionCodeByteCount(sizeof(trainInfo.trainVersionCode)); + result = read(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount); + if (result != trainVersionCodeByteCount) { + VLOG("Failed to read train version code from train info file"); + close(fd); + return false; + } - // Read trainName - trainInfo.trainName.resize(trainNameSize); - result = read(fd, trainInfo.trainName.data(), trainNameSize); - if (result != trainNameSize) { - VLOG("Failed to read train name from file %s", fullPath.c_str()); - close(fd); - return false; - } + // Read # of bytes taken by trainName in the file. + size_t trainNameSize; + result = read(fd, &trainNameSize, sizeof(size_t)); + if (result != sizeof(size_t)) { + VLOG("Failed to read train name size from train info file"); + close(fd); + return false; + } - // Read status - const size_t statusByteCount = sizeof(trainInfo.status); - result = read(fd, &trainInfo.status, statusByteCount); - if (result != statusByteCount) { - VLOG("Failed to read train status from file %s", fullPath.c_str()); - close(fd); - return false; - } + // Read trainName + trainInfo.trainName.resize(trainNameSize); + result = read(fd, trainInfo.trainName.data(), trainNameSize); + if (result != trainNameSize) { + VLOG("Failed to read train name from train info file"); + close(fd); + return false; + } - // Read experiment ids size. - size_t experimentIdSize; - result = read(fd, &experimentIdSize, sizeof(size_t)); - if (result != sizeof(size_t)) { - VLOG("Failed to read train experiment id size from file %s", fullPath.c_str()); - close(fd); - return false; - } + // Read status + const size_t statusByteCount = sizeof(trainInfo.status); + result = read(fd, &trainInfo.status, statusByteCount); + if (result != statusByteCount) { + VLOG("Failed to read train status from train info file"); + close(fd); + return false; + } - // Read experimentIds - trainInfo.experimentIds.resize(experimentIdSize); - result = read(fd, trainInfo.experimentIds.data(), experimentIdSize); - if (result != experimentIdSize) { - VLOG("Failed to read train experiment ids from file %s", fullPath.c_str()); - close(fd); - return false; - } + // Read experiment ids count. + size_t experimentIdsCount; + result = read(fd, &experimentIdsCount, sizeof(size_t)); + if (result != sizeof(size_t)) { + VLOG("Failed to read train experiment id count from train info file"); + close(fd); + return false; + } - // Expect to be at EOF. - char c; - result = read(fd, &c, 1); - if (result != 0) { - VLOG("Failed to read train info from file %s. Did not get expected EOF.", fullPath.c_str()); + // Read experimentIds + for (size_t i = 0; i < experimentIdsCount; i++) { + int64_t experimentId; + result = read(fd, &experimentId, sizeof(experimentId)); + if (result != sizeof(experimentId)) { + VLOG("Failed to read train experiment id from train info file"); close(fd); return false; } + trainInfo.experimentIds.push_back(experimentId); + } - VLOG("Read train info file successful: %s", fullPath.c_str()); + // Expect to be at EOF. + char c; + result = read(fd, &c, 1); + if (result != 0) { + VLOG("Failed to read train info from file. Did not get expected EOF."); close(fd); - return true; + return false; } - return false; + + VLOG("Read train info file successful"); + close(fd); + return true; } void StorageManager::deleteFile(const char* file) { diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h index 88280cf218b3..dfcea65b0872 100644 --- a/cmds/statsd/src/storage/StorageManager.h +++ b/cmds/statsd/src/storage/StorageManager.h @@ -29,11 +29,6 @@ namespace statsd { using android::util::ProtoOutputStream; -struct TrainInfo { - int64_t trainVersionCode; - std::vector<uint8_t> experimentIds; -}; - class StorageManager : public virtual RefBase { public: /** @@ -45,7 +40,7 @@ public: * Writes train info. */ static bool writeTrainInfo(int64_t trainVersionCode, const std::string& trainName, - int32_t status, const std::vector<uint8_t>& experimentIds); + int32_t status, const std::vector<int64_t>& experimentIds); /** * Reads train info. diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp index b03517e607b3..504ee22f72e4 100644 --- a/cmds/statsd/tests/LogEvent_test.cpp +++ b/cmds/statsd/tests/LogEvent_test.cpp @@ -645,6 +645,22 @@ TEST(LogEventTest, TestBinaryFieldAtom_empty) { EXPECT_EQ(orig_str, result_str); } +TEST(LogEventTest, TestWriteExperimentIdsToProto) { + std::vector<int64_t> expIds; + expIds.push_back(5038); + std::vector<uint8_t> proto; + + writeExperimentIdsToProto(expIds, &proto); + + EXPECT_EQ(proto.size(), 3); + // Proto wire format for field ID 1, varint + EXPECT_EQ(proto[0], 0x08); + // varint of 5038, 2 bytes long + EXPECT_EQ(proto[1], 0xae); + EXPECT_EQ(proto[2], 0x27); +} + + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/tests/StatsService_test.cpp b/cmds/statsd/tests/StatsService_test.cpp index 560fb9f02174..7c00531d560b 100644 --- a/cmds/statsd/tests/StatsService_test.cpp +++ b/cmds/statsd/tests/StatsService_test.cpp @@ -33,7 +33,7 @@ using android::util::ProtoOutputStream; #ifdef __ANDROID__ TEST(StatsServiceTest, TestAddConfig_simple) { - StatsService service(nullptr); + StatsService service(nullptr, nullptr); StatsdConfig config; config.set_id(12345); string serialized = config.SerializeAsString(); @@ -43,7 +43,7 @@ TEST(StatsServiceTest, TestAddConfig_simple) { } TEST(StatsServiceTest, TestAddConfig_empty) { - StatsService service(nullptr); + StatsService service(nullptr, nullptr); string serialized = ""; EXPECT_TRUE( @@ -51,7 +51,7 @@ TEST(StatsServiceTest, TestAddConfig_empty) { } TEST(StatsServiceTest, TestAddConfig_invalid) { - StatsService service(nullptr); + StatsService service(nullptr, nullptr); string serialized = "Invalid config!"; EXPECT_FALSE( @@ -69,7 +69,7 @@ TEST(StatsServiceTest, TestGetUidFromArgs) { int32_t uid; - StatsService service(nullptr); + StatsService service(nullptr, nullptr); service.mEngBuild = true; // "-1" diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp index 3dff7f5d1320..309d251e4a89 100644 --- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp @@ -111,7 +111,7 @@ StatsdConfig MakeGaugeMetricConfig(int64_t minTime) { } TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit) { - StatsService service(nullptr); + StatsService service(nullptr, nullptr); SendConfig(service, MakeConfig()); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. @@ -126,7 +126,7 @@ TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit) { } TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp) { - StatsService service(nullptr); + StatsService service(nullptr, nullptr); SendConfig(service, MakeConfig()); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. @@ -146,7 +146,7 @@ TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp) { } TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade) { - StatsService service(nullptr); + StatsService service(nullptr, nullptr); SendConfig(service, MakeConfig()); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. @@ -171,7 +171,7 @@ TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade) { } TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval) { - StatsService service(nullptr); + StatsService service(nullptr, nullptr); SendConfig(service, MakeConfig()); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. @@ -195,7 +195,7 @@ TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval) { } TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) { - StatsService service(nullptr); + StatsService service(nullptr, nullptr); // Partial buckets don't occur when app is first installed. service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); SendConfig(service, MakeValueMetricConfig(0)); @@ -213,7 +213,7 @@ TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) { } TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) { - StatsService service(nullptr); + StatsService service(nullptr, nullptr); // Partial buckets don't occur when app is first installed. service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); SendConfig(service, MakeValueMetricConfig(60 * NS_PER_SEC /* One minute */)); @@ -237,7 +237,7 @@ TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) { } TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) { - StatsService service(nullptr); + StatsService service(nullptr, nullptr); // Partial buckets don't occur when app is first installed. service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); SendConfig(service, MakeGaugeMetricConfig(0)); @@ -255,7 +255,7 @@ TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) { } TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) { - StatsService service(nullptr); + StatsService service(nullptr, nullptr); // Partial buckets don't occur when app is first installed. service.mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); SendConfig(service, MakeGaugeMetricConfig(60 * NS_PER_SEC /* One minute */)); diff --git a/cmds/statsd/tests/log_event/LogEventQueue_test.cpp b/cmds/statsd/tests/log_event/LogEventQueue_test.cpp new file mode 100644 index 000000000000..f27d12957f11 --- /dev/null +++ b/cmds/statsd/tests/log_event/LogEventQueue_test.cpp @@ -0,0 +1,100 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "logd/LogEventQueue.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <thread> + +#include <stdio.h> + +namespace android { +namespace os { +namespace statsd { + +using namespace android; +using namespace testing; + +using std::unique_ptr; + +#ifdef __ANDROID__ +TEST(LogEventQueue_test, TestGoodConsumer) { + LogEventQueue queue(50); + int64_t timeBaseNs = 100; + std::thread writer([&queue, timeBaseNs] { + for (int i = 0; i < 100; i++) { + int64_t oldestEventNs; + bool success = queue.push(std::make_unique<LogEvent>(10, timeBaseNs + i * 1000), + &oldestEventNs); + EXPECT_TRUE(success); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + }); + + std::thread reader([&queue, timeBaseNs] { + for (int i = 0; i < 100; i++) { + auto event = queue.waitPop(); + EXPECT_TRUE(event != nullptr); + // All events are in right order. + EXPECT_EQ(timeBaseNs + i * 1000, event->GetElapsedTimestampNs()); + } + }); + + reader.join(); + writer.join(); +} + +TEST(LogEventQueue_test, TestSlowConsumer) { + LogEventQueue queue(50); + int64_t timeBaseNs = 100; + std::thread writer([&queue, timeBaseNs] { + int failure_count = 0; + int64_t oldestEventNs; + for (int i = 0; i < 100; i++) { + bool success = queue.push(std::make_unique<LogEvent>(10, timeBaseNs + i * 1000), + &oldestEventNs); + if (!success) failure_count++; + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + // There is some remote chance that reader thread not get chance to run before writer thread + // ends. That's why the following comparison is not "==". + // There will be at least 45 events lost due to overflow. + EXPECT_TRUE(failure_count >= 45); + // The oldest event must be at least the 6th event. + EXPECT_TRUE(oldestEventNs <= (100 + 5 * 1000)); + }); + + std::thread reader([&queue, timeBaseNs] { + // The consumer quickly processed 5 events, then it got stuck (not reading anymore). + for (int i = 0; i < 5; i++) { + auto event = queue.waitPop(); + EXPECT_TRUE(event != nullptr); + // All events are in right order. + EXPECT_EQ(timeBaseNs + i * 1000, event->GetElapsedTimestampNs()); + } + }); + + reader.join(); + writer.join(); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 023371d3b443..80b6349ca284 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4037,7 +4037,7 @@ public class ActivityManager { * continues running even if the process is killed and restarted. To remove the watch, * use {@link #clearWatchHeapLimit()}. * - * <p>This API only work if the calling process has been marked as + * <p>This API only works if the calling process has been marked as * {@link ApplicationInfo#FLAG_DEBUGGABLE} or this is running on a debuggable * (userdebug or eng) build.</p> * diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 08239a1e7ed9..2a603d270993 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2100,6 +2100,16 @@ public final class ActivityThread extends ClientTransactionHandler { public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, int flags, int userId) { final boolean differentUser = (UserHandle.myUserId() != userId); + ApplicationInfo ai; + try { + ai = getPackageManager().getApplicationInfo(packageName, + PackageManager.GET_SHARED_LIBRARY_FILES + | PackageManager.MATCH_DEBUG_TRIAGED_MISSING, + (userId < 0) ? UserHandle.myUserId() : userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + synchronized (mResourcesManager) { WeakReference<LoadedApk> ref; if (differentUser) { @@ -2112,11 +2122,7 @@ public final class ActivityThread extends ClientTransactionHandler { } LoadedApk packageInfo = ref != null ? ref.get() : null; - //Slog.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo); - //if (packageInfo != null) Slog.i(TAG, "isUptoDate " + packageInfo.mResDir - // + ": " + packageInfo.mResources.getAssets().isUpToDate()); - if (packageInfo != null && (packageInfo.mResources == null - || packageInfo.mResources.getAssets().isUpToDate())) { + if (ai != null && packageInfo != null && isLoadedApkUpToDate(packageInfo, ai)) { if (packageInfo.isSecurityViolation() && (flags&Context.CONTEXT_IGNORE_SECURITY) == 0) { throw new SecurityException( @@ -2129,16 +2135,6 @@ public final class ActivityThread extends ClientTransactionHandler { } } - ApplicationInfo ai = null; - try { - ai = getPackageManager().getApplicationInfo(packageName, - PackageManager.GET_SHARED_LIBRARY_FILES - | PackageManager.MATCH_DEBUG_TRIAGED_MISSING, - (userId < 0) ? UserHandle.myUserId() : userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - if (ai != null) { return getPackageInfo(ai, compatInfo, flags); } @@ -2209,37 +2205,59 @@ public final class ActivityThread extends ClientTransactionHandler { } LoadedApk packageInfo = ref != null ? ref.get() : null; - if (packageInfo == null || (packageInfo.mResources != null - && !packageInfo.mResources.getAssets().isUpToDate())) { - if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package " + + boolean isUpToDate = packageInfo != null && isLoadedApkUpToDate(packageInfo, aInfo); + + if (isUpToDate) { + return packageInfo; + } + + if (localLOGV) { + Slog.v(TAG, (includeCode ? "Loading code package " : "Loading resource-only package ") + aInfo.packageName + " (in " + (mBoundApplication != null - ? mBoundApplication.processName : null) + ? mBoundApplication.processName : null) + ")"); - packageInfo = + } + + packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader, - securityViolation, includeCode && - (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage); + securityViolation, includeCode + && (aInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage); - if (mSystemThread && "android".equals(aInfo.packageName)) { - packageInfo.installSystemApplicationInfo(aInfo, - getSystemContext().mPackageInfo.getClassLoader()); - } + if (mSystemThread && "android".equals(aInfo.packageName)) { + packageInfo.installSystemApplicationInfo(aInfo, + getSystemContext().mPackageInfo.getClassLoader()); + } - if (differentUser) { - // Caching not supported across users - } else if (includeCode) { - mPackages.put(aInfo.packageName, - new WeakReference<LoadedApk>(packageInfo)); - } else { - mResourcePackages.put(aInfo.packageName, - new WeakReference<LoadedApk>(packageInfo)); - } + if (differentUser) { + // Caching not supported across users + } else if (includeCode) { + mPackages.put(aInfo.packageName, + new WeakReference<LoadedApk>(packageInfo)); + } else { + mResourcePackages.put(aInfo.packageName, + new WeakReference<LoadedApk>(packageInfo)); } + return packageInfo; } } + /** + * Compares overlay/resource directories for a LoadedApk to determine if it's up to date + * with the given ApplicationInfo. + */ + private boolean isLoadedApkUpToDate(LoadedApk loadedApk, ApplicationInfo appInfo) { + Resources packageResources = loadedApk.mResources; + String[] overlayDirs = ArrayUtils.defeatNullable(loadedApk.getOverlayDirs()); + String[] resourceDirs = ArrayUtils.defeatNullable(appInfo.resourceDirs); + + return (packageResources == null || packageResources.getAssets().isUpToDate()) + && overlayDirs.length == resourceDirs.length + && ArrayUtils.containsAll(overlayDirs, resourceDirs); + } + @UnsupportedAppUsage ActivityThread() { mResourcesManager = ResourcesManager.getInstance(); @@ -5434,19 +5452,25 @@ public final class ActivityThread extends ClientTransactionHandler { ref = mResourcePackages.get(ai.packageName); resApk = ref != null ? ref.get() : null; } + + final String[] oldResDirs = new String[2]; + if (apk != null) { + oldResDirs[0] = apk.getResDir(); final ArrayList<String> oldPaths = new ArrayList<>(); LoadedApk.makePaths(this, apk.getApplicationInfo(), oldPaths); apk.updateApplicationInfo(ai, oldPaths); } if (resApk != null) { + oldResDirs[1] = resApk.getResDir(); final ArrayList<String> oldPaths = new ArrayList<>(); LoadedApk.makePaths(this, resApk.getApplicationInfo(), oldPaths); resApk.updateApplicationInfo(ai, oldPaths); } + synchronized (mResourcesManager) { // Update all affected Resources objects to use new ResourcesImpl - mResourcesManager.applyNewResourceDirsLocked(ai.sourceDir, ai.resourceDirs); + mResourcesManager.applyNewResourceDirsLocked(ai, oldResDirs); } ApplicationPackageManager.configurationChanged(); @@ -5699,9 +5723,17 @@ public final class ActivityThread extends ClientTransactionHandler { } } } + + final String[] oldResDirs = { pkgInfo.getResDir() }; + final ArrayList<String> oldPaths = new ArrayList<>(); LoadedApk.makePaths(this, pkgInfo.getApplicationInfo(), oldPaths); pkgInfo.updateApplicationInfo(aInfo, oldPaths); + + synchronized (mResourcesManager) { + // Update affected Resources objects to use new ResourcesImpl + mResourcesManager.applyNewResourceDirsLocked(aInfo, oldResDirs); + } } catch (RemoteException e) { } } @@ -6905,9 +6937,6 @@ public final class ActivityThread extends ClientTransactionHandler { // If feature is disabled, we don't need to install if (!DEPRECATE_DATA_COLUMNS) return; - // If app is modern enough, we don't need to install - if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q) return; - // Install interception and make sure it sticks! Os def = null; do { diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index 2ef085690f8f..8ec5e3a43218 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -27,6 +27,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.Insets; +import android.graphics.Region; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.hardware.input.InputManager; @@ -46,6 +47,7 @@ import android.view.SurfaceSession; import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; import android.view.WindowManager; import android.view.WindowManagerGlobal; @@ -81,6 +83,9 @@ public class ActivityView extends ViewGroup { // Temp container to store view coordinates in window. private final int[] mLocationInWindow = new int[2]; + // The latest tap exclude region that we've sent to WM. + private final Region mTapExcludeRegion = new Region(); + private TaskStackListener mTaskStackListener; private final CloseGuard mGuard = CloseGuard.get(); @@ -279,11 +284,11 @@ public class ActivityView extends ViewGroup { } /** - * Triggers an update of {@link ActivityView}'s location in window to properly set touch exclude + * Triggers an update of {@link ActivityView}'s location in window to properly set tap exclude * regions and avoid focus switches by touches on this view. */ public void onLocationChanged() { - updateLocation(); + updateTapExcludeRegion(); } @Override @@ -291,15 +296,38 @@ public class ActivityView extends ViewGroup { mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */); } - /** Send current location and size to the WM to set tap exclude region for this view. */ - private void updateLocation() { + @Override + public boolean gatherTransparentRegion(Region region) { + // The tap exclude region may be affected by any view on top of it, so we detect the + // possible change by monitoring this function. + updateTapExcludeRegion(); + return super.gatherTransparentRegion(region); + } + + /** Compute and send current tap exclude region to WM for this view. */ + private void updateTapExcludeRegion() { if (!isAttachedToWindow()) { return; } + if (!canReceivePointerEvents()) { + cleanTapExcludeRegion(); + return; + } try { getLocationInWindow(mLocationInWindow); + final int x = mLocationInWindow[0]; + final int y = mLocationInWindow[1]; + mTapExcludeRegion.set(x, y, x + getWidth(), y + getHeight()); + + // There might be views on top of us. We need to subtract those areas from the tap + // exclude region. + final ViewParent parent = getParent(); + if (parent instanceof ViewGroup) { + ((ViewGroup) parent).subtractObscuredTouchableRegion(mTapExcludeRegion, this); + } + WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(), - mLocationInWindow[0], mLocationInWindow[1], getWidth(), getHeight()); + mTapExcludeRegion); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } @@ -322,7 +350,7 @@ public class ActivityView extends ViewGroup { mVirtualDisplay.setDisplayState(true); } - updateLocation(); + updateTapExcludeRegion(); } @Override @@ -330,7 +358,7 @@ public class ActivityView extends ViewGroup { if (mVirtualDisplay != null) { mVirtualDisplay.resize(width, height, getBaseDisplayDensity()); } - updateLocation(); + updateTapExcludeRegion(); } @Override @@ -460,13 +488,14 @@ public class ActivityView extends ViewGroup { /** Report to server that tap exclude region on hosting display should be cleared. */ private void cleanTapExcludeRegion() { - if (!isAttachedToWindow()) { + if (!isAttachedToWindow() || mTapExcludeRegion.isEmpty()) { return; } - // Update tap exclude region with an empty rect to clean the state on server. + // Update tap exclude region with a null region to clean the state on server. try { WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(), - 0 /* left */, 0 /* top */, 0 /* width */, 0 /* height */); + null /* region */); + mTapExcludeRegion.setEmpty(); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 404e52011c39..a906790c45ab 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -3049,6 +3049,15 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public String getAttentionServicePackageName() { + try { + return mPM.getAttentionServicePackageName(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override public String getWellbeingPackageName() { try { return mPM.getWellbeingPackageName(); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 11000df5b993..41a4fba0434c 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2122,8 +2122,7 @@ class ContextImpl extends Context { } private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName, - int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo, - String[] overlayDirs) { + int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) { final String[] splitResDirs; final ClassLoader classLoader; try { @@ -2135,7 +2134,7 @@ class ContextImpl extends Context { return ResourcesManager.getInstance().getResources(activityToken, pi.getResDir(), splitResDirs, - overlayDirs, + pi.getOverlayDirs(), pi.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfig, @@ -2153,11 +2152,9 @@ class ContextImpl extends Context { new UserHandle(UserHandle.getUserId(application.uid)), flags, null, null); final int displayId = getDisplayId(); - // overlayDirs is retrieved directly from ApplicationInfo since ActivityThread may have - // a LoadedApk containing Resources with stale overlays for a remote application. - final String[] overlayDirs = application.resourceDirs; + c.setResources(createResources(mActivityToken, pi, null, displayId, null, - getDisplayAdjustments(displayId).getCompatibilityInfo(), overlayDirs)); + getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; } @@ -2192,7 +2189,7 @@ class ContextImpl extends Context { final int displayId = getDisplayId(); c.setResources(createResources(mActivityToken, pi, null, displayId, null, - getDisplayAdjustments(displayId).getCompatibilityInfo(), pi.getOverlayDirs())); + getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; } @@ -2242,8 +2239,7 @@ class ContextImpl extends Context { final int displayId = getDisplayId(); context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, - overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(), - mPackageInfo.getOverlayDirs())); + overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo())); return context; } @@ -2258,8 +2254,7 @@ class ContextImpl extends Context { final int displayId = display.getDisplayId(); context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, - null, getDisplayAdjustments(displayId).getCompatibilityInfo(), - mPackageInfo.getOverlayDirs())); + null, getDisplayAdjustments(displayId).getCompatibilityInfo())); context.mDisplay = display; return context; } @@ -2441,7 +2436,7 @@ class ContextImpl extends Context { ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, null, null, null, 0, null, null); context.setResources(createResources(null, packageInfo, null, displayId, null, - packageInfo.getCompatibilityInfo(), packageInfo.getOverlayDirs())); + packageInfo.getCompatibilityInfo())); context.updateDisplay(displayId); return context; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 5549d6b80ea6..11fa343df0d3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -617,6 +617,13 @@ public class Notification implements Parcelable */ public static final int FLAG_CAN_COLORIZE = 0x00000800; + /** + * Bit to be bitswised-ored into the {@link #flags} field that should be + * set if this notification can be shown as a bubble. + * @hide + */ + public static final int FLAG_BUBBLE = 0x00001000; + public int flags; /** @hide */ @@ -6243,6 +6250,15 @@ public class Notification implements Parcelable return false; } + /** + * @return true if this is a notification that can show as a bubble. + * + * @hide + */ + public boolean isBubbleNotification() { + return (flags & Notification.FLAG_BUBBLE) != 0; + } + private boolean hasLargeIcon() { return mLargeIcon != null || largeIcon != null; } diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 5cdf85a6ca13..69ec831b5d1d 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -170,6 +170,7 @@ public final class NotificationChannel implements Parcelable { private boolean mBlockableSystem = false; private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE; private boolean mImportanceLockedByOEM; + private boolean mImportanceLockedDefaultApp; /** * Creates a notification channel. @@ -656,11 +657,27 @@ public final class NotificationChannel implements Parcelable { * @hide */ @TestApi + public void setImportanceLockedByCriticalDeviceFunction(boolean locked) { + mImportanceLockedDefaultApp = locked; + } + + /** + * @hide + */ + @TestApi public boolean isImportanceLockedByOEM() { return mImportanceLockedByOEM; } /** + * @hide + */ + @TestApi + public boolean isImportanceLockedByCriticalDeviceFunction() { + return mImportanceLockedDefaultApp; + } + + /** * Returns whether the user has chosen the importance of this channel, either to affirm the * initial selection from the app, or changed it to be higher or lower. * @see #getImportance() @@ -834,6 +851,9 @@ public final class NotificationChannel implements Parcelable { out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(canBubble())); } + // mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of + // truth and so aren't written to this xml file + out.endTag(null, TAG_CHANNEL); } @@ -942,7 +962,8 @@ public final class NotificationChannel implements Parcelable { return sb.toString(); } - public static final @android.annotation.NonNull Creator<NotificationChannel> CREATOR = new Creator<NotificationChannel>() { + public static final @android.annotation.NonNull Creator<NotificationChannel> CREATOR = + new Creator<NotificationChannel>() { @Override public NotificationChannel createFromParcel(Parcel in) { return new NotificationChannel(in); @@ -983,7 +1004,8 @@ public final class NotificationChannel implements Parcelable { && Arrays.equals(mVibration, that.mVibration) && Objects.equals(getGroup(), that.getGroup()) && Objects.equals(getAudioAttributes(), that.getAudioAttributes()) - && mImportanceLockedByOEM == that.mImportanceLockedByOEM; + && mImportanceLockedByOEM == that.mImportanceLockedByOEM + && mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp; } @Override @@ -993,7 +1015,7 @@ public final class NotificationChannel implements Parcelable { getUserLockedFields(), isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(), getAudioAttributes(), isBlockableSystem(), mAllowBubbles, - mImportanceLockedByOEM); + mImportanceLockedByOEM, mImportanceLockedDefaultApp); result = 31 * result + Arrays.hashCode(mVibration); return result; } @@ -1022,6 +1044,7 @@ public final class NotificationChannel implements Parcelable { + ", mBlockableSystem=" + mBlockableSystem + ", mAllowBubbles=" + mAllowBubbles + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM + + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp + '}'; pw.println(prefix + output); } @@ -1049,6 +1072,7 @@ public final class NotificationChannel implements Parcelable { + ", mBlockableSystem=" + mBlockableSystem + ", mAllowBubbles=" + mAllowBubbles + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM + + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp + '}'; } diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 35658fbcb989..b93aaa2aca68 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.CompatResources; @@ -32,6 +33,7 @@ import android.content.res.ResourcesImpl; import android.content.res.ResourcesKey; import android.hardware.display.DisplayManagerGlobal; import android.os.IBinder; +import android.os.Process; import android.os.Trace; import android.util.ArrayMap; import android.util.DisplayMetrics; @@ -1136,27 +1138,46 @@ public class ResourcesManager { } // TODO(adamlesinski): Make this accept more than just overlay directories. - final void applyNewResourceDirsLocked(@NonNull final String baseCodePath, - @Nullable final String[] newResourceDirs) { + final void applyNewResourceDirsLocked(@NonNull final ApplicationInfo appInfo, + @Nullable final String[] oldPaths) { try { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#applyNewResourceDirsLocked"); + String baseCodePath = appInfo.getBaseCodePath(); + + final int myUid = Process.myUid(); + String[] newSplitDirs = appInfo.uid == myUid + ? appInfo.splitSourceDirs + : appInfo.splitPublicSourceDirs; + + // ApplicationInfo is mutable, so clone the arrays to prevent outside modification + String[] copiedSplitDirs = ArrayUtils.cloneOrNull(newSplitDirs); + String[] copiedResourceDirs = ArrayUtils.cloneOrNull(appInfo.resourceDirs); + final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); final int implCount = mResourceImpls.size(); for (int i = 0; i < implCount; i++) { final ResourcesKey key = mResourceImpls.keyAt(i); final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; - if (impl != null && (key.mResDir == null || key.mResDir.equals(baseCodePath))) { + + if (impl == null) { + continue; + } + + if (key.mResDir == null + || key.mResDir.equals(baseCodePath) + || ArrayUtils.contains(oldPaths, key.mResDir)) { updatedResourceKeys.put(impl, new ResourcesKey( - key.mResDir, - key.mSplitResDirs, - newResourceDirs, + baseCodePath, + copiedSplitDirs, + copiedResourceDirs, key.mLibDirs, key.mDisplayId, key.mOverrideConfiguration, - key.mCompatInfo)); + key.mCompatInfo + )); } } diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java index 7746148d325a..2e14d03a912c 100644 --- a/core/java/android/app/StatsManager.java +++ b/core/java/android/app/StatsManager.java @@ -18,6 +18,7 @@ package android.app; import static android.Manifest.permission.DUMP; import static android.Manifest.permission.PACKAGE_USAGE_STATS; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; @@ -295,7 +296,7 @@ public final class StatsManager { * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service */ @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) - public long[] setActiveConfigsChangedOperation(@Nullable PendingIntent pendingIntent) + public @NonNull long[] setActiveConfigsChangedOperation(@Nullable PendingIntent pendingIntent) throws StatsUnavailableException { synchronized (this) { try { @@ -410,6 +411,36 @@ public final class StatsManager { } /** + * Returns the experiments IDs registered with statsd, or an empty array if there aren't any. + * + * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service + * @hide + */ + @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS}) + public long[] getRegisteredExperimentIds() + throws StatsUnavailableException { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + if (DEBUG) { + Slog.d(TAG, "Failed to find statsd when getting experiment IDs"); + } + return new long[0]; + } + return service.getRegisteredExperimentIds(); + } catch (RemoteException e) { + if (DEBUG) { + Slog.d(TAG, + "Failed to connect to StatsCompanionService when getting " + + "registered experiment IDs"); + } + return new long[0]; + } + } + } + + /** * Registers a callback for an atom when that atom is to be pulled. The stats service will * invoke pullData in the callback when the stats service determines that this atom needs to be * pulled. Currently, this only works for atoms with tags above 100,000 that do not have a uid. diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 4b0c05f323cd..8a522656a13a 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -54,7 +54,6 @@ import android.net.NetworkUtils; import android.net.PrivateDnsConnectivityChecker; import android.net.ProxyInfo; import android.net.Uri; -import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.ParcelFileDescriptor; @@ -5440,13 +5439,14 @@ public class DevicePolicyManager { } /** - * Called by a device or profile owner to set whether auto time is required. If auto time is - * required, no user will be able set the date and time and network date and time will be used. + * Called by a device owner, or alternatively a profile owner from Android 8.0 (API level 26) or + * higher, to set whether auto time is required. If auto time is required, no user will be able + * set the date and time and network date and time will be used. * <p> * Note: if auto time is required the user can still manually set the time zone. * <p> - * The calling device admin must be a device or profile owner. If it is not, a security - * exception will be thrown. + * The calling device admin must be a device owner, or alternatively a profile owner from + * Android 8.0 (API level 26) or higher. If it is not, a security exception will be thrown. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param required Whether auto time is set required or not. @@ -6409,27 +6409,20 @@ public class DevicePolicyManager { * Returns whether the specified package can read the device identifiers. * * @param packageName The package name of the app to check for device identifier access. + * @param pid The process id of the package to be checked. + * @param uid The uid of the package to be checked. * @return whether the package can read the device identifiers. * * @hide */ - public boolean checkDeviceIdentifierAccess(String packageName) { - return checkDeviceIdentifierAccessAsUser(packageName, myUserId()); - } - - /** - * @hide - */ - @RequiresPermission(value = android.Manifest.permission.MANAGE_USERS, conditional = true) - public boolean checkDeviceIdentifierAccessAsUser(String packageName, int userId) { - throwIfParentInstance("checkDeviceIdentifierAccessAsUser"); + public boolean checkDeviceIdentifierAccess(String packageName, int pid, int uid) { + throwIfParentInstance("checkDeviceIdentifierAccess"); if (packageName == null) { return false; } if (mService != null) { try { - return mService.checkDeviceIdentifierAccess(packageName, userId, - Binder.getCallingPid(), Binder.getCallingUid()); + return mService.checkDeviceIdentifierAccess(packageName, pid, uid); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -10789,8 +10782,8 @@ public class DevicePolicyManager { } /** - * Returns whether the device is being used as a managed kiosk, as defined in the CDD. As of - * this release, these requirements are as follows: + * Returns whether the device is being used as a managed kiosk. These requirements are as + * follows: * <ul> * <li>The device is in Lock Task (therefore there is also a Device Owner app on the * device)</li> @@ -10829,11 +10822,11 @@ public class DevicePolicyManager { } /** - * Returns whether the device is being used as an unattended managed kiosk, as defined in the - * CDD. As of this release, these requirements are as follows: + * Returns whether the device is being used as an unattended managed kiosk. These requirements + * are as follows: * <ul> - * <li>The device is being used as a managed kiosk, as defined in the CDD and verified at - * {@link #isManagedKiosk()}</li> + * <li>The device is being used as a managed kiosk, as defined at {@link + * #isManagedKiosk()}</li> * <li>The device has not received user input for at least 30 minutes</li> * </ul> * diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 3c389e4aa38c..2b9641999019 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -156,7 +156,7 @@ interface IDevicePolicyManager { void clearProfileOwner(in ComponentName who); boolean hasUserSetupCompleted(); - boolean checkDeviceIdentifierAccess(in String packageName, int userHandle, int pid, int uid); + boolean checkDeviceIdentifierAccess(in String packageName, int pid, int uid); void setDeviceOwnerLockScreenInfo(in ComponentName who, CharSequence deviceOwnerInfo); CharSequence getDeviceOwnerLockScreenInfo(); diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index fa2c9f8a52ae..204d7e3ceca6 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -535,7 +535,6 @@ public final class BluetoothDevice implements Parcelable { /** * Intent to broadcast silence mode changed. * Alway contains the extra field {@link #EXTRA_DEVICE} - * Alway contains the extra field {@link #EXTRA_SILENCE_ENABLED} * * @hide */ @@ -545,16 +544,6 @@ public final class BluetoothDevice implements Parcelable { "android.bluetooth.device.action.SILENCE_MODE_CHANGED"; /** - * Used as an extra field in {@link #ACTION_SILENCE_MODE_CHANGED} intent, - * contains whether device is in silence mode as boolean. - * - * @hide - */ - @SystemApi - public static final String EXTRA_SILENCE_ENABLED = - "android.bluetooth.device.extra.SILENCE_ENABLED"; - - /** * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intent. * * @hide @@ -1615,7 +1604,8 @@ public final class BluetoothDevice implements Parcelable { } /** - * Set the Bluetooth device silence mode. + * Sets whether the {@link BluetoothDevice} enters silence mode. Audio will not + * be routed to the {@link BluetoothDevice} if set to {@code true}. * * When the {@link BluetoothDevice} enters silence mode, and the {@link BluetoothDevice} * is an active device (for A2DP or HFP), the active device for that profile @@ -1635,6 +1625,7 @@ public final class BluetoothDevice implements Parcelable { * * @param silence true to enter silence mode, false to exit * @return true on success, false on error. + * @throws IllegalStateException if Bluetooth is not turned ON. * @hide */ @SystemApi @@ -1642,12 +1633,9 @@ public final class BluetoothDevice implements Parcelable { public boolean setSilenceMode(boolean silence) { final IBluetooth service = sService; if (service == null) { - return false; + throw new IllegalStateException("Bluetooth is not turned ON"); } try { - if (getSilenceMode() == silence) { - return true; - } return service.setSilenceMode(this, silence); } catch (RemoteException e) { Log.e(TAG, "setSilenceMode fail", e); @@ -1656,24 +1644,25 @@ public final class BluetoothDevice implements Parcelable { } /** - * Get the device silence mode status + * Check whether the {@link BluetoothDevice} is in silence mode * * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. * * @return true on device in silence mode, otherwise false. + * @throws IllegalStateException if Bluetooth is not turned ON. * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean getSilenceMode() { + public boolean isInSilenceMode() { final IBluetooth service = sService; if (service == null) { - return false; + throw new IllegalStateException("Bluetooth is not turned ON"); } try { return service.getSilenceMode(this); } catch (RemoteException e) { - Log.e(TAG, "getSilenceMode fail", e); + Log.e(TAG, "isInSilenceMode fail", e); return false; } } diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java index 1727d341bd82..76c4fb8caa0b 100644 --- a/core/java/android/content/ContentCaptureOptions.java +++ b/core/java/android/content/ContentCaptureOptions.java @@ -136,13 +136,18 @@ public final class ContentCaptureOptions implements Parcelable { @Override public String toString() { if (lite) { - return "ContentCaptureOptions [(lite) loggingLevel=" + loggingLevel + "]"; + return "ContentCaptureOptions [loggingLevel=" + loggingLevel + " (lite)]"; } - return "ContentCaptureOptions [loggingLevel=" + loggingLevel + ", maxBufferSize=" - + maxBufferSize + ", idleFlushingFrequencyMs=" + idleFlushingFrequencyMs - + ", textChangeFlushingFrequencyMs=" + textChangeFlushingFrequencyMs - + ", logHistorySize=" + logHistorySize + ", whitelistedComponents=" - + whitelistedComponents + "]"; + final StringBuilder string = new StringBuilder("ContentCaptureOptions ["); + string.append("loggingLevel=").append(loggingLevel) + .append(", maxBufferSize=").append(maxBufferSize) + .append(", idleFlushingFrequencyMs=").append(idleFlushingFrequencyMs) + .append(", textChangeFlushingFrequencyMs=").append(textChangeFlushingFrequencyMs) + .append(", logHistorySize=").append(logHistorySize); + if (whitelistedComponents != null) { + string.append(", whitelisted=").append(whitelistedComponents); + } + return string.append(']').toString(); } /** @hide */ diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index fa85f0ae1670..791c55196ecf 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -1313,6 +1313,12 @@ public abstract class ContentResolver implements ContentInterface { public final @Nullable ParcelFileDescriptor openFileDescriptor(@NonNull Uri uri, @NonNull String mode, @Nullable CancellationSignal cancellationSignal) throws FileNotFoundException { + try { + if (mWrapped != null) return mWrapped.openFile(uri, mode, cancellationSignal); + } catch (RemoteException e) { + return null; + } + AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode, cancellationSignal); if (afd == null) { return null; @@ -1455,6 +1461,12 @@ public abstract class ContentResolver implements ContentInterface { Preconditions.checkNotNull(uri, "uri"); Preconditions.checkNotNull(mode, "mode"); + try { + if (mWrapped != null) return mWrapped.openAssetFile(uri, mode, cancellationSignal); + } catch (RemoteException e) { + return null; + } + String scheme = uri.getScheme(); if (SCHEME_ANDROID_RESOURCE.equals(scheme)) { if (!"r".equals(mode)) { @@ -1634,6 +1646,12 @@ public abstract class ContentResolver implements ContentInterface { Preconditions.checkNotNull(uri, "uri"); Preconditions.checkNotNull(mimeType, "mimeType"); + try { + if (mWrapped != null) return mWrapped.openTypedAssetFile(uri, mimeType, opts, cancellationSignal); + } catch (RemoteException e) { + return null; + } + IContentProvider unstableProvider = acquireUnstableProvider(uri); if (unstableProvider == null) { throw new FileNotFoundException("No content provider: " + uri); @@ -3525,12 +3543,7 @@ public abstract class ContentResolver implements ContentInterface { */ public @NonNull Bitmap loadThumbnail(@NonNull Uri uri, @NonNull Size size, @Nullable CancellationSignal signal) throws IOException { - Objects.requireNonNull(uri); - Objects.requireNonNull(size); - - try (ContentProviderClient client = acquireContentProviderClient(uri)) { - return loadThumbnail(client, uri, size, signal, ImageDecoder.ALLOCATOR_SOFTWARE); - } + return loadThumbnail(this, uri, size, signal, ImageDecoder.ALLOCATOR_SOFTWARE); } /** {@hide} */ diff --git a/core/java/android/content/LocusId.java b/core/java/android/content/LocusId.java index c67ff7caaf64..283cea00b192 100644 --- a/core/java/android/content/LocusId.java +++ b/core/java/android/content/LocusId.java @@ -18,19 +18,50 @@ package android.content; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import android.view.contentcapture.ContentCaptureManager; import com.android.internal.util.Preconditions; import java.io.PrintWriter; /** - * Identifier for an unique state in the application. + * An identifier for an unique state (locus) in the application. Should be stable across reboots and + * backup / restore. * - * <p>Should be stable across reboots and backup / restore. + * <p>Locus is a new concept introduced on + * {@link android.os.Build.VERSION_CODES#Q Android Q} and it lets the intelligence service provided + * by the Android System to correlate state between different subsystems such as content capture, + * shortcuts, and notifications. * - * <p>For example, a chat app could use the context to resume a conversation between 2 users. + * <p>For example, if your app provides an activiy representing a chat between 2 users + * (say {@code A} and {@code B}, this chat state could be represented by: + * + * <pre><code> + * LocusId chatId = new LocusId("Chat_A_B"); + * </code></pre> + * + * <p>And then you should use that {@code chatId} by: + * + * <ul> + * <li>Setting it in the chat notification (through + * {@link android.app.Notification.Builder#setLocusId(LocusId) + * Notification.Builder.setLocusId(chatId)}). + * <li>Setting it into the {@link android.content.pm.ShortcutInfo} (through + * {@link android.content.pm.ShortcutInfo.Builder#setLocusId(LocusId) + * ShortcutInfo.Builder.setLocusId(chatId)}), if you provide a launcher shortcut for that chat + * conversation. + * <li>Associating it with the {@link android.view.contentcapture.ContentCaptureContext} of the + * root view of the chat conversation activity (through + * {@link android.view.View#getContentCaptureSession()}, then + * {@link android.view.contentcapture.ContentCaptureContext.Builder + * new ContentCaptureContext.Builder(chatId).build()} and + * {@link android.view.contentcapture.ContentCaptureSession#setContentCaptureContext( + * android.view.contentcapture.ContentCaptureContext)} - see {@link ContentCaptureManager} + * for more info about content capture). + * <li>Configuring your app to launch the chat conversation through the + * {@link Intent#ACTION_VIEW_LOCUS} intent. + * </ul> */ -// TODO(b/123577059): make sure this is well documented and understandable public final class LocusId implements Parcelable { private final String mId; @@ -45,7 +76,7 @@ public final class LocusId implements Parcelable { } /** - * Gets the {@code id} associated with the locus. + * Gets the canonical {@code id} associated with the locus. */ @NonNull public String getId() { @@ -100,7 +131,7 @@ public final class LocusId implements Parcelable { parcel.writeString(mId); } - public static final @android.annotation.NonNull Parcelable.Creator<LocusId> CREATOR = + public static final @NonNull Parcelable.Creator<LocusId> CREATOR = new Parcelable.Creator<LocusId>() { @NonNull diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java index aabe59d18383..597c08392d04 100644 --- a/core/java/android/content/om/OverlayInfo.java +++ b/core/java/android/content/om/OverlayInfo.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.UserIdInt; import android.os.Parcel; import android.os.Parcelable; @@ -44,7 +45,7 @@ public final class OverlayInfo implements Parcelable { STATE_DISABLED, STATE_ENABLED, STATE_ENABLED_STATIC, - STATE_TARGET_UPGRADING, + // @Deprecated STATE_TARGET_UPGRADING, STATE_OVERLAY_UPGRADING, }) /** @hide */ @@ -96,7 +97,14 @@ public final class OverlayInfo implements Parcelable { * The target package is currently being upgraded; the state will change * once the package installation has finished. * @hide + * + * @deprecated No longer used. Caused invalid transitions from enabled -> upgrading -> enabled, + * where an update is propagated when nothing has changed. Can occur during --dont-kill + * installs when code and resources are hot swapped and the Activity should not be relaunched. + * In all other cases, the process and therefore Activity is killed, so the state loop is + * irrelevant. */ + @Deprecated public static final int STATE_TARGET_UPGRADING = 4; /** @@ -128,7 +136,6 @@ public final class OverlayInfo implements Parcelable { * * @hide */ - @SystemApi public final String packageName; /** @@ -136,7 +143,6 @@ public final class OverlayInfo implements Parcelable { * * @hide */ - @SystemApi public final String targetPackageName; /** @@ -144,7 +150,6 @@ public final class OverlayInfo implements Parcelable { * * @hide */ - @SystemApi public final String targetOverlayableName; /** @@ -152,7 +157,6 @@ public final class OverlayInfo implements Parcelable { * * @hide */ - @SystemApi public final String category; /** @@ -171,7 +175,6 @@ public final class OverlayInfo implements Parcelable { * User handle for which this overlay applies * @hide */ - @SystemApi public final int userId; /** @@ -236,6 +239,56 @@ public final class OverlayInfo implements Parcelable { ensureValidState(); } + /** + * Returns package name of the current overlay. + * @hide + */ + @SystemApi + @NonNull + public String getPackageName() { + return packageName; + } + + /** + * Returns the target package name of the current overlay. + * @hide + */ + @SystemApi + @Nullable + public String getTargetPackageName() { + return targetPackageName; + } + + /** + * Returns the category of the current overlay. + * @hide\ + */ + @SystemApi + @Nullable + public String getCategory() { + return category; + } + + /** + * Returns user handle for which this overlay applies to. + * @hide + */ + @SystemApi + @UserIdInt + public int getUserId() { + return userId; + } + + /** + * Returns name of the target overlayable declaration. + * @hide + */ + @SystemApi + @Nullable + public String getTargetOverlayableName() { + return targetOverlayableName; + } + private void ensureValidState() { if (packageName == null) { throw new IllegalArgumentException("packageName must not be null"); diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 068a93a253ff..5328dda03893 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -678,6 +678,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public static final int PRIVATE_FLAG_IS_RESOURCE_OVERLAY = 1 << 28; + /** + * Value for {@link #privateFlags}: If {@code true} this app allows + * shared/external storage media to be a sandboxed view that only contains + * files owned by the app. + * + * @hide + */ + public static final int PRIVATE_FLAG_ALLOW_EXTERNAL_STORAGE_SANDBOX = 1 << 29; /** @hide */ @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = { @@ -707,7 +715,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { PRIVATE_FLAG_VIRTUAL_PRELOAD, PRIVATE_FLAG_HAS_FRAGILE_USER_DATA, PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE, - PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE + PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE, + PRIVATE_FLAG_ALLOW_EXTERNAL_STORAGE_SANDBOX, }) @Retention(RetentionPolicy.SOURCE) public @interface ApplicationInfoPrivateFlags {} @@ -1822,6 +1831,16 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return (privateFlags & PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE) != 0; } + /** + * If {@code true} this app allows shared/external storage media to be a + * sandboxed view that only contains files owned by the app. + * + * @hide + */ + public boolean isExternalStorageSandboxAllowed() { + return (privateFlags & PRIVATE_FLAG_ALLOW_EXTERNAL_STORAGE_SANDBOX) != 0; + } + private boolean isAllowedToUseHiddenApis() { if (isSignedWithPlatformKey()) { return true; diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index c798270d1fdc..fb2218778c9b 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -740,6 +740,8 @@ interface IPackageManager { String getSystemTextClassifierPackageName(); + String getAttentionServicePackageName(); + String getWellbeingPackageName(); String getAppPredictionServicePackageName(); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 1e6cea39b44d..5d6867e14393 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1211,6 +1211,9 @@ public class PackageInstaller { * Adds a session ID to the set of sessions that will be committed atomically * when this session is committed. * + * <p>If the parent is staged or has rollback enabled, all children must have + * the same properties. + * * @param sessionId the session ID to add to this multi-package session. */ public void addChildSessionId(int sessionId) { @@ -1480,6 +1483,9 @@ public class PackageInstaller { /** * Request that rollbacks be enabled or disabled for the given upgrade. * + * <p>If the parent session is staged or has rollback enabled, all children sessions + * must have the same properties. + * * @param enable set to {@code true} to enable, {@code false} to disable * @hide */ @@ -1607,6 +1613,9 @@ public class PackageInstaller { * multi-package. In that case, if any of the children sessions fail to install at reboot, * all the other children sessions are aborted as well. * + * <p>If the parent session is staged or has rollback enabled, all children sessions + * must have the same properties. + * * {@hide} */ @SystemApi @TestApi @@ -1626,6 +1635,11 @@ public class PackageInstaller { installFlags |= PackageManager.INSTALL_APEX; } + /** @hide */ + public boolean getEnableRollback() { + return (installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0; + } + /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b190b34aa03f..de10bb00bfdf 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1397,6 +1397,14 @@ public abstract class PackageManager { */ public static final int INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS = -119; + /** + * Installation failed return code: one of the child sessions does not match the parent session + * in respect to staged or rollback enabled parameters. + * + * @hide + */ + public static final int INSTALL_FAILED_MULTIPACKAGE_INCONSISTENCY = -120; + /** @hide */ @IntDef(flag = true, prefix = { "DELETE_" }, value = { DELETE_KEEP_DATA, @@ -2990,6 +2998,7 @@ public abstract class PackageManager { * @hide */ @SystemApi + @TestApi public static final int FLAG_PERMISSION_POLICY_FIXED = 1 << 2; /** @@ -3013,6 +3022,7 @@ public abstract class PackageManager { * @hide */ @SystemApi + @TestApi public static final int FLAG_PERMISSION_SYSTEM_FIXED = 1 << 4; /** @@ -3214,7 +3224,8 @@ public abstract class PackageManager { * @throws NameNotFoundException if a package with the given name cannot be * found on the system. */ - public abstract PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) + public abstract PackageInfo getPackageInfo(@NonNull String packageName, + @PackageInfoFlags int flags) throws NameNotFoundException; /** @@ -3239,7 +3250,7 @@ public abstract class PackageManager { * @throws NameNotFoundException if a package with the given name cannot be * found on the system. */ - public abstract PackageInfo getPackageInfo(VersionedPackage versionedPackage, + public abstract PackageInfo getPackageInfo(@NonNull VersionedPackage versionedPackage, @PackageInfoFlags int flags) throws NameNotFoundException; /** @@ -3263,25 +3274,25 @@ public abstract class PackageManager { */ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) @UnsupportedAppUsage - public abstract PackageInfo getPackageInfoAsUser(String packageName, + public abstract PackageInfo getPackageInfoAsUser(@NonNull String packageName, @PackageInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException; /** * Map from the current package names in use on the device to whatever * the current canonical name of that package is. - * @param names Array of current names to be mapped. + * @param packageNames Array of current names to be mapped. * @return Returns an array of the same size as the original, containing * the canonical name for each package. */ - public abstract String[] currentToCanonicalPackageNames(String[] names); + public abstract String[] currentToCanonicalPackageNames(@NonNull String[] packageNames); /** * Map from a packages canonical name to the current name in use on the device. - * @param names Array of new names to be mapped. + * @param packageNames Array of new names to be mapped. * @return Returns an array of the same size as the original, containing * the current name for each package. */ - public abstract String[] canonicalToCurrentPackageNames(String[] names); + public abstract String[] canonicalToCurrentPackageNames(@NonNull String[] packageNames); /** * Returns a "good" intent to launch a front-door activity in a package. @@ -3360,7 +3371,7 @@ public abstract class PackageManager { * @throws NameNotFoundException if a package with the given name cannot be * found on the system. */ - public abstract int[] getPackageGids(String packageName, @PackageInfoFlags int flags) + public abstract int[] getPackageGids(@NonNull String packageName, @PackageInfoFlags int flags) throws NameNotFoundException; /** @@ -3375,7 +3386,7 @@ public abstract class PackageManager { * @throws NameNotFoundException if a package with the given name can not be * found on the system. */ - public abstract int getPackageUid(String packageName, @PackageInfoFlags int flags) + public abstract int getPackageUid(@NonNull String packageName, @PackageInfoFlags int flags) throws NameNotFoundException; /** @@ -3393,7 +3404,7 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public abstract int getPackageUidAsUser(String packageName, @UserIdInt int userId) + public abstract int getPackageUidAsUser(@NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException; /** @@ -3411,13 +3422,13 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public abstract int getPackageUidAsUser(String packageName, @PackageInfoFlags int flags, - @UserIdInt int userId) throws NameNotFoundException; + public abstract int getPackageUidAsUser(@NonNull String packageName, + @PackageInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException; /** * Retrieve all of the information we know about a particular permission. * - * @param name The fully qualified name (i.e. com.google.permission.LOGIN) + * @param permissionName The fully qualified name (i.e. com.google.permission.LOGIN) * of the permission you are interested in. * @param flags Additional option flags to modify the data returned. * @return Returns a {@link PermissionInfo} containing information about the @@ -3425,13 +3436,13 @@ public abstract class PackageManager { * @throws NameNotFoundException if a package with the given name cannot be * found on the system. */ - public abstract PermissionInfo getPermissionInfo(String name, @PermissionInfoFlags int flags) - throws NameNotFoundException; + public abstract PermissionInfo getPermissionInfo(@NonNull String permissionName, + @PermissionInfoFlags int flags) throws NameNotFoundException; /** * Query for all of the permissions associated with a particular group. * - * @param group The fully qualified name (i.e. com.google.permission.LOGIN) + * @param permissionGroup The fully qualified name (i.e. com.google.permission.LOGIN) * of the permission group you are interested in. Use null to * find all of the permissions not associated with a group. * @param flags Additional option flags to modify the data returned. @@ -3440,7 +3451,8 @@ public abstract class PackageManager { * @throws NameNotFoundException if a package with the given name cannot be * found on the system. */ - public abstract List<PermissionInfo> queryPermissionsByGroup(String group, + @NonNull + public abstract List<PermissionInfo> queryPermissionsByGroup(@NonNull String permissionGroup, @PermissionInfoFlags int flags) throws NameNotFoundException; /** @@ -3465,7 +3477,7 @@ public abstract class PackageManager { * Retrieve all of the information we know about a particular group of * permissions. * - * @param name The fully qualified name (i.e. + * @param permissionName The fully qualified name (i.e. * com.google.permission_group.APPS) of the permission you are * interested in. * @param flags Additional option flags to modify the data returned. @@ -3474,7 +3486,8 @@ public abstract class PackageManager { * @throws NameNotFoundException if a package with the given name cannot be * found on the system. */ - public abstract PermissionGroupInfo getPermissionGroupInfo(String name, + @NonNull + public abstract PermissionGroupInfo getPermissionGroupInfo(@NonNull String permissionName, @PermissionGroupInfoFlags int flags) throws NameNotFoundException; /** @@ -3484,6 +3497,7 @@ public abstract class PackageManager { * @return Returns a list of {@link PermissionGroupInfo} containing * information about all of the known permission groups. */ + @NonNull public abstract List<PermissionGroupInfo> getAllPermissionGroups( @PermissionGroupInfoFlags int flags); @@ -3504,12 +3518,14 @@ public abstract class PackageManager { * @throws NameNotFoundException if a package with the given name cannot be * found on the system. */ - public abstract ApplicationInfo getApplicationInfo(String packageName, + @NonNull + public abstract ApplicationInfo getApplicationInfo(@NonNull String packageName, @ApplicationInfoFlags int flags) throws NameNotFoundException; /** {@hide} */ + @NonNull @UnsupportedAppUsage - public abstract ApplicationInfo getApplicationInfoAsUser(String packageName, + public abstract ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName, @ApplicationInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException; /** @@ -3552,7 +3568,8 @@ public abstract class PackageManager { * @throws NameNotFoundException if a package with the given name cannot be * found on the system. */ - public abstract ActivityInfo getActivityInfo(ComponentName component, + @NonNull + public abstract ActivityInfo getActivityInfo(@NonNull ComponentName component, @ComponentInfoFlags int flags) throws NameNotFoundException; /** @@ -3568,7 +3585,8 @@ public abstract class PackageManager { * @throws NameNotFoundException if a package with the given name cannot be * found on the system. */ - public abstract ActivityInfo getReceiverInfo(ComponentName component, + @NonNull + public abstract ActivityInfo getReceiverInfo(@NonNull ComponentName component, @ComponentInfoFlags int flags) throws NameNotFoundException; /** @@ -3583,7 +3601,8 @@ public abstract class PackageManager { * @throws NameNotFoundException if a package with the given name cannot be * found on the system. */ - public abstract ServiceInfo getServiceInfo(ComponentName component, + @NonNull + public abstract ServiceInfo getServiceInfo(@NonNull ComponentName component, @ComponentInfoFlags int flags) throws NameNotFoundException; /** @@ -3599,7 +3618,8 @@ public abstract class PackageManager { * @throws NameNotFoundException if a package with the given name cannot be * found on the system. */ - public abstract ProviderInfo getProviderInfo(ComponentName component, + @NonNull + public abstract ProviderInfo getProviderInfo(@NonNull ComponentName component, @ComponentInfoFlags int flags) throws NameNotFoundException; /** @@ -3612,7 +3632,8 @@ public abstract class PackageManager { * @throws NameNotFoundException if a module with the given name cannot be * found on the system. */ - public ModuleInfo getModuleInfo(String packageName, @ModuleInfoFlags int flags) + @NonNull + public ModuleInfo getModuleInfo(@NonNull String packageName, @ModuleInfoFlags int flags) throws NameNotFoundException { throw new UnsupportedOperationException( "getModuleInfo not implemented in subclass"); @@ -3626,7 +3647,8 @@ public abstract class PackageManager { * module, containing information about the module. In the unlikely case * there are no installed modules, an empty list is returned. */ - public @NonNull List<ModuleInfo> getInstalledModules(@ModuleInfoFlags int flags) { + @NonNull + public List<ModuleInfo> getInstalledModules(@ModuleInfoFlags int flags) { throw new UnsupportedOperationException( "getInstalledModules not implemented in subclass"); } @@ -3644,6 +3666,7 @@ public abstract class PackageManager { * applications with data directory i.e. applications which had been * deleted with {@code DONT_DELETE_DATA} flag set). */ + @NonNull public abstract List<PackageInfo> getInstalledPackages(@PackageInfoFlags int flags); /** @@ -3661,8 +3684,9 @@ public abstract class PackageManager { * applications with data directory i.e. applications which had been * deleted with {@code DONT_DELETE_DATA} flag set). */ + @NonNull public abstract List<PackageInfo> getPackagesHoldingPermissions( - String[] permissions, @PackageInfoFlags int flags); + @NonNull String[] permissions, @PackageInfoFlags int flags); /** * Return a List of all packages that are installed on the device, for a @@ -3680,6 +3704,7 @@ public abstract class PackageManager { * deleted with {@code DONT_DELETE_DATA} flag set). * @hide */ + @NonNull @TestApi @SystemApi @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) @@ -3690,8 +3715,8 @@ public abstract class PackageManager { * Check whether a particular package has been granted a particular * permission. * - * @param permName The name of the permission you are checking for. - * @param pkgName The name of the package you are checking against. + * @param permissionName The name of the permission you are checking for. + * @param packageName The name of the package you are checking against. * * @return If the package has the permission, PERMISSION_GRANTED is * returned. If it does not have the permission, PERMISSION_DENIED @@ -3701,7 +3726,9 @@ public abstract class PackageManager { * @see #PERMISSION_DENIED */ @CheckResult - public abstract @PermissionResult int checkPermission(String permName, String pkgName); + @PermissionResult + public abstract int checkPermission(@NonNull String permissionName, + @NonNull String packageName); /** * Checks whether a particular permissions has been revoked for a @@ -3710,14 +3737,14 @@ public abstract class PackageManager { * permissions, hence the only way for an app to get such a permission * is by a policy change. * - * @param permName The name of the permission you are checking for. - * @param pkgName The name of the package you are checking against. + * @param permissionName The name of the permission you are checking for. + * @param packageName The name of the package you are checking against. * * @return Whether the permission is restricted by policy. */ @CheckResult - public abstract boolean isPermissionRevokedByPolicy(@NonNull String permName, - @NonNull String pkgName); + public abstract boolean isPermissionRevokedByPolicy(@NonNull String permissionName, + @NonNull String packageName); /** * Gets the package name of the component controlling runtime permissions. @@ -3726,6 +3753,7 @@ public abstract class PackageManager { * * @hide */ + @NonNull @TestApi public abstract String getPermissionControllerPackageName(); @@ -3761,7 +3789,7 @@ public abstract class PackageManager { * * @see #removePermission(String) */ - public abstract boolean addPermission(PermissionInfo info); + public abstract boolean addPermission(@NonNull PermissionInfo info); /** * Like {@link #addPermission(PermissionInfo)} but asynchronously @@ -3770,7 +3798,7 @@ public abstract class PackageManager { * expense of no guarantee the added permission will be retained if * the device is rebooted before it is written. */ - public abstract boolean addPermissionAsync(PermissionInfo info); + public abstract boolean addPermissionAsync(@NonNull PermissionInfo info); /** * Removes a permission that was previously added with @@ -3778,14 +3806,14 @@ public abstract class PackageManager { * -- you are only allowed to remove permissions that you are allowed * to add. * - * @param name The name of the permission to remove. + * @param permissionName The name of the permission to remove. * * @throws SecurityException if you are not allowed to remove the * given permission name. * * @see #addPermission(PermissionInfo) */ - public abstract void removePermission(String name); + public abstract void removePermission(@NonNull String permissionName); /** * Permission flags set when granting or revoking a permission. @@ -3880,8 +3908,9 @@ public abstract class PackageManager { android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS }) - public abstract @PermissionFlags int getPermissionFlags(String permissionName, - String packageName, @NonNull UserHandle user); + @PermissionFlags + public abstract int getPermissionFlags(@NonNull String permissionName, + @NonNull String packageName, @NonNull UserHandle user); /** * Updates the flags associated with a permission by replacing the flags in @@ -3901,9 +3930,9 @@ public abstract class PackageManager { android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS }) - public abstract void updatePermissionFlags(String permissionName, - String packageName, @PermissionFlags int flagMask, @PermissionFlags int flagValues, - @NonNull UserHandle user); + public abstract void updatePermissionFlags(@NonNull String permissionName, + @NonNull String packageName, @PermissionFlags int flagMask, + @PermissionFlags int flagValues, @NonNull UserHandle user); /** * Gets whether you should show UI with rationale for requesting a permission. @@ -3911,13 +3940,13 @@ public abstract class PackageManager { * which the permission is requested does not clearly communicate to the user * what would be the benefit from grating this permission. * - * @param permission A permission your app wants to request. + * @param permissionName A permission your app wants to request. * @return Whether you can show permission rationale UI. * * @hide */ @UnsupportedAppUsage - public abstract boolean shouldShowRequestPermissionRationale(String permission); + public abstract boolean shouldShowRequestPermissionRationale(@NonNull String permissionName); /** * Returns an {@link android.content.Intent} suitable for passing to @@ -3928,6 +3957,7 @@ public abstract class PackageManager { * * @hide */ + @NonNull @UnsupportedAppUsage public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) { if (ArrayUtils.isEmpty(permissions)) { @@ -3946,8 +3976,8 @@ public abstract class PackageManager { * with each other: they can share the same user-id, run instrumentation * against each other, etc. * - * @param pkg1 First package name whose signature will be compared. - * @param pkg2 Second package name whose signature will be compared. + * @param packageName1 First package name whose signature will be compared. + * @param packageName2 Second package name whose signature will be compared. * * @return Returns an integer indicating whether all signatures on the * two packages match. The value is >= 0 ({@link #SIGNATURE_MATCH}) if @@ -3957,7 +3987,9 @@ public abstract class PackageManager { * @see #checkSignatures(int, int) */ @CheckResult - public abstract @SignatureResult int checkSignatures(String pkg1, String pkg2); + @SignatureResult + public abstract int checkSignatures(@NonNull String packageName1, + @NonNull String packageName2); /** * Like {@link #checkSignatures(String, String)}, but takes UIDs of @@ -4030,7 +4062,7 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public abstract int getUidForSharedUser(String sharedUserName) + public abstract int getUidForSharedUser(@NonNull String sharedUserName) throws NameNotFoundException; /** @@ -4049,6 +4081,7 @@ public abstract class PackageManager { * applications with data directory i.e. applications which had been * deleted with {@code DONT_DELETE_DATA} flag set). */ + @NonNull public abstract List<ApplicationInfo> getInstalledApplications(@ApplicationInfoFlags int flags); /** @@ -4071,6 +4104,7 @@ public abstract class PackageManager { * deleted with {@code DONT_DELETE_DATA} flag set). * @hide */ + @NonNull @TestApi public abstract List<ApplicationInfo> getInstalledApplicationsAsUser( @ApplicationInfoFlags int flags, @UserIdInt int userId); @@ -4121,7 +4155,7 @@ public abstract class PackageManager { * @see #getInstantAppCookieMaxBytes() * @see #clearInstantAppCookie() */ - public abstract boolean isInstantApp(String packageName); + public abstract boolean isInstantApp(@NonNull String packageName); /** * Gets the maximum size in bytes of the cookie data an instant app @@ -4208,6 +4242,7 @@ public abstract class PackageManager { * available on the system, or null if none are installed. * */ + @Nullable public abstract String[] getSystemSharedLibraryNames(); /** @@ -4296,6 +4331,7 @@ public abstract class PackageManager { * @return An array of FeatureInfo classes describing the features * that are available on the system, or null if there are none(!!). */ + @NonNull public abstract FeatureInfo[] getSystemAvailableFeatures(); /** @@ -4306,7 +4342,7 @@ public abstract class PackageManager { * * @return Returns true if the devices supports the feature, else false. */ - public abstract boolean hasSystemFeature(String name); + public abstract boolean hasSystemFeature(@NonNull String featureName); /** * Check whether the given feature name and version is one of the available @@ -4317,7 +4353,7 @@ public abstract class PackageManager { * * @return Returns true if the devices supports the feature, else false. */ - public abstract boolean hasSystemFeature(String name, int version); + public abstract boolean hasSystemFeature(@NonNull String featureName, int version); /** * Determine the best action to perform for a given Intent. This is how @@ -4345,7 +4381,9 @@ public abstract class PackageManager { * found and there is no default set, returns a ResolveInfo object * containing something else, such as the activity resolver. */ - public abstract ResolveInfo resolveActivity(Intent intent, @ResolveInfoFlags int flags); + @Nullable + public abstract ResolveInfo resolveActivity(@NonNull Intent intent, + @ResolveInfoFlags int flags); /** * Determine the best action to perform for a given Intent for a given user. @@ -4375,9 +4413,10 @@ public abstract class PackageManager { * containing something else, such as the activity resolver. * @hide */ + @Nullable @UnsupportedAppUsage - public abstract ResolveInfo resolveActivityAsUser(Intent intent, @ResolveInfoFlags int flags, - @UserIdInt int userId); + public abstract ResolveInfo resolveActivityAsUser(@NonNull Intent intent, + @ResolveInfoFlags int flags, @UserIdInt int userId); /** * Retrieve all activities that can be performed for the given intent. @@ -4394,7 +4433,8 @@ public abstract class PackageManager { * {@link #resolveActivity}. If there are no matching activities, an * empty list is returned. */ - public abstract List<ResolveInfo> queryIntentActivities(Intent intent, + @Nullable + public abstract List<ResolveInfo> queryIntentActivities(@NonNull Intent intent, @ResolveInfoFlags int flags); /** @@ -4414,8 +4454,9 @@ public abstract class PackageManager { * empty list is returned. * @hide */ + @Nullable @UnsupportedAppUsage - public abstract List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, + public abstract List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent, @ResolveInfoFlags int flags, @UserIdInt int userId); /** @@ -4469,8 +4510,9 @@ public abstract class PackageManager { * included by one of the <var>specifics</var> intents. If there are * no matching activities, an empty list is returned. */ + @NonNull public abstract List<ResolveInfo> queryIntentActivityOptions(@Nullable ComponentName caller, - @Nullable Intent[] specifics, Intent intent, @ResolveInfoFlags int flags); + @Nullable Intent[] specifics, @NonNull Intent intent, @ResolveInfoFlags int flags); /** * Retrieve all receivers that can handle a broadcast of the given intent. @@ -4481,7 +4523,8 @@ public abstract class PackageManager { * each matching receiver, ordered from best to worst. If there are * no matching receivers, an empty list or null is returned. */ - public abstract List<ResolveInfo> queryBroadcastReceivers(Intent intent, + @NonNull + public abstract List<ResolveInfo> queryBroadcastReceivers(@NonNull Intent intent, @ResolveInfoFlags int flags); /** @@ -4496,9 +4539,10 @@ public abstract class PackageManager { * no matching receivers, an empty list or null is returned. * @hide */ + @NonNull @SystemApi @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) - public List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent, + public List<ResolveInfo> queryBroadcastReceiversAsUser(@NonNull Intent intent, @ResolveInfoFlags int flags, UserHandle userHandle) { return queryBroadcastReceiversAsUser(intent, flags, userHandle.getIdentifier()); } @@ -4506,15 +4550,17 @@ public abstract class PackageManager { /** * @hide */ + @NonNull @UnsupportedAppUsage - public abstract List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent, + public abstract List<ResolveInfo> queryBroadcastReceiversAsUser(@NonNull Intent intent, @ResolveInfoFlags int flags, @UserIdInt int userId); - /** {@hide} */ + /** @deprecated @hide */ + @NonNull @Deprecated @UnsupportedAppUsage - public List<ResolveInfo> queryBroadcastReceivers(Intent intent, + public List<ResolveInfo> queryBroadcastReceivers(@NonNull Intent intent, @ResolveInfoFlags int flags, @UserIdInt int userId) { final String msg = "Shame on you for calling the hidden API " + "queryBroadcastReceivers(). Shame!"; @@ -4536,13 +4582,15 @@ public abstract class PackageManager { * that was determined to be the best action. Returns null if no * matching service was found. */ - public abstract ResolveInfo resolveService(Intent intent, @ResolveInfoFlags int flags); + @Nullable + public abstract ResolveInfo resolveService(@NonNull Intent intent, @ResolveInfoFlags int flags); /** * @hide */ - public abstract ResolveInfo resolveServiceAsUser(Intent intent, @ResolveInfoFlags int flags, - @UserIdInt int userId); + @Nullable + public abstract ResolveInfo resolveServiceAsUser(@NonNull Intent intent, + @ResolveInfoFlags int flags, @UserIdInt int userId); /** * Retrieve all services that can match the given intent. @@ -4555,7 +4603,8 @@ public abstract class PackageManager { * {@link #resolveService}. If there are no matching services, an * empty list or null is returned. */ - public abstract List<ResolveInfo> queryIntentServices(Intent intent, + @NonNull + public abstract List<ResolveInfo> queryIntentServices(@NonNull Intent intent, @ResolveInfoFlags int flags); /** @@ -4571,8 +4620,9 @@ public abstract class PackageManager { * empty list or null is returned. * @hide */ + @NonNull @UnsupportedAppUsage - public abstract List<ResolveInfo> queryIntentServicesAsUser(Intent intent, + public abstract List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent, @ResolveInfoFlags int flags, @UserIdInt int userId); /** @@ -4608,9 +4658,10 @@ public abstract class PackageManager { * no matching services, an empty list or null is returned. * @hide */ + @NonNull @UnsupportedAppUsage public abstract List<ResolveInfo> queryIntentContentProvidersAsUser( - Intent intent, @ResolveInfoFlags int flags, @UserIdInt int userId); + @NonNull Intent intent, @ResolveInfoFlags int flags, @UserIdInt int userId); /** * Retrieve all providers that can match the given intent. @@ -4642,7 +4693,8 @@ public abstract class PackageManager { * each matching provider, ordered from best to worst. If there are * no matching services, an empty list or null is returned. */ - public abstract List<ResolveInfo> queryIntentContentProviders(Intent intent, + @NonNull + public abstract List<ResolveInfo> queryIntentContentProviders(@NonNull Intent intent, @ResolveInfoFlags int flags); /** @@ -4659,21 +4711,23 @@ public abstract class PackageManager { * @return A {@link ProviderInfo} object containing information about the * provider. If a provider was not found, returns null. */ - public abstract ProviderInfo resolveContentProvider(String authority, + @Nullable + public abstract ProviderInfo resolveContentProvider(@NonNull String authority, @ComponentInfoFlags int flags); /** * Find a single content provider by its base path name. * - * @param name The name of the provider to find. + * @param providerName The name of the provider to find. * @param flags Additional option flags to modify the data returned. * @param userId The user id. * @return A {@link ProviderInfo} object containing information about the * provider. If a provider was not found, returns null. * @hide */ + @Nullable @UnsupportedAppUsage - public abstract ProviderInfo resolveContentProviderAsUser(String name, + public abstract ProviderInfo resolveContentProviderAsUser(@NonNull String providerName, @ComponentInfoFlags int flags, @UserIdInt int userId); /** @@ -4693,8 +4747,9 @@ public abstract class PackageManager { * <var>processName</var> is null, all known content providers. * <em>If there are no matching providers, null is returned.</em> */ + @NonNull public abstract List<ProviderInfo> queryContentProviders( - String processName, int uid, @ComponentInfoFlags int flags); + @Nullable String processName, int uid, @ComponentInfoFlags int flags); /** * Same as {@link #queryContentProviders}, except when {@code metaDataKey} is not null, @@ -4711,8 +4766,9 @@ public abstract class PackageManager { * * @hide */ - public List<ProviderInfo> queryContentProviders( - String processName, int uid, @ComponentInfoFlags int flags, String metaDataKey) { + @NonNull + public List<ProviderInfo> queryContentProviders(@Nullable String processName, + int uid, @ComponentInfoFlags int flags, String metaDataKey) { // Provide the default implementation for mocks. return queryContentProviders(processName, uid, flags); } @@ -4730,7 +4786,8 @@ public abstract class PackageManager { * @throws NameNotFoundException if a package with the given name cannot be * found on the system. */ - public abstract InstrumentationInfo getInstrumentationInfo(ComponentName className, + @NonNull + public abstract InstrumentationInfo getInstrumentationInfo(@NonNull ComponentName className, @InstrumentationInfoFlags int flags) throws NameNotFoundException; /** @@ -4745,7 +4802,8 @@ public abstract class PackageManager { * entry for each matching instrumentation. If there are no * instrumentation available, returns an empty list. */ - public abstract List<InstrumentationInfo> queryInstrumentation(String targetPackage, + @NonNull + public abstract List<InstrumentationInfo> queryInstrumentation(@NonNull String targetPackage, @InstrumentationInfoFlags int flags); /** @@ -4765,8 +4823,9 @@ public abstract class PackageManager { * @return Returns a Drawable holding the requested image. Returns null if * an image could not be found for any reason. */ - public abstract Drawable getDrawable(String packageName, @DrawableRes int resid, - ApplicationInfo appInfo); + @Nullable + public abstract Drawable getDrawable(@NonNull String packageName, @DrawableRes int resid, + @Nullable ApplicationInfo appInfo); /** * Retrieve the icon associated with an activity. Given the full name of @@ -4783,7 +4842,8 @@ public abstract class PackageManager { * * @see #getActivityIcon(Intent) */ - public abstract Drawable getActivityIcon(ComponentName activityName) + @NonNull + public abstract Drawable getActivityIcon(@NonNull ComponentName activityName) throws NameNotFoundException; /** @@ -4803,7 +4863,8 @@ public abstract class PackageManager { * * @see #getActivityIcon(ComponentName) */ - public abstract Drawable getActivityIcon(Intent intent) + @NonNull + public abstract Drawable getActivityIcon(@NonNull Intent intent) throws NameNotFoundException; /** @@ -4819,7 +4880,8 @@ public abstract class PackageManager { * activity could not be loaded. * @see #getActivityBanner(Intent) */ - public abstract Drawable getActivityBanner(ComponentName activityName) + @Nullable + public abstract Drawable getActivityBanner(@NonNull ComponentName activityName) throws NameNotFoundException; /** @@ -4837,7 +4899,8 @@ public abstract class PackageManager { * matching the given intent could not be loaded. * @see #getActivityBanner(ComponentName) */ - public abstract Drawable getActivityBanner(Intent intent) + @Nullable + public abstract Drawable getActivityBanner(@NonNull Intent intent) throws NameNotFoundException; /** @@ -4846,6 +4909,7 @@ public abstract class PackageManager { * * @return Drawable Image of the icon. */ + @NonNull public abstract Drawable getDefaultActivityIcon(); /** @@ -4859,7 +4923,8 @@ public abstract class PackageManager { * * @see #getApplicationIcon(String) */ - public abstract Drawable getApplicationIcon(ApplicationInfo info); + @NonNull + public abstract Drawable getApplicationIcon(@NonNull ApplicationInfo info); /** * Retrieve the icon associated with an application. Given the name of the @@ -4877,7 +4942,8 @@ public abstract class PackageManager { * * @see #getApplicationIcon(ApplicationInfo) */ - public abstract Drawable getApplicationIcon(String packageName) + @NonNull + public abstract Drawable getApplicationIcon(@NonNull String packageName) throws NameNotFoundException; /** @@ -4888,7 +4954,8 @@ public abstract class PackageManager { * banner specified. * @see #getApplicationBanner(String) */ - public abstract Drawable getApplicationBanner(ApplicationInfo info); + @Nullable + public abstract Drawable getApplicationBanner(@NonNull ApplicationInfo info); /** * Retrieve the banner associated with an application. Given the name of the @@ -4904,7 +4971,8 @@ public abstract class PackageManager { * application could not be loaded. * @see #getApplicationBanner(ApplicationInfo) */ - public abstract Drawable getApplicationBanner(String packageName) + @Nullable + public abstract Drawable getApplicationBanner(@NonNull String packageName) throws NameNotFoundException; /** @@ -4920,7 +4988,8 @@ public abstract class PackageManager { * activity could not be loaded. * @see #getActivityLogo(Intent) */ - public abstract Drawable getActivityLogo(ComponentName activityName) + @Nullable + public abstract Drawable getActivityLogo(@NonNull ComponentName activityName) throws NameNotFoundException; /** @@ -4941,7 +5010,8 @@ public abstract class PackageManager { * * @see #getActivityLogo(ComponentName) */ - public abstract Drawable getActivityLogo(Intent intent) + @Nullable + public abstract Drawable getActivityLogo(@NonNull Intent intent) throws NameNotFoundException; /** @@ -4955,7 +5025,8 @@ public abstract class PackageManager { * * @see #getApplicationLogo(String) */ - public abstract Drawable getApplicationLogo(ApplicationInfo info); + @Nullable + public abstract Drawable getApplicationLogo(@NonNull ApplicationInfo info); /** * Retrieve the logo associated with an application. Given the name of the @@ -4974,7 +5045,8 @@ public abstract class PackageManager { * * @see #getApplicationLogo(ApplicationInfo) */ - public abstract Drawable getApplicationLogo(String packageName) + @Nullable + public abstract Drawable getApplicationLogo(@NonNull String packageName) throws NameNotFoundException; /** @@ -4988,12 +5060,14 @@ public abstract class PackageManager { * is performed in place and the original drawable is returned. * </p> * - * @param icon The icon to badge. + * @param drawable The drawable to badge. * @param user The target user. * @return A drawable that combines the original icon and a badge as * determined by the system. */ - public abstract Drawable getUserBadgedIcon(Drawable icon, UserHandle user); + @NonNull + public abstract Drawable getUserBadgedIcon(@NonNull Drawable drawable, + @NonNull UserHandle user); /** * If the target user is a managed profile of the calling user or the caller @@ -5019,8 +5093,9 @@ public abstract class PackageManager { * @return A drawable that combines the original drawable and a badge as * determined by the system. */ - public abstract Drawable getUserBadgedDrawableForDensity(Drawable drawable, - UserHandle user, Rect badgeLocation, int badgeDensity); + @NonNull + public abstract Drawable getUserBadgedDrawableForDensity(@NonNull Drawable drawable, + @NonNull UserHandle user, @Nullable Rect badgeLocation, int badgeDensity); /** * If the target user is a managed profile of the calling user or the caller @@ -5034,8 +5109,9 @@ public abstract class PackageManager { * @return the drawable or null if no drawable is required. * @hide */ + @Nullable @UnsupportedAppUsage - public abstract Drawable getUserBadgeForDensity(UserHandle user, int density); + public abstract Drawable getUserBadgeForDensity(@NonNull UserHandle user, int density); /** * If the target user is a managed profile of the calling user or the caller @@ -5051,8 +5127,10 @@ public abstract class PackageManager { * @return the drawable or null if no drawable is required. * @hide */ + @Nullable @UnsupportedAppUsage - public abstract Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density); + public abstract Drawable getUserBadgeForDensityNoBackground(@NonNull UserHandle user, + int density); /** * If the target user is a managed profile of the calling user or the caller @@ -5065,7 +5143,9 @@ public abstract class PackageManager { * @return A label that combines the original label and a badge as * determined by the system. */ - public abstract CharSequence getUserBadgedLabel(CharSequence label, UserHandle user); + @NonNull + public abstract CharSequence getUserBadgedLabel(@NonNull CharSequence label, + @NonNull UserHandle user); /** * Retrieve text from a package. This is a low-level API used by @@ -5084,8 +5164,9 @@ public abstract class PackageManager { * @return Returns a CharSequence holding the requested text. Returns null * if the text could not be found for any reason. */ - public abstract CharSequence getText(String packageName, @StringRes int resid, - ApplicationInfo appInfo); + @Nullable + public abstract CharSequence getText(@NonNull String packageName, @StringRes int resid, + @Nullable ApplicationInfo appInfo); /** * Retrieve an XML file from a package. This is a low-level API used to @@ -5103,8 +5184,9 @@ public abstract class PackageManager { * data. Returns null if the xml resource could not be found for any * reason. */ - public abstract XmlResourceParser getXml(String packageName, @XmlRes int resid, - ApplicationInfo appInfo); + @Nullable + public abstract XmlResourceParser getXml(@NonNull String packageName, @XmlRes int resid, + @Nullable ApplicationInfo appInfo); /** * Return the label to use for this application. @@ -5113,7 +5195,8 @@ public abstract class PackageManager { * it could not be found for any reason. * @param info The application to get the label of. */ - public abstract CharSequence getApplicationLabel(ApplicationInfo info); + @NonNull + public abstract CharSequence getApplicationLabel(@NonNull ApplicationInfo info); /** * Retrieve the resources associated with an activity. Given the full @@ -5130,7 +5213,8 @@ public abstract class PackageManager { * * @see #getResourcesForApplication(ApplicationInfo) */ - public abstract Resources getResourcesForActivity(ComponentName activityName) + @NonNull + public abstract Resources getResourcesForActivity(@NonNull ComponentName activityName) throws NameNotFoundException; /** @@ -5143,7 +5227,8 @@ public abstract class PackageManager { * @throws NameNotFoundException Thrown if the resources for the given * application could not be loaded (most likely because it was uninstalled). */ - public abstract Resources getResourcesForApplication(ApplicationInfo app) + @NonNull + public abstract Resources getResourcesForApplication(@NonNull ApplicationInfo app) throws NameNotFoundException; /** @@ -5152,7 +5237,7 @@ public abstract class PackageManager { * calls getResources() to return its application's resources. If the * appPackageName cannot be found, NameNotFoundException is thrown. * - * @param appPackageName Package name of the application whose resources + * @param packageName Package name of the application whose resources * are to be retrieved. * * @return Returns the application's Resources. @@ -5161,12 +5246,14 @@ public abstract class PackageManager { * * @see #getResourcesForApplication(ApplicationInfo) */ - public abstract Resources getResourcesForApplication(String appPackageName) + @NonNull + public abstract Resources getResourcesForApplication(@NonNull String packageName) throws NameNotFoundException; /** @hide */ + @NonNull @UnsupportedAppUsage - public abstract Resources getResourcesForApplicationAsUser(String appPackageName, + public abstract Resources getResourcesForApplicationAsUser(@NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException; /** @@ -5178,7 +5265,9 @@ public abstract class PackageManager { * @return A PackageInfo object containing information about the package * archive. If the package could not be parsed, returns null. */ - public PackageInfo getPackageArchiveInfo(String archiveFilePath, @PackageInfoFlags int flags) { + @Nullable + public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath, + @PackageInfoFlags int flags) { final PackageParser parser = new PackageParser(); parser.setCallback(new PackageParser.CallbackImpl(this)); final File apkFile = new File(archiveFilePath); @@ -5212,7 +5301,8 @@ public abstract class PackageManager { */ @Deprecated @SystemApi - public abstract int installExistingPackage(String packageName) throws NameNotFoundException; + public abstract int installExistingPackage(@NonNull String packageName) + throws NameNotFoundException; /** * If there is already an application with the given package name installed @@ -5223,8 +5313,8 @@ public abstract class PackageManager { */ @Deprecated @SystemApi - public abstract int installExistingPackage(String packageName, @InstallReason int installReason) - throws NameNotFoundException; + public abstract int installExistingPackage(@NonNull String packageName, + @InstallReason int installReason) throws NameNotFoundException; /** * If there is already an application with the given package name installed @@ -5239,8 +5329,8 @@ public abstract class PackageManager { Manifest.permission.INSTALL_PACKAGES, Manifest.permission.INTERACT_ACROSS_USERS_FULL}) @UnsupportedAppUsage - public abstract int installExistingPackageAsUser(String packageName, @UserIdInt int userId) - throws NameNotFoundException; + public abstract int installExistingPackageAsUser(@NonNull String packageName, + @UserIdInt int userId) throws NameNotFoundException; /** * Allows a package listening to the @@ -5316,7 +5406,7 @@ public abstract class PackageManager { @SystemApi @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int verificationId, int verificationCode, - List<String> failedDomains); + @NonNull List<String> failedDomains); /** * Get the status of a Domain Verification Result for an IntentFilter. This is @@ -5340,7 +5430,8 @@ public abstract class PackageManager { */ @SystemApi @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL) - public abstract int getIntentVerificationStatusAsUser(String packageName, @UserIdInt int userId); + public abstract int getIntentVerificationStatusAsUser(@NonNull String packageName, + @UserIdInt int userId); /** * Allow to change the status of a Intent Verification status for all IntentFilter of an App. @@ -5364,8 +5455,8 @@ public abstract class PackageManager { */ @SystemApi @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) - public abstract boolean updateIntentVerificationStatusAsUser(String packageName, int status, - @UserIdInt int userId); + public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String packageName, + int status, @UserIdInt int userId); /** * Get the list of IntentFilterVerificationInfo for a specific package and User. @@ -5379,9 +5470,10 @@ public abstract class PackageManager { * * @hide */ + @NonNull @SystemApi public abstract List<IntentFilterVerificationInfo> getIntentFilterVerifications( - String packageName); + @NonNull String packageName); /** * Get the list of IntentFilter for a specific package. @@ -5394,8 +5486,9 @@ public abstract class PackageManager { * * @hide */ + @NonNull @SystemApi - public abstract List<IntentFilter> getAllIntentFilters(String packageName); + public abstract List<IntentFilter> getAllIntentFilters(@NonNull String packageName); /** * Get the default Browser package name for a specific user. @@ -5407,6 +5500,7 @@ public abstract class PackageManager { * * @hide */ + @Nullable @TestApi @SystemApi @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL) @@ -5428,7 +5522,7 @@ public abstract class PackageManager { @RequiresPermission(allOf = { Manifest.permission.SET_PREFERRED_APPLICATIONS, Manifest.permission.INTERACT_ACROSS_USERS_FULL}) - public abstract boolean setDefaultBrowserPackageNameAsUser(String packageName, + public abstract boolean setDefaultBrowserPackageNameAsUser(@Nullable String packageName, @UserIdInt int userId); /** @@ -5446,13 +5540,13 @@ public abstract class PackageManager { * @param installerPackageName The package name of the new installer. May be * null to clear the association. */ - public abstract void setInstallerPackageName(String targetPackage, - String installerPackageName); + public abstract void setInstallerPackageName(@NonNull String targetPackage, + @Nullable String installerPackageName); /** @hide */ @SystemApi @RequiresPermission(Manifest.permission.INSTALL_PACKAGES) - public abstract void setUpdateAvailable(String packageName, boolean updateAvaialble); + public abstract void setUpdateAvailable(@NonNull String packageName, boolean updateAvaialble); /** * Attempts to delete a package. Since this may take a little while, the @@ -5472,8 +5566,8 @@ public abstract class PackageManager { */ @RequiresPermission(Manifest.permission.DELETE_PACKAGES) @UnsupportedAppUsage - public abstract void deletePackage(String packageName, IPackageDeleteObserver observer, - @DeleteFlags int flags); + public abstract void deletePackage(@NonNull String packageName, + @Nullable IPackageDeleteObserver observer, @DeleteFlags int flags); /** * Attempts to delete a package. Since this may take a little while, the @@ -5495,7 +5589,8 @@ public abstract class PackageManager { Manifest.permission.INTERACT_ACROSS_USERS_FULL}) @UnsupportedAppUsage public abstract void deletePackageAsUser(@NonNull String packageName, - IPackageDeleteObserver observer, @DeleteFlags int flags, @UserIdInt int userId); + @Nullable IPackageDeleteObserver observer, @DeleteFlags int flags, + @UserIdInt int userId); /** * Retrieve the package name of the application that installed a package. This identifies @@ -5505,7 +5600,7 @@ public abstract class PackageManager { * @throws IllegalArgumentException if the given package name is not installed */ @Nullable - public abstract String getInstallerPackageName(String packageName); + public abstract String getInstallerPackageName(@NonNull String packageName); /** * Attempts to clear the user data directory of an application. @@ -5522,8 +5617,8 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public abstract void clearApplicationUserData(String packageName, - IPackageDataObserver observer); + public abstract void clearApplicationUserData(@NonNull String packageName, + @Nullable IPackageDataObserver observer); /** * Attempts to delete the cache files associated with an application. * Since this may take a little while, the result will @@ -5541,8 +5636,8 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public abstract void deleteApplicationCacheFiles(String packageName, - IPackageDataObserver observer); + public abstract void deleteApplicationCacheFiles(@NonNull String packageName, + @Nullable IPackageDataObserver observer); /** * Attempts to delete the cache files associated with an application for a given user. Since @@ -5563,8 +5658,8 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public abstract void deleteApplicationCacheFilesAsUser(String packageName, int userId, - IPackageDataObserver observer); + public abstract void deleteApplicationCacheFilesAsUser(@NonNull String packageName, + @UserIdInt int userId, @Nullable IPackageDataObserver observer); /** * Free storage by deleting LRU sorted list of cache files across @@ -5589,14 +5684,15 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public void freeStorageAndNotify(long freeStorageSize, IPackageDataObserver observer) { + public void freeStorageAndNotify(long freeStorageSize, + @Nullable IPackageDataObserver observer) { freeStorageAndNotify(null, freeStorageSize, observer); } /** {@hide} */ @UnsupportedAppUsage - public abstract void freeStorageAndNotify(String volumeUuid, long freeStorageSize, - IPackageDataObserver observer); + public abstract void freeStorageAndNotify(@Nullable String volumeUuid, long freeStorageSize, + @Nullable IPackageDataObserver observer); /** * Free storage by deleting LRU sorted list of cache files across @@ -5622,13 +5718,14 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public void freeStorage(long freeStorageSize, IntentSender pi) { + public void freeStorage(long freeStorageSize, @Nullable IntentSender pi) { freeStorage(null, freeStorageSize, pi); } /** {@hide} */ @UnsupportedAppUsage - public abstract void freeStorage(String volumeUuid, long freeStorageSize, IntentSender pi); + public abstract void freeStorage(@Nullable String volumeUuid, long freeStorageSize, + @Nullable IntentSender pi); /** * Retrieve the size information for a package. @@ -5651,8 +5748,8 @@ public abstract class PackageManager { */ @Deprecated @UnsupportedAppUsage - public abstract void getPackageSizeInfoAsUser(String packageName, @UserIdInt int userId, - IPackageStatsObserver observer); + public abstract void getPackageSizeInfoAsUser(@NonNull String packageName, + @UserIdInt int userId, @Nullable IPackageStatsObserver observer); /** * Like {@link #getPackageSizeInfoAsUser(String, int, IPackageStatsObserver)}, but @@ -5663,7 +5760,7 @@ public abstract class PackageManager { */ @Deprecated @UnsupportedAppUsage - public void getPackageSizeInfo(String packageName, IPackageStatsObserver observer) { + public void getPackageSizeInfo(@NonNull String packageName, IPackageStatsObserver observer) { getPackageSizeInfoAsUser(packageName, getUserId(), observer); } @@ -5676,7 +5773,7 @@ public abstract class PackageManager { * holders, see {@link android.app.role.RoleManager}. */ @Deprecated - public abstract void addPackageToPreferred(String packageName); + public abstract void addPackageToPreferred(@NonNull String packageName); /** * @deprecated This function no longer does anything. It is the platform's @@ -5687,7 +5784,7 @@ public abstract class PackageManager { * holders, see {@link android.app.role.RoleManager}. */ @Deprecated - public abstract void removePackageFromPreferred(String packageName); + public abstract void removePackageFromPreferred(@NonNull String packageName); /** * Retrieve the list of all currently configured preferred packages. The @@ -5705,6 +5802,7 @@ public abstract class PackageManager { * an app to be responsible for a particular role and to check current role * holders, see {@link android.app.role.RoleManager}. */ + @NonNull @Deprecated public abstract List<PackageInfo> getPreferredPackages(@PackageInfoFlags int flags); @@ -5731,8 +5829,8 @@ public abstract class PackageManager { * holders, see {@link android.app.role.RoleManager}. */ @Deprecated - public abstract void addPreferredActivity(IntentFilter filter, int match, - ComponentName[] set, ComponentName activity); + public abstract void addPreferredActivity(@NonNull IntentFilter filter, int match, + @Nullable ComponentName[] set, @NonNull ComponentName activity); /** * Same as {@link #addPreferredActivity(IntentFilter, int, @@ -5749,8 +5847,8 @@ public abstract class PackageManager { */ @Deprecated @UnsupportedAppUsage - public void addPreferredActivityAsUser(IntentFilter filter, int match, - ComponentName[] set, ComponentName activity, @UserIdInt int userId) { + public void addPreferredActivityAsUser(@NonNull IntentFilter filter, int match, + @Nullable ComponentName[] set, @NonNull ComponentName activity, @UserIdInt int userId) { throw new RuntimeException("Not implemented. Must override in a subclass."); } @@ -5781,8 +5879,8 @@ public abstract class PackageManager { */ @Deprecated @UnsupportedAppUsage - public abstract void replacePreferredActivity(IntentFilter filter, int match, - ComponentName[] set, ComponentName activity); + public abstract void replacePreferredActivity(@NonNull IntentFilter filter, int match, + @Nullable ComponentName[] set, @NonNull ComponentName activity); /** * Replaces an existing preferred activity mapping to the system, and if that were not present @@ -5826,8 +5924,8 @@ public abstract class PackageManager { */ @Deprecated @UnsupportedAppUsage - public void replacePreferredActivityAsUser(IntentFilter filter, int match, - ComponentName[] set, ComponentName activity, @UserIdInt int userId) { + public void replacePreferredActivityAsUser(@NonNull IntentFilter filter, int match, + @Nullable ComponentName[] set, @NonNull ComponentName activity, @UserIdInt int userId) { throw new RuntimeException("Not implemented. Must override in a subclass."); } @@ -5848,7 +5946,7 @@ public abstract class PackageManager { * holders, see {@link android.app.role.RoleManager}. */ @Deprecated - public abstract void clearPackagePreferredActivities(String packageName); + public abstract void clearPackagePreferredActivities(@NonNull String packageName); /** * Retrieve all preferred activities, previously added with @@ -5876,15 +5974,16 @@ public abstract class PackageManager { */ @Deprecated public abstract int getPreferredActivities(@NonNull List<IntentFilter> outFilters, - @NonNull List<ComponentName> outActivities, String packageName); + @NonNull List<ComponentName> outActivities, @Nullable String packageName); /** * Ask for the set of available 'home' activities and the current explicit * default, if any. * @hide */ + @Nullable @UnsupportedAppUsage - public abstract ComponentName getHomeActivities(List<ResolveInfo> outActivities); + public abstract ComponentName getHomeActivities(@NonNull List<ResolveInfo> outActivities); /** * Set the enabled setting for a package component (activity, receiver, service, provider). @@ -5984,7 +6083,7 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public abstract void flushPackageRestrictionsAsUser(int userId); + public abstract void flushPackageRestrictionsAsUser(@UserIdInt int userId); /** * Puts the package in a hidden state, which is almost like an uninstalled state, @@ -5994,8 +6093,8 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public abstract boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden, - UserHandle userHandle); + public abstract boolean setApplicationHiddenSettingAsUser(@NonNull String packageName, + boolean hidden, @NonNull UserHandle userHandle); /** * Returns the hidden state of a package. @@ -6003,8 +6102,8 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public abstract boolean getApplicationHiddenSettingAsUser(String packageName, - UserHandle userHandle); + public abstract boolean getApplicationHiddenSettingAsUser(@NonNull String packageName, + @NonNull UserHandle userHandle); /** * Return whether the device has been booted into safe mode. @@ -6020,7 +6119,8 @@ public abstract class PackageManager { */ @SystemApi @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS) - public abstract void addOnPermissionsChangeListener(OnPermissionsChangedListener listener); + public abstract void addOnPermissionsChangeListener( + @NonNull OnPermissionsChangedListener listener); /** * Remvoes a listener for permission changes for installed packages. @@ -6031,7 +6131,8 @@ public abstract class PackageManager { */ @SystemApi @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS) - public abstract void removeOnPermissionsChangeListener(OnPermissionsChangedListener listener); + public abstract void removeOnPermissionsChangeListener( + @NonNull OnPermissionsChangedListener listener); /** * Return the {@link KeySet} associated with the String alias for this @@ -6041,14 +6142,16 @@ public abstract class PackageManager { * application's AndroidManifest.xml. * @hide */ + @NonNull @UnsupportedAppUsage - public abstract KeySet getKeySetByAlias(String packageName, String alias); + public abstract KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias); /** Return the signing {@link KeySet} for this application. * @hide */ + @NonNull @UnsupportedAppUsage - public abstract KeySet getSigningKeySet(String packageName); + public abstract KeySet getSigningKeySet(@NonNull String packageName); /** * Return whether the package denoted by packageName has been signed by all @@ -6058,7 +6161,7 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public abstract boolean isSignedBy(String packageName, KeySet ks); + public abstract boolean isSignedBy(@NonNull String packageName, @NonNull KeySet ks); /** * Return whether the package denoted by packageName has been signed by all @@ -6067,7 +6170,7 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public abstract boolean isSignedByExactly(String packageName, KeySet ks); + public abstract boolean isSignedByExactly(@NonNull String packageName, @NonNull KeySet ks); /** * Flag to denote no restrictions. This should be used to clear any restrictions that may have @@ -6284,7 +6387,7 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public abstract boolean isPackageSuspendedForUser(String packageName, int userId); + public abstract boolean isPackageSuspendedForUser(@NonNull String packageName, int userId); /** * Query if an app is currently suspended. @@ -6294,7 +6397,7 @@ public abstract class PackageManager { * * @see #isPackageSuspended() */ - public boolean isPackageSuspended(String packageName) throws NameNotFoundException { + public boolean isPackageSuspended(@NonNull String packageName) throws NameNotFoundException { throw new UnsupportedOperationException("isPackageSuspended not implemented"); } @@ -6378,23 +6481,26 @@ public abstract class PackageManager { /** {@hide} */ @UnsupportedAppUsage - public abstract void registerMoveCallback(MoveCallback callback, Handler handler); + public abstract void registerMoveCallback(@NonNull MoveCallback callback, + @NonNull Handler handler); /** {@hide} */ @UnsupportedAppUsage - public abstract void unregisterMoveCallback(MoveCallback callback); + public abstract void unregisterMoveCallback(@NonNull MoveCallback callback); /** {@hide} */ @UnsupportedAppUsage - public abstract int movePackage(String packageName, VolumeInfo vol); + public abstract int movePackage(@NonNull String packageName, @NonNull VolumeInfo vol); /** {@hide} */ @UnsupportedAppUsage - public abstract @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app); + public abstract @Nullable VolumeInfo getPackageCurrentVolume(@NonNull ApplicationInfo app); /** {@hide} */ + @NonNull @UnsupportedAppUsage - public abstract @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app); + public abstract List<VolumeInfo> getPackageCandidateVolumes( + @NonNull ApplicationInfo app); /** {@hide} */ - public abstract int movePrimaryStorage(VolumeInfo vol); + public abstract int movePrimaryStorage(@NonNull VolumeInfo vol); /** {@hide} */ public abstract @Nullable VolumeInfo getPrimaryStorageCurrentVolume(); /** {@hide} */ @@ -6407,6 +6513,7 @@ public abstract class PackageManager { * @return identity that uniquely identifies current device * @hide */ + @NonNull public abstract VerifierDeviceIdentity getVerifierDeviceIdentity(); /** @@ -6437,8 +6544,8 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public abstract void addCrossProfileIntentFilter(IntentFilter filter, int sourceUserId, - int targetUserId, int flags); + public abstract void addCrossProfileIntentFilter(@NonNull IntentFilter filter, + @UserIdInt int sourceUserId, @UserIdInt int targetUserId, int flags); /** * Clearing {@code CrossProfileIntentFilter}s which have the specified user @@ -6448,27 +6555,32 @@ public abstract class PackageManager { * @hide */ @UnsupportedAppUsage - public abstract void clearCrossProfileIntentFilters(int sourceUserId); + public abstract void clearCrossProfileIntentFilters(@UserIdInt int sourceUserId); /** * @hide */ + @NonNull @UnsupportedAppUsage - public abstract Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo); + public abstract Drawable loadItemIcon(@NonNull PackageItemInfo itemInfo, + @Nullable ApplicationInfo appInfo); /** * @hide */ + @NonNull @UnsupportedAppUsage - public abstract Drawable loadUnbadgedItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo); + public abstract Drawable loadUnbadgedItemIcon(@NonNull PackageItemInfo itemInfo, + @Nullable ApplicationInfo appInfo); /** {@hide} */ @UnsupportedAppUsage - public abstract boolean isPackageAvailable(String packageName); + public abstract boolean isPackageAvailable(@NonNull String packageName); /** {@hide} */ + @NonNull @UnsupportedAppUsage - public static String installStatusToString(int status, String msg) { + public static String installStatusToString(int status, @Nullable String msg) { final String str = installStatusToString(status); if (msg != null) { return str + ": " + msg; @@ -6478,6 +6590,7 @@ public abstract class PackageManager { } /** {@hide} */ + @NonNull @UnsupportedAppUsage public static String installStatusToString(int status) { switch (status) { @@ -6582,7 +6695,8 @@ public abstract class PackageManager { } /** {@hide} */ - public static String deleteStatusToString(int status, String msg) { + @NonNull + public static String deleteStatusToString(int status, @Nullable String msg) { final String str = deleteStatusToString(status); if (msg != null) { return str + ": " + msg; @@ -6592,6 +6706,7 @@ public abstract class PackageManager { } /** {@hide} */ + @NonNull @UnsupportedAppUsage public static String deleteStatusToString(int status) { switch (status) { @@ -6621,6 +6736,7 @@ public abstract class PackageManager { } /** {@hide} */ + @NonNull public static String permissionFlagToString(int flag) { switch (flag) { case FLAG_PERMISSION_GRANTED_BY_DEFAULT: return "GRANTED_BY_DEFAULT"; @@ -6668,8 +6784,8 @@ public abstract class PackageManager { * @hide */ @TestApi - public abstract @InstallReason int getInstallReason(String packageName, - @NonNull UserHandle user); + @InstallReason + public abstract int getInstallReason(@NonNull String packageName, @NonNull UserHandle user); /** * Checks whether the calling package is allowed to request package installs through package @@ -6695,6 +6811,7 @@ public abstract class PackageManager { * @see {@link android.content.Intent#ACTION_INSTANT_APP_RESOLVER_SETTINGS} * @hide */ + @Nullable @SystemApi public abstract ComponentName getInstantAppResolverSettingsComponent(); @@ -6705,6 +6822,7 @@ public abstract class PackageManager { * @see {@link android.content.Intent#ACTION_INSTALL_INSTANT_APP_PACKAGE} * @hide */ + @Nullable @SystemApi public abstract ComponentName getInstantAppInstallerComponent(); @@ -6714,7 +6832,9 @@ public abstract class PackageManager { * @see {@link android.provider.Settings.Secure#ANDROID_ID} * @hide */ - public abstract String getInstantAppAndroidId(String packageName, @NonNull UserHandle user); + @Nullable + public abstract String getInstantAppAndroidId(@NonNull String packageName, + @NonNull UserHandle user); /** * Callback use to notify the callers of module registration that the operation @@ -6757,7 +6877,7 @@ public abstract class PackageManager { * @hide */ @SystemApi - public abstract void registerDexModule(String dexModulePath, + public abstract void registerDexModule(@NonNull String dexModulePath, @Nullable DexModuleRegisterCallback callback); /** @@ -6837,8 +6957,8 @@ public abstract class PackageManager { * @param type representation of the {@code certificate} * @return true if this package was or is signed by exactly the certificate {@code certificate} */ - public boolean hasSigningCertificate( - String packageName, byte[] certificate, @CertificateInputType int type) { + public boolean hasSigningCertificate(@NonNull String packageName, @NonNull byte[] certificate, + @CertificateInputType int type) { throw new UnsupportedOperationException( "hasSigningCertificate not implemented in subclass"); } @@ -6862,7 +6982,7 @@ public abstract class PackageManager { * @return true if this package was or is signed by exactly the certificate {@code certificate} */ public boolean hasSigningCertificate( - int uid, byte[] certificate, @CertificateInputType int type) { + int uid, @NonNull byte[] certificate, @CertificateInputType int type) { throw new UnsupportedOperationException( "hasSigningCertificate not implemented in subclass"); } @@ -6872,16 +6992,28 @@ public abstract class PackageManager { * * @hide */ + @Nullable public String getSystemTextClassifierPackageName() { throw new UnsupportedOperationException( "getSystemTextClassifierPackageName not implemented in subclass"); } /** + * @return attention service package name, or null if there's none. + * + * @hide + */ + public String getAttentionServicePackageName() { + throw new UnsupportedOperationException( + "getAttentionServicePackageName not implemented in subclass"); + } + + /** * @return the wellbeing app package name, or null if it's not defined by the OEM. * * @hide */ + @Nullable @TestApi public String getWellbeingPackageName() { throw new UnsupportedOperationException( @@ -6893,6 +7025,7 @@ public abstract class PackageManager { * * @hide */ + @Nullable public String getAppPredictionServicePackageName() { throw new UnsupportedOperationException( "getAppPredictionServicePackageName not implemented in subclass"); @@ -6903,6 +7036,7 @@ public abstract class PackageManager { * * @hide */ + @Nullable public String getSystemCaptionsServicePackageName() { throw new UnsupportedOperationException( "getSystemCaptionsServicePackageName not implemented in subclass"); @@ -6928,7 +7062,7 @@ public abstract class PackageManager { * * @hide */ - public boolean isPackageStateProtected(String packageName, int userId) { + public boolean isPackageStateProtected(@NonNull String packageName, @UserIdInt int userId) { throw new UnsupportedOperationException( "isPackageStateProtected not implemented in subclass"); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 35d1eac5c0ad..0a01dcda8bbb 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -3689,6 +3689,12 @@ public class PackageParser { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE; } + if (sa.getBoolean( + R.styleable.AndroidManifestApplication_allowExternalStorageSandbox, + owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q)) { + ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ALLOW_EXTERNAL_STORAGE_SANDBOX; + } + ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0); ai.minAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_minAspectRatio, 0); diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 49b4cb01c6a6..514015fe0c86 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -1263,12 +1263,19 @@ public final class AssetManager implements AutoCloseable { */ @UnsupportedAppUsage public boolean isUpToDate() { - for (ApkAssets apkAssets : getApkAssets()) { - if (!apkAssets.isUpToDate()) { + synchronized (this) { + if (!mOpen) { return false; } + + for (ApkAssets apkAssets : mApkAssets) { + if (!apkAssets.isUpToDate()) { + return false; + } + } + + return true; } - return true; } /** diff --git a/core/java/android/database/TranslatingCursor.java b/core/java/android/database/TranslatingCursor.java index d9165b4c1008..35cbdc764819 100644 --- a/core/java/android/database/TranslatingCursor.java +++ b/core/java/android/database/TranslatingCursor.java @@ -22,6 +22,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.os.CancellationSignal; +import android.util.ArraySet; import com.android.internal.util.ArrayUtils; @@ -59,7 +60,7 @@ public class TranslatingCursor extends CrossProcessCursorWrapper { private final boolean mDropLast; private final int mAuxiliaryColumnIndex; - private final int[] mTranslateColumnIndices; + private final ArraySet<Integer> mTranslateColumnIndices; public TranslatingCursor(@NonNull Cursor cursor, @NonNull Config config, @NonNull Translator translator, boolean dropLast) { @@ -70,9 +71,12 @@ public class TranslatingCursor extends CrossProcessCursorWrapper { mDropLast = dropLast; mAuxiliaryColumnIndex = cursor.getColumnIndexOrThrow(config.auxiliaryColumn); - mTranslateColumnIndices = new int[config.translateColumns.length]; - for (int i = 0; i < mTranslateColumnIndices.length; ++i) { - mTranslateColumnIndices[i] = cursor.getColumnIndex(config.translateColumns[i]); + mTranslateColumnIndices = new ArraySet<>(); + for (int i = 0; i < cursor.getColumnCount(); ++i) { + String columnName = cursor.getColumnName(i); + if (ArrayUtils.contains(config.translateColumns, columnName)) { + mTranslateColumnIndices.add(i); + } } } diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java index bad80b89a6b0..014bc242e17a 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -39,6 +39,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; +import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -47,11 +48,15 @@ import java.util.regex.Pattern; */ public class SQLiteQueryBuilder { private static final String TAG = "SQLiteQueryBuilder"; + private static final Pattern sLimitPattern = Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?"); + private static final Pattern sAggregationPattern = Pattern.compile( + "(?i)(AVG|COUNT|MAX|MIN|SUM|TOTAL)\\((.+)\\)"); private Map<String, String> mProjectionMap = null; private List<Pattern> mProjectionGreylist = null; + private boolean mProjectionAggregationAllowed = false; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private String mTables = ""; @@ -203,6 +208,16 @@ public class SQLiteQueryBuilder { return mProjectionGreylist; } + /** {@hide} */ + public void setProjectionAggregationAllowed(boolean projectionAggregationAllowed) { + mProjectionAggregationAllowed = projectionAggregationAllowed; + } + + /** {@hide} */ + public boolean isProjectionAggregationAllowed() { + return mProjectionAggregationAllowed; + } + /** * Sets the cursor factory to be used for the query. You can use * one factory for all queries on a database but it is normally @@ -842,26 +857,48 @@ public class SQLiteQueryBuilder { return query.toString(); } + private static @NonNull String maybeWithOperator(@Nullable String operator, + @NonNull String column) { + if (operator != null) { + return operator + "(" + column + ")"; + } else { + return column; + } + } + + /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private String[] computeProjection(String[] projectionIn) { + public String[] computeProjection(String[] projectionIn) { if (projectionIn != null && projectionIn.length > 0) { if (mProjectionMap != null) { String[] projection = new String[projectionIn.length]; int length = projectionIn.length; for (int i = 0; i < length; i++) { + String operator = null; String userColumn = projectionIn[i]; String column = mProjectionMap.get(userColumn); + // If aggregation is allowed, extract the underlying column + // that may be aggregated + if (mProjectionAggregationAllowed) { + final Matcher matcher = sAggregationPattern.matcher(userColumn); + if (matcher.matches()) { + operator = matcher.group(1); + userColumn = matcher.group(2); + column = mProjectionMap.get(userColumn); + } + } + if (column != null) { - projection[i] = column; + projection[i] = maybeWithOperator(operator, column); continue; } if (!mStrict && ( userColumn.contains(" AS ") || userColumn.contains(" as "))) { /* A column alias already exist */ - projection[i] = userColumn; + projection[i] = maybeWithOperator(operator, userColumn); continue; } @@ -878,7 +915,7 @@ public class SQLiteQueryBuilder { if (match) { Log.w(TAG, "Allowing abusive custom column: " + userColumn); - projection[i] = userColumn; + projection[i] = maybeWithOperator(operator, userColumn); continue; } } @@ -911,7 +948,8 @@ public class SQLiteQueryBuilder { return null; } - private @Nullable String computeWhere(@Nullable String selection) { + /** {@hide} */ + public @Nullable String computeWhere(@Nullable String selection) { final boolean hasInternal = !TextUtils.isEmpty(mWhereClause); final boolean hasExternal = !TextUtils.isEmpty(selection); diff --git a/core/java/android/hardware/camera2/CaptureFailure.java b/core/java/android/hardware/camera2/CaptureFailure.java index cd2bc5f943e7..20ca4a338f01 100644 --- a/core/java/android/hardware/camera2/CaptureFailure.java +++ b/core/java/android/hardware/camera2/CaptureFailure.java @@ -17,6 +17,7 @@ package android.hardware.camera2; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -61,17 +62,19 @@ public class CaptureFailure { private final boolean mDropped; private final int mSequenceId; private final long mFrameNumber; + private final String mErrorPhysicalCameraId; /** * @hide */ public CaptureFailure(CaptureRequest request, int reason, - boolean dropped, int sequenceId, long frameNumber) { + boolean dropped, int sequenceId, long frameNumber, String errorPhysicalCameraId) { mRequest = request; mReason = reason; mDropped = dropped; mSequenceId = sequenceId; mFrameNumber = frameNumber; + mErrorPhysicalCameraId = errorPhysicalCameraId; } /** @@ -155,4 +158,17 @@ public class CaptureFailure { public int getSequenceId() { return mSequenceId; } + + /** + * The physical camera device ID in case the capture failure comes from a {@link CaptureRequest} + * with configured physical camera streams for a logical camera. + * + * @return String The physical camera device ID of the respective failing output. + * {@code null} in case the capture request has no associated physical camera device. + * @see CaptureRequest.Builder#setPhysicalCameraKey + * @see android.hardware.camera2.params.OutputConfiguration#setPhysicalCameraId + */ + public @Nullable String getPhysicalCameraId() { + return mErrorPhysicalCameraId; + } } diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 57b608f0fd84..fc12b090b2f3 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -2232,6 +2232,7 @@ public class CameraDeviceImpl extends CameraDevice final int requestId = resultExtras.getRequestId(); final int subsequenceId = resultExtras.getSubsequenceId(); final long frameNumber = resultExtras.getFrameNumber(); + final String errorPhysicalCameraId = resultExtras.getErrorPhysicalCameraId(); final CaptureCallbackHolder holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); @@ -2287,7 +2288,8 @@ public class CameraDeviceImpl extends CameraDevice reason, /*dropped*/ mayHaveBuffers, requestId, - frameNumber); + frameNumber, + errorPhysicalCameraId); failureDispatch = new Runnable() { @Override diff --git a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java index edc3d91eaf12..1ff5bd562f2e 100644 --- a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java +++ b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java @@ -29,6 +29,7 @@ public class CaptureResultExtras implements Parcelable { private long frameNumber; private int partialResultCount; private int errorStreamId; + private String errorPhysicalCameraId; public static final @android.annotation.NonNull Parcelable.Creator<CaptureResultExtras> CREATOR = new Parcelable.Creator<CaptureResultExtras>() { @@ -49,7 +50,8 @@ public class CaptureResultExtras implements Parcelable { public CaptureResultExtras(int requestId, int subsequenceId, int afTriggerId, int precaptureTriggerId, long frameNumber, - int partialResultCount, int errorStreamId) { + int partialResultCount, int errorStreamId, + String errorPhysicalCameraId) { this.requestId = requestId; this.subsequenceId = subsequenceId; this.afTriggerId = afTriggerId; @@ -57,6 +59,7 @@ public class CaptureResultExtras implements Parcelable { this.frameNumber = frameNumber; this.partialResultCount = partialResultCount; this.errorStreamId = errorStreamId; + this.errorPhysicalCameraId = errorPhysicalCameraId; } @Override @@ -73,6 +76,12 @@ public class CaptureResultExtras implements Parcelable { dest.writeLong(frameNumber); dest.writeInt(partialResultCount); dest.writeInt(errorStreamId); + if ((errorPhysicalCameraId != null) && !errorPhysicalCameraId.isEmpty()) { + dest.writeBoolean(true); + dest.writeString(errorPhysicalCameraId); + } else { + dest.writeBoolean(false); + } } public void readFromParcel(Parcel in) { @@ -83,6 +92,14 @@ public class CaptureResultExtras implements Parcelable { frameNumber = in.readLong(); partialResultCount = in.readInt(); errorStreamId = in.readInt(); + boolean errorPhysicalCameraIdPresent = in.readBoolean(); + if (errorPhysicalCameraIdPresent) { + errorPhysicalCameraId = in.readString(); + } + } + + public String getErrorPhysicalCameraId() { + return errorPhysicalCameraId; } public int getRequestId() { diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java index aff09f2a45f2..908ed0913ffc 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java +++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java @@ -109,11 +109,11 @@ public class LegacyCameraDevice implements AutoCloseable { } if (holder == null) { return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, - ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE); + ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, null); } return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(), /*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber(), - /*partialResultCount*/1, errorStreamId); + /*partialResultCount*/1, errorStreamId, null); } /** diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java index da0899be8cfa..690df1acc7e7 100644 --- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java +++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java @@ -333,6 +333,16 @@ public class RequestThreadManager { startPreview(); } + private void disconnectCallbackSurfaces() { + for (Surface s : mCallbackOutputs) { + try { + LegacyCameraDevice.disconnectSurface(s); + } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) { + Log.d(TAG, "Surface abandoned, skipping...", e); + } + } + } + private void configureOutputs(Collection<Pair<Surface, Size>> outputs) { if (DEBUG) { String outputsStr = outputs == null ? "null" : (outputs.size() + " surfaces"); @@ -370,14 +380,8 @@ public class RequestThreadManager { mGLThreadManager.waitUntilIdle(); } resetJpegSurfaceFormats(mCallbackOutputs); + disconnectCallbackSurfaces(); - for (Surface s : mCallbackOutputs) { - try { - LegacyCameraDevice.disconnectSurface(s); - } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) { - Log.w(TAG, "Surface abandoned, skipping...", e); - } - } mPreviewOutputs.clear(); mCallbackOutputs.clear(); mJpegSurfaceIds.clear(); @@ -972,11 +976,11 @@ public class RequestThreadManager { mGLThreadManager.quit(); mGLThreadManager = null; } + disconnectCallbackSurfaces(); if (mCamera != null) { mCamera.release(); mCamera = null; } - resetJpegSurfaceFormats(mCallbackOutputs); break; case RequestHandlerThread.MSG_POKE_IDLE_HANDLER: // OK: Ignore message. diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index ae93cf019776..4a64128f146b 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -1934,6 +1934,8 @@ public class ConnectivityManager { @NonNull Callback callback) { ParcelFileDescriptor dup; try { + // Dup is needed here as the pfd inside the socket is owned by the IpSecService, + // which cannot be obtained by the app process. dup = ParcelFileDescriptor.dup(socket.getFileDescriptor()); } catch (IOException ignored) { // Construct an invalid fd, so that if the user later calls start(), it will fail with @@ -1975,6 +1977,7 @@ public class ConnectivityManager { @NonNull Callback callback) { ParcelFileDescriptor dup; try { + // TODO: Consider remove unnecessary dup. dup = pfd.dup(); } catch (IOException ignored) { // Construct an invalid fd, so that if the user later calls start(), it will fail with diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java index 59802514c7a3..06c32c675a31 100644 --- a/core/java/android/net/DnsResolver.java +++ b/core/java/android/net/DnsResolver.java @@ -22,6 +22,10 @@ import static android.net.NetworkUtils.resNetworkResult; import static android.net.NetworkUtils.resNetworkSend; import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_DGRAM; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -30,12 +34,18 @@ import android.annotation.Nullable; import android.os.CancellationSignal; import android.os.Looper; import android.system.ErrnoException; +import android.system.Os; import android.util.Log; +import libcore.io.IoUtils; + import java.io.FileDescriptor; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; @@ -52,6 +62,7 @@ public final class DnsResolver { private static final String TAG = "DnsResolver"; private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; private static final int MAXPACKET = 8 * 1024; + private static final int SLEEP_TIME_MS = 2; @IntDef(prefix = { "CLASS_" }, value = { CLASS_IN @@ -188,9 +199,9 @@ public final class DnsResolver { * Send a raw DNS query. * The answer will be provided asynchronously through the provided {@link AnswerCallback}. * - * @param network {@link Network} specifying which network for querying. + * @param network {@link Network} specifying which network to query on. * {@code null} for query on default network. - * @param query blob message + * @param query blob message to query * @param flags flags as a combination of the FLAGS_* constants * @param executor The {@link Executor} that the callback should be executed on. * @param cancellationSignal used by the caller to signal if the query should be @@ -205,26 +216,32 @@ public final class DnsResolver { if (cancellationSignal != null && cancellationSignal.isCanceled()) { return; } + final Object lock = new Object(); final FileDescriptor queryfd; try { queryfd = resNetworkSend((network != null ? network.netId : NETID_UNSET), query, query.length, flags); } catch (ErrnoException e) { - callback.onQueryException(e); + executor.execute(() -> { + callback.onQueryException(e); + }); return; } - maybeAddCancellationSignal(cancellationSignal, queryfd); - registerFDListener(executor, queryfd, callback); + synchronized (lock) { + registerFDListener(executor, queryfd, callback, cancellationSignal, lock); + if (cancellationSignal == null) return; + addCancellationSignal(cancellationSignal, queryfd, lock); + } } /** * Send a DNS query with the specified name, class and query type. * The answer will be provided asynchronously through the provided {@link AnswerCallback}. * - * @param network {@link Network} specifying which network for querying. + * @param network {@link Network} specifying which network to query on. * {@code null} for query on default network. - * @param domain domain name for querying + * @param domain domain name to query * @param nsClass dns class as one of the CLASS_* constants * @param nsType dns resource record (RR) type as one of the TYPE_* constants * @param flags flags as a combination of the FLAGS_* constants @@ -242,40 +259,187 @@ public final class DnsResolver { if (cancellationSignal != null && cancellationSignal.isCanceled()) { return; } + final Object lock = new Object(); final FileDescriptor queryfd; try { queryfd = resNetworkQuery((network != null ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags); } catch (ErrnoException e) { - callback.onQueryException(e); + executor.execute(() -> { + callback.onQueryException(e); + }); + return; + } + synchronized (lock) { + registerFDListener(executor, queryfd, callback, cancellationSignal, lock); + if (cancellationSignal == null) return; + addCancellationSignal(cancellationSignal, queryfd, lock); + } + } + + private class InetAddressAnswerAccumulator extends InetAddressAnswerCallback { + private final List<InetAddress> mAllAnswers; + private ParseException mParseException; + private ErrnoException mErrnoException; + private final InetAddressAnswerCallback mUserCallback; + private final int mTargetAnswerCount; + private int mReceivedAnswerCount = 0; + + InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerCallback callback) { + mTargetAnswerCount = size; + mAllAnswers = new ArrayList<>(); + mUserCallback = callback; + } + + private boolean maybeReportException() { + if (mErrnoException != null) { + mUserCallback.onQueryException(mErrnoException); + return true; + } + if (mParseException != null) { + mUserCallback.onParseException(mParseException); + return true; + } + return false; + } + + private void maybeReportAnswer() { + if (++mReceivedAnswerCount != mTargetAnswerCount) return; + if (mAllAnswers.isEmpty() && maybeReportException()) return; + // TODO: Do RFC6724 sort. + mUserCallback.onAnswer(mAllAnswers); + } + + @Override + public void onAnswer(@NonNull List<InetAddress> answer) { + mAllAnswers.addAll(answer); + maybeReportAnswer(); + } + + @Override + public void onParseException(@NonNull ParseException e) { + mParseException = e; + maybeReportAnswer(); + } + + @Override + public void onQueryException(@NonNull ErrnoException e) { + mErrnoException = e; + maybeReportAnswer(); + } + } + + /** + * Send a DNS query with the specified name, get back a set of InetAddresses asynchronously. + * The answer will be provided asynchronously through the provided + * {@link InetAddressAnswerCallback}. + * + * @param network {@link Network} specifying which network to query on. + * {@code null} for query on default network. + * @param domain domain name to query + * @param flags flags as a combination of the FLAGS_* constants + * @param executor The {@link Executor} that the callback should be executed on. + * @param cancellationSignal used by the caller to signal if the query should be + * cancelled. May be {@code null}. + * @param callback an {@link InetAddressAnswerCallback} which will be called to notify the + * caller of the result of dns query. + */ + public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags, + @NonNull @CallbackExecutor Executor executor, + @Nullable CancellationSignal cancellationSignal, + @NonNull InetAddressAnswerCallback callback) { + if (cancellationSignal != null && cancellationSignal.isCanceled()) { return; } + final Object lock = new Object(); + final boolean queryIpv6 = haveIpv6(network); + final boolean queryIpv4 = haveIpv4(network); + + final FileDescriptor v4fd; + final FileDescriptor v6fd; + + int queryCount = 0; + + if (queryIpv6) { + try { + v6fd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags); + } catch (ErrnoException e) { + executor.execute(() -> { + callback.onQueryException(e); + }); + return; + } + queryCount++; + } else v6fd = null; + + // TODO: Use device flag to control the sleep time. + // Avoiding gateways drop packets if queries are sent too close together + try { + Thread.sleep(SLEEP_TIME_MS); + } catch (InterruptedException ex) { } + + if (queryIpv4) { + try { + v4fd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags); + } catch (ErrnoException e) { + if (queryIpv6) resNetworkCancel(v6fd); // Closes fd, marks it invalid. + executor.execute(() -> { + callback.onQueryException(e); + }); + return; + } + queryCount++; + } else v4fd = null; + + final InetAddressAnswerAccumulator accumulator = + new InetAddressAnswerAccumulator(queryCount, callback); - maybeAddCancellationSignal(cancellationSignal, queryfd); - registerFDListener(executor, queryfd, callback); + synchronized (lock) { + if (queryIpv6) { + registerFDListener(executor, v6fd, accumulator, cancellationSignal, lock); + } + if (queryIpv4) { + registerFDListener(executor, v4fd, accumulator, cancellationSignal, lock); + } + if (cancellationSignal == null) return; + cancellationSignal.setOnCancelListener(() -> { + synchronized (lock) { + if (queryIpv4) cancelQuery(v4fd); + if (queryIpv6) cancelQuery(v6fd); + } + }); + } } private <T> void registerFDListener(@NonNull Executor executor, - @NonNull FileDescriptor queryfd, @NonNull AnswerCallback<T> answerCallback) { + @NonNull FileDescriptor queryfd, @NonNull AnswerCallback<T> answerCallback, + @Nullable CancellationSignal cancellationSignal, @NonNull Object lock) { Looper.getMainLooper().getQueue().addOnFileDescriptorEventListener( queryfd, FD_EVENTS, (fd, events) -> { executor.execute(() -> { - byte[] answerbuf = null; - try { - answerbuf = resNetworkResult(fd); - } catch (ErrnoException e) { - Log.e(TAG, "resNetworkResult:" + e.toString()); - answerCallback.onQueryException(e); - return; - } - - try { - answerCallback.onAnswer( - answerCallback.parser.parse(answerbuf)); - } catch (ParseException e) { - answerCallback.onParseException(e); + synchronized (lock) { + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + return; + } + byte[] answerbuf = null; + try { + answerbuf = resNetworkResult(fd); // Closes fd, marks it invalid. + } catch (ErrnoException e) { + Log.e(TAG, "resNetworkResult:" + e.toString()); + answerCallback.onQueryException(e); + return; + } + + try { + answerCallback.onAnswer( + answerCallback.parser.parse(answerbuf)); + } catch (ParseException e) { + answerCallback.onParseException(e); + } } }); // Unregister this fd listener @@ -283,15 +447,51 @@ public final class DnsResolver { }); } - private void maybeAddCancellationSignal(@Nullable CancellationSignal cancellationSignal, - @NonNull FileDescriptor queryfd) { - if (cancellationSignal == null) return; - cancellationSignal.setOnCancelListener( - () -> { - Looper.getMainLooper().getQueue() - .removeOnFileDescriptorEventListener(queryfd); - resNetworkCancel(queryfd); - }); + private void cancelQuery(@NonNull FileDescriptor queryfd) { + if (!queryfd.valid()) return; + Looper.getMainLooper().getQueue().removeOnFileDescriptorEventListener(queryfd); + resNetworkCancel(queryfd); // Closes fd, marks it invalid. + } + + private void addCancellationSignal(@NonNull CancellationSignal cancellationSignal, + @NonNull FileDescriptor queryfd, @NonNull Object lock) { + cancellationSignal.setOnCancelListener(() -> { + synchronized (lock) { + cancelQuery(queryfd); + } + }); + } + + // These two functions match the behaviour of have_ipv4 and have_ipv6 in the native resolver. + private boolean haveIpv4(@Nullable Network network) { + final SocketAddress addrIpv4 = + new InetSocketAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 0); + return checkConnectivity(network, AF_INET, addrIpv4); + } + + private boolean haveIpv6(@Nullable Network network) { + final SocketAddress addrIpv6 = + new InetSocketAddress(InetAddresses.parseNumericAddress("2000::"), 0); + return checkConnectivity(network, AF_INET6, addrIpv6); + } + + private boolean checkConnectivity(@Nullable Network network, + int domain, @NonNull SocketAddress addr) { + final FileDescriptor socket; + try { + socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP); + } catch (ErrnoException e) { + return false; + } + try { + if (network != null) network.bindSocket(socket); + Os.connect(socket, addr); + } catch (IOException | ErrnoException e) { + return false; + } finally { + IoUtils.closeQuietly(socket); + } + return true; } private static class DnsAddressAnswer extends DnsPacket { diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java index 402bffdc2a97..8cfe6df678c7 100644 --- a/core/java/android/net/IpPrefix.java +++ b/core/java/android/net/IpPrefix.java @@ -16,6 +16,7 @@ package android.net; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -71,7 +72,7 @@ public final class IpPrefix implements Parcelable { * * @hide */ - public IpPrefix(@NonNull byte[] address, int prefixLength) { + public IpPrefix(@NonNull byte[] address, @IntRange(from = 0, to = 128) int prefixLength) { this.address = address.clone(); this.prefixLength = prefixLength; checkAndMaskAddressAndPrefixLength(); @@ -88,7 +89,7 @@ public final class IpPrefix implements Parcelable { */ @SystemApi @TestApi - public IpPrefix(@NonNull InetAddress address, int prefixLength) { + public IpPrefix(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength) { // We don't reuse the (byte[], int) constructor because it calls clone() on the byte array, // which is unnecessary because getAddress() already returns a clone. this.address = address.getAddress(); @@ -150,13 +151,13 @@ public final class IpPrefix implements Parcelable { * * @return the address in the form of a byte array. */ - public InetAddress getAddress() { + public @NonNull InetAddress getAddress() { try { return InetAddress.getByAddress(address); } catch (UnknownHostException e) { // Cannot happen. InetAddress.getByAddress can only throw an exception if the byte // array is the wrong length, but we check that in the constructor. - return null; + throw new IllegalArgumentException("Address is invalid"); } } @@ -166,7 +167,7 @@ public final class IpPrefix implements Parcelable { * * @return the address in the form of a byte array. */ - public byte[] getRawAddress() { + public @NonNull byte[] getRawAddress() { return address.clone(); } @@ -175,6 +176,7 @@ public final class IpPrefix implements Parcelable { * * @return the prefix length. */ + @IntRange(from = 0, to = 128) public int getPrefixLength() { return prefixLength; } @@ -183,10 +185,10 @@ public final class IpPrefix implements Parcelable { * Determines whether the prefix contains the specified address. * * @param address An {@link InetAddress} to test. - * @return {@code true} if the prefix covers the given address. + * @return {@code true} if the prefix covers the given address. {@code false} otherwise. */ - public boolean contains(InetAddress address) { - byte[] addrBytes = (address == null) ? null : address.getAddress(); + public boolean contains(@NonNull InetAddress address) { + byte[] addrBytes = address.getAddress(); if (addrBytes == null || addrBytes.length != this.address.length) { return false; } @@ -201,7 +203,7 @@ public final class IpPrefix implements Parcelable { * @param otherPrefix the prefix to test * @hide */ - public boolean containsPrefix(IpPrefix otherPrefix) { + public boolean containsPrefix(@NonNull IpPrefix otherPrefix) { if (otherPrefix.getPrefixLength() < prefixLength) return false; final byte[] otherAddress = otherPrefix.getRawAddress(); NetworkUtils.maskRawAddress(otherAddress, prefixLength); diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java index 333603f3a0f2..93dd2e4d7717 100644 --- a/core/java/android/net/LinkAddress.java +++ b/core/java/android/net/LinkAddress.java @@ -25,6 +25,7 @@ import static android.system.OsConstants.RT_SCOPE_LINK; import static android.system.OsConstants.RT_SCOPE_SITE; import static android.system.OsConstants.RT_SCOPE_UNIVERSE; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -170,7 +171,7 @@ public class LinkAddress implements Parcelable { * Constructs a new {@code LinkAddress} from an {@code InetAddress} and prefix length, with * the specified flags and scope. Flags and scope are not checked for validity. * @param address The IP address. - * @param prefixLength The prefix length. + * @param prefixLength The prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). * @param flags A bitmask of {@code IFA_F_*} values representing properties of the address. * @param scope An integer defining the scope in which the address is unique (e.g., * {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}). @@ -178,7 +179,8 @@ public class LinkAddress implements Parcelable { */ @SystemApi @TestApi - public LinkAddress(InetAddress address, int prefixLength, int flags, int scope) { + public LinkAddress(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength, + int flags, int scope) { init(address, prefixLength, flags, scope); } @@ -186,12 +188,13 @@ public class LinkAddress implements Parcelable { * Constructs a new {@code LinkAddress} from an {@code InetAddress} and a prefix length. * The flags are set to zero and the scope is determined from the address. * @param address The IP address. - * @param prefixLength The prefix length. + * @param prefixLength The prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). * @hide */ @SystemApi @TestApi - public LinkAddress(@NonNull InetAddress address, int prefixLength) { + public LinkAddress(@NonNull InetAddress address, + @IntRange(from = 0, to = 128) int prefixLength) { this(address, prefixLength, 0, 0); this.scope = scopeForUnicastAddress(address); } @@ -202,7 +205,7 @@ public class LinkAddress implements Parcelable { * @param interfaceAddress The interface address. * @hide */ - public LinkAddress(InterfaceAddress interfaceAddress) { + public LinkAddress(@NonNull InterfaceAddress interfaceAddress) { this(interfaceAddress.getAddress(), interfaceAddress.getNetworkPrefixLength()); } @@ -306,6 +309,7 @@ public class LinkAddress implements Parcelable { /** * Returns the prefix length of this {@code LinkAddress}. */ + @IntRange(from = 0, to = 128) public int getPrefixLength() { return prefixLength; } @@ -316,6 +320,7 @@ public class LinkAddress implements Parcelable { * @hide */ @UnsupportedAppUsage + @IntRange(from = 0, to = 128) public int getNetworkPrefixLength() { return getPrefixLength(); } diff --git a/core/java/android/net/NetworkCapabilities.aidl b/core/java/android/net/NetworkCapabilities.aidl index cd7d71cad978..01d328605de4 100644 --- a/core/java/android/net/NetworkCapabilities.aidl +++ b/core/java/android/net/NetworkCapabilities.aidl @@ -17,5 +17,5 @@ package android.net; -parcelable NetworkCapabilities; +@JavaOnlyStableParcelable parcelable NetworkCapabilities; diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index b0239c839348..52d3fc48a3a5 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -16,6 +16,8 @@ package android.net; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -24,6 +26,8 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -51,20 +55,32 @@ import java.util.Objects; * (IPv4 or IPv6). */ public final class RouteInfo implements Parcelable { + /** @hide */ + @IntDef(value = { + RTN_UNICAST, + RTN_UNREACHABLE, + RTN_THROW, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RouteType {} + /** * The IP destination address for this route. */ + @NonNull private final IpPrefix mDestination; /** * The gateway address for this route. */ @UnsupportedAppUsage + @Nullable private final InetAddress mGateway; /** * The interface for this route. */ + @Nullable private final String mInterface; @@ -108,13 +124,14 @@ public final class RouteInfo implements Parcelable { * @param destination the destination prefix * @param gateway the IP address to route packets through * @param iface the interface name to send packets on + * @param type the type of this route * * @hide */ @SystemApi @TestApi public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway, - @Nullable String iface, int type) { + @Nullable String iface, @RouteType int type) { switch (type) { case RTN_UNICAST: case RTN_UNREACHABLE: @@ -173,10 +190,24 @@ public final class RouteInfo implements Parcelable { } /** - * @hide + * Constructs a {@code RouteInfo} object. + * + * If destination is null, then gateway must be specified and the + * constructed route is either the IPv4 default route <code>0.0.0.0</code> + * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default + * route <code>::/0</code> if gateway is an instance of {@link Inet6Address}. + * <p> + * Destination and gateway may not both be null. + * + * @param destination the destination address and prefix in an {@link IpPrefix} + * @param gateway the {@link InetAddress} to route packets through + * @param iface the interface name to send packets on + * + * @hide */ @UnsupportedAppUsage - public RouteInfo(IpPrefix destination, InetAddress gateway, String iface) { + public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway, + @Nullable String iface) { this(destination, gateway, iface, RTN_UNICAST); } @@ -184,7 +215,8 @@ public final class RouteInfo implements Parcelable { * @hide */ @UnsupportedAppUsage - public RouteInfo(LinkAddress destination, InetAddress gateway, String iface) { + public RouteInfo(@Nullable LinkAddress destination, @Nullable InetAddress gateway, + @Nullable String iface) { this(destination == null ? null : new IpPrefix(destination.getAddress(), destination.getPrefixLength()), gateway, iface); @@ -205,7 +237,7 @@ public final class RouteInfo implements Parcelable { * * @hide */ - public RouteInfo(IpPrefix destination, InetAddress gateway) { + public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway) { this(destination, gateway, null); } @@ -215,7 +247,7 @@ public final class RouteInfo implements Parcelable { * TODO: Remove this. */ @UnsupportedAppUsage - public RouteInfo(LinkAddress destination, InetAddress gateway) { + public RouteInfo(@Nullable LinkAddress destination, @Nullable InetAddress gateway) { this(destination, gateway, null); } @@ -227,7 +259,7 @@ public final class RouteInfo implements Parcelable { * @hide */ @UnsupportedAppUsage - public RouteInfo(InetAddress gateway) { + public RouteInfo(@NonNull InetAddress gateway) { this((IpPrefix) null, gateway, null); } @@ -239,35 +271,36 @@ public final class RouteInfo implements Parcelable { * * @hide */ - public RouteInfo(IpPrefix destination) { + public RouteInfo(@NonNull IpPrefix destination) { this(destination, null, null); } /** * @hide */ - public RouteInfo(LinkAddress destination) { + public RouteInfo(@NonNull LinkAddress destination) { this(destination, null, null); } /** * @hide */ - public RouteInfo(IpPrefix destination, int type) { + public RouteInfo(@NonNull IpPrefix destination, @RouteType int type) { this(destination, null, null, type); } /** * @hide */ - public static RouteInfo makeHostRoute(InetAddress host, String iface) { + public static RouteInfo makeHostRoute(@NonNull InetAddress host, @Nullable String iface) { return makeHostRoute(host, null, iface); } /** * @hide */ - public static RouteInfo makeHostRoute(InetAddress host, InetAddress gateway, String iface) { + public static RouteInfo makeHostRoute(@Nullable InetAddress host, @Nullable InetAddress gateway, + @Nullable String iface) { if (host == null) return null; if (host instanceof Inet4Address) { @@ -290,6 +323,7 @@ public final class RouteInfo implements Parcelable { * * @return {@link IpPrefix} specifying the destination. This is never {@code null}. */ + @NonNull public IpPrefix getDestination() { return mDestination; } @@ -298,6 +332,7 @@ public final class RouteInfo implements Parcelable { * TODO: Convert callers to use IpPrefix and then remove. * @hide */ + @NonNull public LinkAddress getDestinationLinkAddress() { return new LinkAddress(mDestination.getAddress(), mDestination.getPrefixLength()); } @@ -308,6 +343,7 @@ public final class RouteInfo implements Parcelable { * @return {@link InetAddress} specifying the gateway or next hop. This may be * {@code null} for a directly-connected route." */ + @Nullable public InetAddress getGateway() { return mGateway; } @@ -317,6 +353,7 @@ public final class RouteInfo implements Parcelable { * * @return The name of the interface used for this route. */ + @Nullable public String getInterface() { return mInterface; } @@ -330,6 +367,7 @@ public final class RouteInfo implements Parcelable { */ @TestApi @SystemApi + @RouteType public int getType() { return mType; } @@ -401,6 +439,7 @@ public final class RouteInfo implements Parcelable { * @hide */ @UnsupportedAppUsage + @Nullable public static RouteInfo selectBestRoute(Collection<RouteInfo> routes, InetAddress dest) { if ((routes == null) || (dest == null)) return null; diff --git a/core/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java index 17a03c7c8933..4dd2ace59c62 100644 --- a/core/java/android/net/apf/ApfCapabilities.java +++ b/core/java/android/net/apf/ApfCapabilities.java @@ -19,14 +19,17 @@ package android.net.apf; import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.TestApi; -import android.content.Context; +import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.R; /** - * APF program support capabilities. + * APF program support capabilities. APF stands for Android Packet Filtering and it is a flexible + * way to drop unwanted network packets to save power. + * + * See documentation at hardware/google/apf/apf.h * * This class is immutable. * @hide @@ -104,10 +107,11 @@ public final class ApfCapabilities implements Parcelable { } /** - * Returns true if the APF interpreter advertises support for the data buffer access opcodes - * LDDW and STDW. + * Determines whether the APF interpreter advertises support for the data buffer access opcodes + * LDDW (LoaD Data Word) and STDW (STore Data Word). Full LDDW (LoaD Data Word) and + * STDW (STore Data Word) support is present from APFv4 on. * - * Full LDDW and STDW support is present from APFv4 on. + * @return {@code true} if the IWifiStaIface#readApfPacketFilterData is supported. */ public boolean hasDataAccess() { return apfVersionSupported >= 4; @@ -116,14 +120,14 @@ public final class ApfCapabilities implements Parcelable { /** * @return Whether the APF Filter in the device should filter out IEEE 802.3 Frames. */ - public static boolean getApfDrop8023Frames(@NonNull Context context) { - return context.getResources().getBoolean(R.bool.config_apfDrop802_3Frames); + public static boolean getApfDrop8023Frames() { + return Resources.getSystem().getBoolean(R.bool.config_apfDrop802_3Frames); } /** * @return An array of blacklisted EtherType, packets with EtherTypes within it will be dropped. */ - public static @NonNull int[] getApfEthTypeBlackList(@NonNull Context context) { - return context.getResources().getIntArray(R.array.config_apfEthTypeBlackList); + public static @NonNull int[] getApfEtherTypeBlackList() { + return Resources.getSystem().getIntArray(R.array.config_apfEthTypeBlackList); } } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 8970c625caa7..1be6c4bce36a 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -373,7 +373,8 @@ public final class NfcAdapter { * A callback to be invoked when the system successfully delivers your {@link NdefMessage} * to another device. * @see #setOnNdefPushCompleteCallback - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public interface OnNdefPushCompleteCallback { @@ -398,7 +399,8 @@ public final class NfcAdapter { * content currently visible to the user. Alternatively, you can call {@link * #setNdefPushMessage setNdefPushMessage()} if the {@link NdefMessage} always contains the * same data. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public interface CreateNdefMessageCallback { @@ -427,7 +429,8 @@ public final class NfcAdapter { /** - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public interface CreateBeamUrisCallback { @@ -981,7 +984,8 @@ public final class NfcAdapter { * @param uris an array of Uri(s) to push over Android Beam * @param activity activity for which the Uri(s) will be pushed * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setBeamPushUris(Uri[] uris, Activity activity) { @@ -1068,7 +1072,8 @@ public final class NfcAdapter { * @param callback callback, or null to disable * @param activity activity for which the Uri(s) will be pushed * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) { @@ -1157,7 +1162,8 @@ public final class NfcAdapter { * to only register one at a time, and to do so in that activity's * {@link Activity#onCreate} * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setNdefPushMessage(NdefMessage message, Activity activity, @@ -1275,7 +1281,8 @@ public final class NfcAdapter { * to only register one at a time, and to do so in that activity's * {@link Activity#onCreate} * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity, @@ -1361,7 +1368,8 @@ public final class NfcAdapter { * to only register one at a time, and to do so in that activity's * {@link Activity#onCreate} * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback, @@ -1577,7 +1585,8 @@ public final class NfcAdapter { * @param activity the current foreground Activity that has registered data to share * @return whether the Beam animation was successfully invoked * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated public boolean invokeBeam(Activity activity) { @@ -1822,7 +1831,8 @@ public final class NfcAdapter { * @see android.provider.Settings#ACTION_NFCSHARING_SETTINGS * @return true if NDEF Push feature is enabled * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. - * @deprecated this feature is deprecated. + * @deprecated this feature is deprecated. File sharing can work using other technology like + * Bluetooth. */ @java.lang.Deprecated diff --git a/core/java/android/os/BatterySaverPolicyConfig.java b/core/java/android/os/BatterySaverPolicyConfig.java index bda4e27bf542..879ab1ecb101 100644 --- a/core/java/android/os/BatterySaverPolicyConfig.java +++ b/core/java/android/os/BatterySaverPolicyConfig.java @@ -58,7 +58,8 @@ public final class BatterySaverPolicyConfig implements Parcelable { mAdvertiseIsEnabled = in.mAdvertiseIsEnabled; mDeferFullBackup = in.mDeferFullBackup; mDeferKeyValueBackup = in.mDeferKeyValueBackup; - mDeviceSpecificSettings = Collections.unmodifiableMap(in.mDeviceSpecificSettings); + mDeviceSpecificSettings = Collections.unmodifiableMap( + new ArrayMap<>(in.mDeviceSpecificSettings)); mDisableAnimation = in.mDisableAnimation; mDisableAod = in.mDisableAod; mDisableLaunchBoost = in.mDisableLaunchBoost; diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java index b92e71357503..b7cccc66294a 100644 --- a/core/java/android/os/DropBoxManager.java +++ b/core/java/android/os/DropBoxManager.java @@ -69,7 +69,8 @@ public class DropBoxManager { /** * Broadcast Action: This is broadcast when a new entry is added in the dropbox. * You must hold the {@link android.Manifest.permission#READ_LOGS} permission - * in order to receive this broadcast. + * in order to receive this broadcast. This broadcast can be rate limited for low priority + * entries * * <p class="note">This is a protected intent that can only be sent * by the system. diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index cceb6edc4c0a..f7e927e48863 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -20,6 +20,8 @@ import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; +import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.os.storage.StorageManager; @@ -1060,7 +1062,7 @@ public class Environment { * @throws IllegalArgumentException if the path is not a valid storage * device. */ - public static boolean isExternalStorageRemovable(File path) { + public static boolean isExternalStorageRemovable(@NonNull File path) { final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId()); if (volume != null) { return volume.isRemovable(); @@ -1103,7 +1105,7 @@ public class Environment { * @throws IllegalArgumentException if the path is not a valid storage * device. */ - public static boolean isExternalStorageEmulated(File path) { + public static boolean isExternalStorageEmulated(@NonNull File path) { final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId()); if (volume != null) { return volume.isEmulated(); @@ -1112,6 +1114,44 @@ public class Environment { } } + /** + * Returns whether the shared/external storage media at the given path is a + * sandboxed view that only contains files owned by the app. + * <p> + * This value may be different from the value requested by + * {@code allowExternalStorageSandbox} in the app's manifest, since an app + * may inherit its sandboxed state based on when it was first installed. + * <p> + * Sandboxed apps can continue to discover and read media belonging to other + * apps via {@link android.provider.MediaStore}. + */ + public static boolean isExternalStorageSandboxed() { + final File externalDir = sCurrentUser.getExternalDirs()[0]; + return isExternalStorageSandboxed(externalDir); + } + + /** + * Returns whether the shared/external storage media at the given path is a + * sandboxed view that only contains files owned by the app. + * <p> + * This value may be different from the value requested by + * {@code allowExternalStorageSandbox} in the app's manifest, since an app + * may inherit its sandboxed state based on when it was first installed. + * <p> + * Sandboxed apps can continue to discover and read media belonging to other + * apps via {@link android.provider.MediaStore}. + * + * @throws IllegalArgumentException if the path is not a valid storage + * device. + */ + public static boolean isExternalStorageSandboxed(@NonNull File path) { + final Context context = AppGlobals.getInitialApplication(); + final AppOpsManager appOps = context.getSystemService(AppOpsManager.class); + return appOps.noteOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE, + context.getApplicationInfo().uid, + context.getPackageName()) != AppOpsManager.MODE_ALLOWED; + } + static File getDirectory(String variableName, String defaultPath) { String path = System.getenv(variableName); return path == null ? new File(defaultPath) : new File(path); diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index 6536fc991b30..03e8c154e3fc 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -330,12 +330,6 @@ interface INetworkManagementService */ void removeIdleTimer(String iface); - /** - * Configure name servers, search paths, and resolver parameters for the given network. - */ - void setDnsConfigurationForNetwork(int netId, in String[] servers, in String[] domains, - in int[] params, String tlsHostname, in String[] tlsServers); - void setFirewallEnabled(boolean enabled); boolean isFirewallEnabled(); void setFirewallInterfaceRule(String iface, boolean allow); @@ -381,11 +375,6 @@ interface INetworkManagementService void createVirtualNetwork(int netId, boolean secure); /** - * Remove a network. - */ - void removeNetwork(int netId); - - /** * Add an interface to a network. */ void addInterfaceToNetwork(String iface, int netId); diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl index 6d4c5a034b54..311c86d08211 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -217,4 +217,9 @@ interface IStatsManager { */ oneway void sendBinaryPushStateChangedAtom(in String trainName, in long trainVersionCode, in int options, in int state, in long[] experimentId); + + /** + * Returns the most recently registered experiment IDs. + */ + long[] getRegisteredExperimentIds(); } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 64e2f890ee47..7d61bf6f3986 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -773,8 +773,10 @@ public final class PowerManager { */ public static final int LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF = 4; - static final int MIN_LOCATION_MODE = LOCATION_MODE_NO_CHANGE; - static final int MAX_LOCATION_MODE = LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF; + /** @hide */ + public static final int MIN_LOCATION_MODE = LOCATION_MODE_NO_CHANGE; + /** @hide */ + public static final int MAX_LOCATION_MODE = LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF; /** * @hide diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 728d77ef941e..5631282b3bd3 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -299,27 +299,6 @@ public final class DeviceConfig { "device_identifier_access_restrictions_disabled"; } - /** - * Telephony related properties definitions. - * - * @hide - */ - public interface Telephony { - String NAMESPACE = "telephony"; - /** - * Ringer ramping time in milliseconds. - */ - String RAMPING_RINGER_DURATION = "ramping_ringer_duration"; - /** - * Whether to apply ramping ringer on incoming phone calls. - */ - String RAMPING_RINGER_ENABLED = "ramping_ringer_enabled"; - /** - * Vibration time in milliseconds before ramping ringer starts. - */ - String RAMPING_RINGER_VIBRATION_DURATION = "ramping_ringer_vibration_duration"; - } - private static final Object sLock = new Object(); @GuardedBy("sLock") private static ArrayMap<OnPropertyChangedListener, Pair<String, Executor>> sSingleListeners = diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 7feeeee0f790..bda6ed19d3c1 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -31,6 +31,7 @@ import android.annotation.UnsupportedAppUsage; import android.app.Activity; import android.app.AppGlobals; import android.content.ClipData; +import android.content.ContentInterface; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentUris; @@ -884,9 +885,7 @@ public final class MediaStore { * access this path. Instead of trying to open this path * directly, apps should use * {@link ContentResolver#openFileDescriptor(Uri, String)} - * to gain access. This value will always be {@code NULL} - * for apps targeting - * {@link android.os.Build.VERSION_CODES#Q} or higher. + * to gain access. */ @Deprecated @Column(Cursor.FIELD_TYPE_STRING) @@ -1714,11 +1713,9 @@ public final class MediaStore { url = cr.insert(EXTERNAL_CONTENT_URI, values); if (source != null) { - OutputStream imageOut = cr.openOutputStream(url); - try { - source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut); - } finally { - imageOut.close(); + try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream( + cr.openFile(url, "w", null))) { + source.compress(Bitmap.CompressFormat.JPEG, 50, out); } long id = ContentUris.parseId(url); @@ -1959,9 +1956,7 @@ public final class MediaStore { * access this path. Instead of trying to open this path * directly, apps should use * {@link ContentResolver#loadThumbnail} - * to gain access. This value will always be - * {@code NULL} for apps targeting - * {@link android.os.Build.VERSION_CODES#Q} or higher. + * to gain access. */ @Deprecated @Column(Cursor.FIELD_TYPE_STRING) @@ -2442,9 +2437,7 @@ public final class MediaStore { * access this path. Instead of trying to open this path * directly, apps should use * {@link ContentResolver#openFileDescriptor(Uri, String)} - * to gain access. This value will always be - * {@code NULL} for apps targeting - * {@link android.os.Build.VERSION_CODES#Q} or higher. + * to gain access. */ @Deprecated @Column(Cursor.FIELD_TYPE_STRING) @@ -2734,9 +2727,7 @@ public final class MediaStore { * access this path. Instead of trying to open this path * directly, apps should use * {@link ContentResolver#loadThumbnail} - * to gain access. This value will always be - * {@code NULL} for apps targeting - * {@link android.os.Build.VERSION_CODES#Q} or higher. + * to gain access. */ @Deprecated @Column(Cursor.FIELD_TYPE_STRING) @@ -2823,9 +2814,7 @@ public final class MediaStore { * access this path. Instead of trying to open this path * directly, apps should use * {@link ContentResolver#loadThumbnail} - * to gain access. This value will always be - * {@code NULL} for apps targeting - * {@link android.os.Build.VERSION_CODES#Q} or higher. + * to gain access. */ @Deprecated @Column(Cursor.FIELD_TYPE_STRING) @@ -3193,9 +3182,7 @@ public final class MediaStore { * access this path. Instead of trying to open this path * directly, apps should use * {@link ContentResolver#openFileDescriptor(Uri, String)} - * to gain access. This value will always be - * {@code NULL} for apps targeting - * {@link android.os.Build.VERSION_CODES#Q} or higher. + * to gain access. */ @Deprecated @Column(Cursor.FIELD_TYPE_STRING) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 0abefb2fdae1..a97db101dc93 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14717,7 +14717,6 @@ public final class Settings { * * @hide */ - // TODO(b/117663715): require a new write permission restricted to a single source @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG) static void resetToDefaults(@NonNull ContentResolver resolver, @ResetMode int resetMode, @Nullable String prefix) { diff --git a/core/java/android/service/carrier/CarrierIdentifier.java b/core/java/android/service/carrier/CarrierIdentifier.java index 6629233ef590..af5bf7475f95 100644 --- a/core/java/android/service/carrier/CarrierIdentifier.java +++ b/core/java/android/service/carrier/CarrierIdentifier.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.Rlog; import android.telephony.TelephonyManager; import com.android.internal.telephony.uicc.IccUtils; @@ -223,7 +224,7 @@ public class CarrierIdentifier implements Parcelable { + "mcc=" + mMcc + ",mnc=" + mMnc + ",spn=" + mSpn - + ",imsi=" + mImsi + + ",imsi=" + Rlog.pii(false, mImsi) + ",gid1=" + mGid1 + ",gid2=" + mGid2 + ",carrierid=" + mCarrierId diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index 7a35b9e8fa74..dc57a1591913 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -17,6 +17,8 @@ package android.service.contentcapture; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; +import static android.view.contentcapture.ContentCaptureHelper.toList; +import static android.view.contentcapture.ContentCaptureSession.NO_SESSION_ID; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -36,9 +38,9 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.service.autofill.AutofillService; -import android.util.ArrayMap; import android.util.Log; import android.util.Slog; +import android.util.SparseIntArray; import android.view.contentcapture.ContentCaptureCondition; import android.view.contentcapture.ContentCaptureContext; import android.view.contentcapture.ContentCaptureEvent; @@ -53,7 +55,6 @@ import com.android.internal.os.IResultReceiver; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -117,7 +118,7 @@ public abstract class ContentCaptureService extends Service { } @Override - public void onSessionStarted(ContentCaptureContext context, String sessionId, int uid, + public void onSessionStarted(ContentCaptureContext context, int sessionId, int uid, IResultReceiver clientReceiver, int initialState) { mHandler.sendMessage(obtainMessage(ContentCaptureService::handleOnCreateSession, ContentCaptureService.this, context, sessionId, uid, clientReceiver, @@ -125,14 +126,14 @@ public abstract class ContentCaptureService extends Service { } @Override - public void onActivitySnapshot(String sessionId, SnapshotData snapshotData) { + public void onActivitySnapshot(int sessionId, SnapshotData snapshotData) { mHandler.sendMessage( obtainMessage(ContentCaptureService::handleOnActivitySnapshot, ContentCaptureService.this, sessionId, snapshotData)); } @Override - public void onSessionFinished(String sessionId) { + public void onSessionFinished(int sessionId) { mHandler.sendMessage(obtainMessage(ContentCaptureService::handleFinishSession, ContentCaptureService.this, sessionId)); } @@ -171,7 +172,7 @@ public abstract class ContentCaptureService extends Service { * <p>This map is populated when an session is started, which is called by the system server * and can be trusted. Then subsequent calls made by the app are verified against this map. */ - private final ArrayMap<String, Integer> mSessionUids = new ArrayMap<>(); + private final SparseIntArray mSessionUids = new SparseIntArray(); @CallSuper @Override @@ -240,11 +241,17 @@ public abstract class ContentCaptureService extends Service { */ public final void setContentCaptureConditions(@NonNull String packageName, @Nullable Set<ContentCaptureCondition> conditions) { - // TODO(b/129267994): implement - } + final IContentCaptureServiceCallback callback = mCallback; + if (callback == null) { + Log.w(TAG, "setContentCaptureConditions(): no server callback"); + return; + } - private <T> ArrayList<T> toList(@Nullable Set<T> set) { - return set == null ? null : new ArrayList<T>(set); + try { + callback.setContentCaptureConditions(packageName, toList(conditions)); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } /** @@ -378,7 +385,7 @@ public abstract class ContentCaptureService extends Service { // so we don't need to create a temporary InteractionSessionId for each event. private void handleOnCreateSession(@NonNull ContentCaptureContext context, - @NonNull String sessionId, int uid, IResultReceiver clientReceiver, int initialState) { + int sessionId, int uid, IResultReceiver clientReceiver, int initialState) { mSessionUids.put(sessionId, uid); onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId)); @@ -403,27 +410,27 @@ public abstract class ContentCaptureService extends Service { // Most events belong to the same session, so we can keep a reference to the last one // to avoid creating too many ContentCaptureSessionId objects - String lastSessionId = null; + int lastSessionId = NO_SESSION_ID; ContentCaptureSessionId sessionId = null; final List<ContentCaptureEvent> events = parceledEvents.getList(); for (int i = 0; i < events.size(); i++) { final ContentCaptureEvent event = events.get(i); if (!handleIsRightCallerFor(event, uid)) continue; - String sessionIdString = event.getSessionId(); - if (!sessionIdString.equals(lastSessionId)) { - sessionId = new ContentCaptureSessionId(sessionIdString); - lastSessionId = sessionIdString; + int sessionIdInt = event.getSessionId(); + if (sessionIdInt != lastSessionId) { + sessionId = new ContentCaptureSessionId(sessionIdInt); + lastSessionId = sessionIdInt; } switch (event.getType()) { case ContentCaptureEvent.TYPE_SESSION_STARTED: final ContentCaptureContext clientContext = event.getContentCaptureContext(); clientContext.setParentSessionId(event.getParentSessionId()); - mSessionUids.put(sessionIdString, uid); + mSessionUids.put(sessionIdInt, uid); onCreateContentCaptureSession(clientContext, sessionId); break; case ContentCaptureEvent.TYPE_SESSION_FINISHED: - mSessionUids.remove(sessionIdString); + mSessionUids.delete(sessionIdInt); onDestroyContentCaptureSession(sessionId); break; default: @@ -432,13 +439,12 @@ public abstract class ContentCaptureService extends Service { } } - private void handleOnActivitySnapshot(@NonNull String sessionId, - @NonNull SnapshotData snapshotData) { + private void handleOnActivitySnapshot(int sessionId, @NonNull SnapshotData snapshotData) { onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData); } - private void handleFinishSession(@NonNull String sessionId) { - mSessionUids.remove(sessionId); + private void handleFinishSession(int sessionId) { + mSessionUids.delete(sessionId); onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId)); } @@ -454,7 +460,7 @@ public abstract class ContentCaptureService extends Service { * Checks if the given {@code uid} owns the session associated with the event. */ private boolean handleIsRightCallerFor(@NonNull ContentCaptureEvent event, int uid) { - final String sessionId; + final int sessionId; switch (event.getType()) { case ContentCaptureEvent.TYPE_SESSION_STARTED: case ContentCaptureEvent.TYPE_SESSION_FINISHED: @@ -463,8 +469,7 @@ public abstract class ContentCaptureService extends Service { default: sessionId = event.getSessionId(); } - final Integer rightUid = mSessionUids.get(sessionId); - if (rightUid == null) { + if (mSessionUids.indexOfKey(sessionId) < 0) { if (sVerbose) { Log.v(TAG, "handleIsRightCallerFor(" + event + "): no session for " + sessionId + ": " + mSessionUids); @@ -472,6 +477,7 @@ public abstract class ContentCaptureService extends Service { // Just ignore, as the session could have been finished already return false; } + final int rightUid = mSessionUids.get(sessionId); if (rightUid != uid) { Log.e(TAG, "invalid call from UID " + uid + ": session " + sessionId + " belongs to " + rightUid); diff --git a/core/java/android/service/contentcapture/IContentCaptureService.aidl b/core/java/android/service/contentcapture/IContentCaptureService.aidl index 6be7a80653e9..03e1b7857837 100644 --- a/core/java/android/service/contentcapture/IContentCaptureService.aidl +++ b/core/java/android/service/contentcapture/IContentCaptureService.aidl @@ -35,10 +35,10 @@ import java.util.List; oneway interface IContentCaptureService { void onConnected(IBinder callback, boolean verbose, boolean debug); void onDisconnected(); - void onSessionStarted(in ContentCaptureContext context, String sessionId, int uid, + void onSessionStarted(in ContentCaptureContext context, int sessionId, int uid, in IResultReceiver clientReceiver, int initialState); - void onSessionFinished(String sessionId); - void onActivitySnapshot(String sessionId, in SnapshotData snapshotData); + void onSessionFinished(int sessionId); + void onActivitySnapshot(int sessionId, in SnapshotData snapshotData); void onUserDataRemovalRequest(in UserDataRemovalRequest request); void onActivityEvent(in ActivityEvent event); } diff --git a/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl index 8bc8defede80..0550ad3ea20c 100644 --- a/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl +++ b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl @@ -17,6 +17,7 @@ package android.service.contentcapture; import android.content.ComponentName; +import android.view.contentcapture.ContentCaptureCondition; import java.util.List; @@ -27,5 +28,6 @@ import java.util.List; */ oneway interface IContentCaptureServiceCallback { void setContentCaptureWhitelist(in List<String> packages, in List<ComponentName> activities); + void setContentCaptureConditions(String packageName, in List<ContentCaptureCondition> conditions); void disableSelf(); } diff --git a/core/java/android/service/wallpaper/IWallpaperService.aidl b/core/java/android/service/wallpaper/IWallpaperService.aidl index 99a81f503a95..56e2486dd626 100644 --- a/core/java/android/service/wallpaper/IWallpaperService.aidl +++ b/core/java/android/service/wallpaper/IWallpaperService.aidl @@ -26,4 +26,5 @@ oneway interface IWallpaperService { void attach(IWallpaperConnection connection, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, in Rect padding, int displayId); + void detach(); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index e1762dffeef5..d645e3f746d7 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -72,6 +72,7 @@ import com.android.internal.view.BaseSurfaceHolder; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; /** @@ -1309,6 +1310,7 @@ public abstract class WallpaperService extends Service { final int mDisplayId; final DisplayManager mDisplayManager; final Display mDisplay; + private final AtomicBoolean mDetached = new AtomicBoolean(); Engine mEngine; @@ -1399,8 +1401,23 @@ public abstract class WallpaperService extends Service { mCaller.sendMessage(msg); } + public void detach() { + mDetached.set(true); + } + + private void doDetachEngine() { + mActiveEngines.remove(mEngine); + mEngine.detach(); + } + @Override public void executeMessage(Message message) { + if (mDetached.get()) { + if (mActiveEngines.contains(mEngine)) { + doDetachEngine(); + } + return; + } switch (message.what) { case DO_ATTACH: { try { @@ -1416,8 +1433,7 @@ public abstract class WallpaperService extends Service { return; } case DO_DETACH: { - mActiveEngines.remove(mEngine); - mEngine.detach(); + doDetachEngine(); return; } case DO_SET_DESIRED_SIZE: { @@ -1497,6 +1513,7 @@ public abstract class WallpaperService extends Service { */ class IWallpaperServiceWrapper extends IWallpaperService.Stub { private final WallpaperService mTarget; + private IWallpaperEngineWrapper mEngineWrapper; public IWallpaperServiceWrapper(WallpaperService context) { mTarget = context; @@ -1506,9 +1523,14 @@ public abstract class WallpaperService extends Service { public void attach(IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding, int displayId) { - new IWallpaperEngineWrapper(mTarget, conn, windowToken, + mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType, isPreview, reqWidth, reqHeight, padding, displayId); } + + @Override + public void detach() { + mEngineWrapper.detach(); + } } @Override diff --git a/core/java/android/speech/tts/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java index a7b280b7b992..2fab40472bd5 100644 --- a/core/java/android/speech/tts/TtsEngines.java +++ b/core/java/android/speech/tts/TtsEngines.java @@ -15,8 +15,10 @@ */ package android.speech.tts; -import org.xmlpull.v1.XmlPullParserException; +import static android.provider.Settings.Secure.getString; +import android.annotation.NonNull; +import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -27,10 +29,6 @@ import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; - -import static android.provider.Settings.Secure.getString; - -import android.annotation.UnsupportedAppUsage; import android.provider.Settings; import android.speech.tts.TextToSpeech.Engine; import android.speech.tts.TextToSpeech.EngineInfo; @@ -39,6 +37,8 @@ import android.util.AttributeSet; import android.util.Log; import android.util.Xml; +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -522,7 +522,8 @@ public class TtsEngines { * read back, will evaluate to {@link Locale#getDefault()}. */ @UnsupportedAppUsage - public synchronized void updateLocalePrefForEngine(String engineName, Locale newLocale) { + public synchronized void updateLocalePrefForEngine( + @NonNull String engineName, Locale newLocale) { final String prefList = Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE); if (DBG) { diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java index 077d12de4d59..d7baa1086b2f 100644 --- a/core/java/android/text/format/Formatter.java +++ b/core/java/android/text/format/Formatter.java @@ -92,10 +92,15 @@ public final class Formatter { * @return formatted string with the number */ public static String formatFileSize(@Nullable Context context, long sizeBytes) { + return formatFileSize(context, sizeBytes, FLAG_SI_UNITS); + } + + /** @hide */ + public static String formatFileSize(@Nullable Context context, long sizeBytes, int flags) { if (context == null) { return ""; } - final BytesResult res = formatBytes(context.getResources(), sizeBytes, FLAG_SI_UNITS); + final BytesResult res = formatBytes(context.getResources(), sizeBytes, flags); return bidiWrap(context, context.getString(com.android.internal.R.string.fileSizeSuffix, res.value, res.units)); } diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index 610f7edef451..715181f28076 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -512,8 +512,8 @@ public final class DisplayCutout { * @hide */ public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) { - if (isBoundsEmpty() - || insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) { + if (insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0 + || isBoundsEmpty()) { return this; } @@ -534,6 +534,12 @@ public final class DisplayCutout { safeInsets.right = atLeastZero(safeInsets.right - insetRight); } + // If we are not cutting off part of the cutout by insetting it on bottom/right, and we also + // don't move it around, we can avoid the allocation and copy of the instance. + if (insetLeft == 0 && insetTop == 0 && mSafeInsets.equals(safeInsets)) { + return this; + } + Rect[] bounds = mBounds.getRects(); for (int i = 0; i < bounds.length; ++i) { if (!bounds[i].equals(ZERO_RECT)) { diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index c794a69d3680..8fbbcf4b88c6 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -16,11 +16,20 @@ package android.view; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; + import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Build; import android.os.Handler; import android.os.Message; +import android.os.SystemClock; +import android.util.StatsLog; /** * Detects various gestures and events using the supplied {@link MotionEvent}s. @@ -251,8 +260,12 @@ public class GestureDetector { private boolean mAlwaysInTapRegion; private boolean mAlwaysInBiggerTapRegion; private boolean mIgnoreNextUpEvent; + // Whether a classification has been recorded by statsd for the current event stream. Reset on + // ACTION_DOWN. + private boolean mHasRecordedClassification; private MotionEvent mCurrentDownEvent; + private MotionEvent mCurrentMotionEvent; private MotionEvent mPreviousUpEvent; /** @@ -297,6 +310,7 @@ public class GestureDetector { break; case LONG_PRESS: + recordGestureClassification(msg.arg1); dispatchLongPress(); break; @@ -304,6 +318,8 @@ public class GestureDetector { // If the user's finger is still down, do not count it as a tap if (mDoubleTapListener != null) { if (!mStillDown) { + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP); mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); } else { mDeferConfirmSingleTap = true; @@ -501,6 +517,11 @@ public class GestureDetector { final int action = ev.getAction(); + if (mCurrentMotionEvent != null) { + mCurrentMotionEvent.recycle(); + } + mCurrentMotionEvent = MotionEvent.obtain(ev); + if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } @@ -569,6 +590,8 @@ public class GestureDetector { && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) { // This is a second tap mIsDoubleTapping = true; + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP); // Give a callback with the first tap of the double-tap handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent); // Give a callback with down event of the double-tap @@ -590,11 +613,17 @@ public class GestureDetector { mStillDown = true; mInLongPress = false; mDeferConfirmSingleTap = false; + mHasRecordedClassification = false; if (mIsLongpressEnabled) { mHandler.removeMessages(LONG_PRESS); - mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() - + ViewConfiguration.getLongPressTimeout()); + mHandler.sendMessageAtTime( + mHandler.obtainMessage( + LONG_PRESS, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS, + 0 /* arg2 */), + mCurrentDownEvent.getDownTime() + + ViewConfiguration.getLongPressTimeout()); } mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); @@ -613,6 +642,8 @@ public class GestureDetector { final float scrollY = mLastFocusY - focusY; if (mIsDoubleTapping) { // Give the move events of the double-tap + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP); handled |= mDoubleTapListener.onDoubleTapEvent(ev); } else if (mAlwaysInTapRegion) { final int deltaX = (int) (focusX - mDownFocusX); @@ -635,8 +666,12 @@ public class GestureDetector { // reschedule long press with a modified timeout. mHandler.removeMessages(LONG_PRESS); final long longPressTimeout = ViewConfiguration.getLongPressTimeout(); - mHandler.sendEmptyMessageAtTime(LONG_PRESS, ev.getDownTime() - + (long) (longPressTimeout * multiplier)); + mHandler.sendMessageAtTime( + mHandler.obtainMessage( + LONG_PRESS, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS, + 0 /* arg2 */), + ev.getDownTime() + (long) (longPressTimeout * multiplier)); } // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll // until the gesture is resolved. @@ -646,6 +681,8 @@ public class GestureDetector { } if (distance > slopSquare) { + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL); handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); mLastFocusX = focusX; mLastFocusY = focusY; @@ -659,6 +696,7 @@ public class GestureDetector { mAlwaysInBiggerTapRegion = false; } } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { + recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL); handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); mLastFocusX = focusX; mLastFocusY = focusY; @@ -667,7 +705,11 @@ public class GestureDetector { motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS; if (deepPress && hasPendingLongPress) { mHandler.removeMessages(LONG_PRESS); - mHandler.sendEmptyMessage(LONG_PRESS); + mHandler.sendMessage( + mHandler.obtainMessage( + LONG_PRESS, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS, + 0 /* arg2 */)); } break; @@ -676,11 +718,15 @@ public class GestureDetector { MotionEvent currentUpEvent = MotionEvent.obtain(ev); if (mIsDoubleTapping) { // Finally, give the up event of the double-tap + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP); handled |= mDoubleTapListener.onDoubleTapEvent(ev); } else if (mInLongPress) { mHandler.removeMessages(TAP); mInLongPress = false; } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) { + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP); handled = mListener.onSingleTapUp(ev); if (mDeferConfirmSingleTap && mDoubleTapListener != null) { mDoubleTapListener.onSingleTapConfirmed(ev); @@ -821,4 +867,26 @@ public class GestureDetector { mInLongPress = true; mListener.onLongPress(mCurrentDownEvent); } + + private void recordGestureClassification(int classification) { + if (mHasRecordedClassification + || classification + == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) { + // Only record the first classification for an event stream. + return; + } + if (mCurrentDownEvent == null || mCurrentMotionEvent == null) { + // If the complete event stream wasn't seen, don't record anything. + mHasRecordedClassification = true; + return; + } + StatsLog.write( + StatsLog.TOUCH_GESTURE_CLASSIFIED, + getClass().getName(), + classification, + (int) (SystemClock.uptimeMillis() - mCurrentMotionEvent.getDownTime()), + (float) Math.hypot(mCurrentMotionEvent.getRawX() - mCurrentDownEvent.getRawX(), + mCurrentMotionEvent.getRawY() - mCurrentDownEvent.getRawY())); + mHasRecordedClassification = true; + } } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 87efb3fbf6c0..d317df05cc6e 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -255,12 +255,11 @@ interface IWindowSession { void updatePointerIcon(IWindow window); /** - * Update a tap exclude region with a rectangular area identified by provided id in the window. - * Touches on this region will not switch focus to this window. Passing an empty rect will - * remove the area from the exclude region of this window. + * Update a tap exclude region identified by provided id in the window. Touches on this region + * will neither be dispatched to this window nor change the focus to this window. Passing an + * invalid region will remove the area from the exclude region of this window. */ - void updateTapExcludeRegion(IWindow window, int regionId, int left, int top, int width, - int height); + void updateTapExcludeRegion(IWindow window, int regionId, in Region region); /** * Called when the client has changed the local insets state, and now the server should reflect diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 13b0cc038fce..b76f2a175346 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -17,7 +17,10 @@ package android.view; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; +import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES; import static android.view.WindowInsets.Type.SIZE; +import static android.view.WindowInsets.Type.SYSTEM_GESTURES; import static android.view.WindowInsets.Type.indexOf; import android.annotation.IntDef; @@ -55,6 +58,12 @@ public class InsetsState implements Parcelable { TYPE_SIDE_BAR_1, TYPE_SIDE_BAR_2, TYPE_SIDE_BAR_3, + TYPE_TOP_GESTURES, + TYPE_BOTTOM_GESTURES, + TYPE_LEFT_GESTURES, + TYPE_RIGHT_GESTURES, + TYPE_TOP_TAPPABLE_ELEMENT, + TYPE_BOTTOM_TAPPABLE_ELEMENT, TYPE_IME }) public @interface InternalInsetType {} @@ -73,8 +82,16 @@ public class InsetsState implements Parcelable { public static final int TYPE_SIDE_BAR_2 = 2; public static final int TYPE_SIDE_BAR_3 = 3; + public static final int TYPE_TOP_GESTURES = 4; + public static final int TYPE_BOTTOM_GESTURES = 5; + public static final int TYPE_LEFT_GESTURES = 6; + public static final int TYPE_RIGHT_GESTURES = 7; + public static final int TYPE_TOP_TAPPABLE_ELEMENT = 8; + public static final int TYPE_BOTTOM_TAPPABLE_ELEMENT = 9; + /** Input method window. */ - public static final int TYPE_IME = 4; + public static final int TYPE_IME = 10; + static final int LAST_TYPE = TYPE_IME; // Derived types @@ -137,17 +154,6 @@ public class InsetsState implements Parcelable { && legacyContentInsets != null && legacyStableInsets != null) { WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets); WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets); - - // TODO: set system gesture insets based on actual system gesture area. - typeInsetsMap[Type.indexOf(Type.systemGestures())] = Insets.of(legacyContentInsets); - typeInsetsMap[Type.indexOf(Type.mandatorySystemGestures())] = - Insets.of(legacyContentInsets); - typeInsetsMap[Type.indexOf(Type.tappableElement())] = Insets.of(legacyContentInsets); - - typeMaxInsetsMap[Type.indexOf(Type.systemGestures())] = Insets.of(legacyStableInsets); - typeMaxInsetsMap[Type.indexOf(Type.mandatorySystemGestures())] = - Insets.of(legacyStableInsets); - typeMaxInsetsMap[Type.indexOf(Type.tappableElement())] = Insets.of(legacyStableInsets); } for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { InsetsSource source = mSources.get(type); @@ -159,7 +165,9 @@ public class InsetsState implements Parcelable { && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR); boolean skipIme = source.getType() == TYPE_IME && (legacySoftInputMode & LayoutParams.SOFT_INPUT_ADJUST_RESIZE) == 0; - if (skipSystemBars || skipIme) { + boolean skipLegacyTypes = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE + && (toPublicType(type) & Type.compatSystemInsets()) != 0; + if (skipSystemBars || skipIme || skipLegacyTypes) { typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible(); continue; } @@ -183,7 +191,25 @@ public class InsetsState implements Parcelable { @Nullable boolean[] typeVisibilityMap) { Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility); - int index = indexOf(toPublicType(source.getType())); + int type = toPublicType(source.getType()); + processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap, + insets, type); + + if (type == MANDATORY_SYSTEM_GESTURES) { + // Mandatory system gestures are also system gestures. + // TODO: find a way to express this more generally. One option would be to define + // Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the + // ability to set systemGestureInsets() independently from + // mandatorySystemGestureInsets() in the Builder. + processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap, + insets, SYSTEM_GESTURES); + } + } + + private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap, + @InsetSide @Nullable SparseIntArray typeSideMap, + @Nullable boolean[] typeVisibilityMap, Insets insets, int type) { + int index = indexOf(type); Insets existing = typeInsetsMap[index]; if (existing == null) { typeInsetsMap[index] = insets; @@ -300,6 +326,15 @@ public class InsetsState implements Parcelable { return Type.SIDE_BARS; case TYPE_IME: return Type.IME; + case TYPE_TOP_GESTURES: + case TYPE_BOTTOM_GESTURES: + return Type.MANDATORY_SYSTEM_GESTURES; + case TYPE_LEFT_GESTURES: + case TYPE_RIGHT_GESTURES: + return Type.SYSTEM_GESTURES; + case TYPE_TOP_TAPPABLE_ELEMENT: + case TYPE_BOTTOM_TAPPABLE_ELEMENT: + return Type.TAPPABLE_ELEMENT; default: throw new IllegalArgumentException("Unknown type: " + type); } @@ -336,10 +371,20 @@ public class InsetsState implements Parcelable { return "TYPE_SIDE_BAR_2"; case TYPE_SIDE_BAR_3: return "TYPE_SIDE_BAR_3"; - case TYPE_IME: - return "TYPE_IME"; + case TYPE_TOP_GESTURES: + return "TYPE_TOP_GESTURES"; + case TYPE_BOTTOM_GESTURES: + return "TYPE_BOTTOM_GESTURES"; + case TYPE_LEFT_GESTURES: + return "TYPE_LEFT_GESTURES"; + case TYPE_RIGHT_GESTURES: + return "TYPE_RIGHT_GESTURES"; + case TYPE_TOP_TAPPABLE_ELEMENT: + return "TYPE_TOP_TAPPABLE_ELEMENT"; + case TYPE_BOTTOM_TAPPABLE_ELEMENT: + return "TYPE_BOTTOM_TAPPABLE_ELEMENT"; default: - return "TYPE_UNKNOWN"; + return "TYPE_UNKNOWN_" + type; } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c34613ea2828..65fe87fa8ca0 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -17,6 +17,10 @@ package android.view; import static android.content.res.Resources.ID_NULL; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; @@ -86,7 +90,6 @@ import android.os.Trace; import android.sysprop.DisplayProperties; import android.text.InputType; import android.text.TextUtils; -import android.util.ArrayMap; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.LayoutDirection; @@ -97,6 +100,7 @@ import android.util.Property; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.StateSet; +import android.util.StatsLog; import android.util.SuperNotCalledException; import android.util.TypedValue; import android.view.AccessibilityIterators.CharacterTextSegmentIterator; @@ -4063,11 +4067,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, }, formatToHexString = true) /* @hide */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769414) public int mPrivateFlags; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768943) int mPrivateFlags2; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 129147060) int mPrivateFlags3; private int mPrivateFlags4; @@ -7956,6 +7960,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * View is not a pane. * * {@see AccessibilityNodeInfo#setPaneTitle(CharSequence)} + * + * @attr ref android.R.styleable#View_accessibilityPaneTitle */ public void setAccessibilityPaneTitle(@Nullable CharSequence accessibilityPaneTitle) { if (!TextUtils.equals(accessibilityPaneTitle, mAccessibilityPaneTitle)) { @@ -7971,6 +7977,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return The current pane title. * * {@see #setAccessibilityPaneTitle}. + * + * @attr ref android.R.styleable#View_accessibilityPaneTitle */ @InspectableProperty @Nullable @@ -8525,11 +8533,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Populates a {@link ViewStructure} for Content Capture. + * Populates a {@link ViewStructure} for content capture. * - * <p>This method is called after a view is that is eligible for Content Capture + * <p>This method is called after a view is that is eligible for content capture * (for example, if it {@link #isImportantForAutofill()}, an intelligence service is enabled for - * the user, and the activity rendering the view is enabled for Content Capture) is laid out and + * the user, and the activity rendering the view is enabled for content capture) is laid out and * is visible. * * <p>The populated structure is then passed to the service through @@ -8548,6 +8556,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@code childStructure.getAutofillId()} or * {@link ContentCaptureSession#newAutofillId(AutofillId, long)}. * + * <p>When the virtual view hierarchy represents a web page, you should also: + * + * <ul> + * <li>Call {@link ContentCaptureManager#getContentCaptureConditions()} to infer content + * capture events should be generate for that URL. + * <li>Create a new {@link ContentCaptureSession} child for every HTML element that + * renders a new URL (like an {@code IFRAME}) and use that session to notify events from + * that subtree. + * </ul> + * * <p><b>Note: </b>the following methods of the {@code structure} will be ignored: * <ul> * <li>{@link ViewStructure#setChildCount(int)} @@ -9264,11 +9282,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Hints the Android System whether this view is considered important for Content Capture, based + * Hints the Android System whether this view is considered important for content capture, based * on the value explicitly set by {@link #setImportantForContentCapture(int)} and heuristics * when it's {@link #IMPORTANT_FOR_CONTENT_CAPTURE_AUTO}. * - * @return whether the view is considered important for autofill. + * <p>See {@link ContentCaptureManager} for more info about content capture. + * + * @return whether the view is considered important for content capture. * * @see #setImportantForContentCapture(int) * @see #IMPORTANT_FOR_CONTENT_CAPTURE_AUTO @@ -9467,7 +9487,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Sets the (optional) {@link ContentCaptureSession} associated with this view. * * <p>This method should be called when you need to associate a {@link ContentCaptureContext} to - * the Content Capture events associated with this view or its view hierarchy (if it's a + * the content capture events associated with this view or its view hierarchy (if it's a * {@link ViewGroup}). * * <p>For example, if your activity is associated with a web domain, first you would need to @@ -9498,7 +9518,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Gets the session used to notify Content Capture events. + * Gets the session used to notify content capture events. * * @return session explicitly set by {@link #setContentCaptureSession(ContentCaptureSession)}, * inherited by ancestors, default session or {@code null} if content capture is disabled for @@ -9719,7 +9739,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Dispatches the initial Content Capture events for a view structure. + * Dispatches the initial content capture events for a view structure. * * @hide */ @@ -12101,6 +12121,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #setScreenReaderFocusable(boolean) * * @return Whether the view should be treated as a focusable unit by screen reader. + * + * @attr ref android.R.styleable#View_screenReaderFocusable */ @InspectableProperty public boolean isScreenReaderFocusable() { @@ -12119,6 +12141,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @param screenReaderFocusable Whether the view should be treated as a unit by screen reader * accessibility tools. + * + * @attr ref android.R.styleable#View_screenReaderFocusable */ public void setScreenReaderFocusable(boolean screenReaderFocusable) { updatePflags3AndNotifyA11yIfChanged(PFLAG3_SCREEN_READER_FOCUSABLE, screenReaderFocusable); @@ -13900,6 +13924,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Returns whether this view can receive pointer events. + * + * @return {@code true} if this view can receive pointer events. + * @hide + */ + protected boolean canReceivePointerEvents() { + return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null; + } + + /** * Filter the touch event to apply security policies. * * @param event The motion event to be filtered. @@ -14542,7 +14576,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (clickable) { setPressed(true, x, y); } - checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y); + checkForLongClick( + ViewConfiguration.getLongPressTimeout(), + x, + y, + // This is not a touch gesture -- do not classify it as one. + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION); return true; } } @@ -15283,7 +15322,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mHasPerformedLongPress = false; if (!clickable) { - checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y); + checkForLongClick( + ViewConfiguration.getLongPressTimeout(), + x, + y, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); break; } @@ -15307,7 +15350,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); - checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y); + checkForLongClick( + ViewConfiguration.getLongPressTimeout(), + x, + y, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } break; @@ -15344,7 +15391,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * ambiguousMultiplier); // Subtract the time already spent delay -= event.getEventTime() - event.getDownTime(); - checkForLongClick(delay, x, y); + checkForLongClick( + delay, + x, + y, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } touchSlop *= ambiguousMultiplier; } @@ -15366,7 +15417,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (deepPress && hasPendingLongPressCallback()) { // process the long click action immediately removeLongPressCallback(); - checkForLongClick(0 /* send immediately */, x, y); + checkForLongClick( + 0 /* send immediately */, + x, + y, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS); } break; @@ -17939,7 +17994,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final int scrollX = mScrollX; final int scrollY = mScrollY; invalidateInternal(dirty.left - scrollX, dirty.top - scrollY, - dirty.right - scrollX, dirty.bottom - scrollY, true, false); + dirty.right - scrollX, dirty.bottom - scrollY, true); } /** @@ -17965,7 +18020,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void invalidate(int l, int t, int r, int b) { final int scrollX = mScrollX; final int scrollY = mScrollY; - invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false); + invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true); } /** @@ -17995,11 +18050,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @UnsupportedAppUsage public void invalidate(boolean invalidateCache) { - invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); + invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache); } - void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, - boolean fullInvalidate) { + void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache) { if (mGhostView != null) { mGhostView.invalidate(true); return; @@ -18016,11 +18070,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED - || (fullInvalidate && isOpaque() != mLastIsOpaque)) { - if (fullInvalidate) { - mLastIsOpaque = isOpaque(); - mPrivateFlags &= ~PFLAG_DRAWN; - } + || isOpaque() != mLastIsOpaque) { + mLastIsOpaque = isOpaque(); + mPrivateFlags &= ~PFLAG_DRAWN; mPrivateFlags |= PFLAG_DIRTY; @@ -22565,12 +22617,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @Override public void invalidateDrawable(@NonNull Drawable drawable) { if (verifyDrawable(drawable)) { - final Rect dirty = drawable.getDirtyBounds(); - final int scrollX = mScrollX; - final int scrollY = mScrollY; - - invalidate(dirty.left + scrollX, dirty.top + scrollY, - dirty.right + scrollX, dirty.bottom + scrollY); + invalidate(); rebuildOutline(); } } @@ -26122,7 +26169,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - private void checkForLongClick(long delay, float x, float y) { + private void checkForLongClick(long delay, float x, float y, int classification) { if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) { mHasPerformedLongPress = false; @@ -26132,6 +26179,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPendingCheckForLongPress.setAnchor(x, y); mPendingCheckForLongPress.rememberWindowAttachCount(); mPendingCheckForLongPress.rememberPressedState(); + mPendingCheckForLongPress.setClassification(classification); postDelayed(mPendingCheckForLongPress, delay); } } @@ -27689,11 +27737,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private float mX; private float mY; private boolean mOriginalPressedState; + /** + * The classification of the long click being checked: one of the + * StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__* constants. + */ + private int mClassification; @Override public void run() { if ((mOriginalPressedState == isPressed()) && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { + recordGestureClassification(mClassification); if (performLongClick(mX, mY)) { mHasPerformedLongPress = true; } @@ -27712,6 +27766,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void rememberPressedState() { mOriginalPressedState = isPressed(); } + + public void setClassification(int classification) { + mClassification = classification; + } } private final class CheckForTap implements Runnable { @@ -27724,17 +27782,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setPressed(true, x, y); final long delay = ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout(); - checkForLongClick(delay, x, y); + checkForLongClick(delay, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } } private final class PerformClick implements Runnable { @Override public void run() { + recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP); performClickInternal(); } } + /** Records a classification for the current event stream. */ + private void recordGestureClassification(int classification) { + if (classification == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) { + return; + } + // To avoid negatively impacting View performance, the latency and displacement metrics + // are omitted. + StatsLog.write(StatsLog.TOUCH_GESTURE_CLASSIFIED, getClass().getName(), classification); + } + /** * This method returns a ViewPropertyAnimator object, which can be used to animate * specific properties on this View. @@ -28578,8 +28647,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * hierarchy is traversed: value is either the view itself for appearead events, or its * autofill id for disappeared. */ - // TODO(b/121197119): use SparseArray once session id becomes integer - ArrayMap<String, ArrayList<Object>> mContentCaptureEvents; + SparseArray<ArrayList<Object>> mContentCaptureEvents; /** * Cached reference to the {@link ContentCaptureManager}. @@ -28609,9 +28677,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @NonNull View view, boolean appeared) { if (mContentCaptureEvents == null) { // Most of the time there will be just one session, so intial capacity is 1 - mContentCaptureEvents = new ArrayMap<>(1); + mContentCaptureEvents = new SparseArray<>(1); } - String sessionId = session.getId(); + int sessionId = session.getId(); // TODO: life would be much easier if we provided a MultiMap implementation somwhere... ArrayList<Object> events = mContentCaptureEvents.get(sessionId); if (events == null) { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 4851476e3d70..937bd1b34e61 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2011,7 +2011,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); - if (!canViewReceivePointerEvents(child) + if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { continue; } @@ -2094,7 +2094,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); - if (!canViewReceivePointerEvents(child) + if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { continue; } @@ -2314,7 +2314,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); - if (!canViewReceivePointerEvents(child) + if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { continue; } @@ -2500,7 +2500,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); - if (!canViewReceivePointerEvents(child) + if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { continue; } @@ -2680,7 +2680,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager i = childrenCount - 1; } - if (!canViewReceivePointerEvents(child) + if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; @@ -2970,15 +2970,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } - /** - * Returns true if a child view can receive pointer events. - * @hide - */ - private static boolean canViewReceivePointerEvents(@NonNull View child) { - return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE - || child.getAnimation() != null; - } - private float[] getTempPoint() { if (mTempPoint == null) { mTempPoint = new float[2]; @@ -7199,6 +7190,46 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + /** + * @hide + */ + @Override + public void subtractObscuredTouchableRegion(Region touchableRegion, View view) { + final int childrenCount = mChildrenCount; + final ArrayList<View> preorderedList = buildTouchDispatchChildList(); + final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); + final View[] children = mChildren; + for (int i = childrenCount - 1; i >= 0; i--) { + final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); + final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); + if (child == view) { + // We've reached the target view. + break; + } + if (!child.canReceivePointerEvents()) { + // This child cannot be touched. Skip it. + continue; + } + applyOpToRegionByBounds(touchableRegion, child, Region.Op.DIFFERENCE); + } + + // The touchable region should not exceed the bounds of its container. + applyOpToRegionByBounds(touchableRegion, this, Region.Op.INTERSECT); + + final ViewParent parent = getParent(); + if (parent != null) { + parent.subtractObscuredTouchableRegion(touchableRegion, this); + } + } + + private static void applyOpToRegionByBounds(Region region, View view, Region.Op op) { + final int[] locationInWindow = new int[2]; + view.getLocationInWindow(locationInWindow); + final int x = locationInWindow[0]; + final int y = locationInWindow[1]; + region.op(x, y, x + view.getWidth(), y + view.getHeight(), op); + } + @Override public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { insets = super.dispatchApplyWindowInsets(insets); diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index 572e69b1a78c..feba7bb7f195 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -18,6 +18,7 @@ package android.view; import android.annotation.NonNull; import android.graphics.Rect; +import android.graphics.Region; import android.os.Bundle; import android.view.accessibility.AccessibilityEvent; @@ -660,4 +661,17 @@ public interface ViewParent { * @return true if the action was consumed by this ViewParent */ public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle arguments); + + /** + * Given a touchable region of a child, this method reduces region by the bounds of all views on + * top of the child for which {@link View#canReceivePointerEvents} returns {@code true}. This + * applies recursively for all views in the view hierarchy on top of this one. + * + * @param touchableRegion The touchable region we want to modify. + * @param view A child view of this ViewGroup which indicates the z-order of the touchable + * region. + * @hide + */ + default void subtractObscuredTouchableRegion(Region touchableRegion, View view) { + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 2880e7f13545..f3b7ad5e557c 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1455,6 +1455,8 @@ public final class ViewRootImpl implements ViewParent, @Override public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) { + // TODO: Re-enable after camera is fixed or consider targetSdk checking this + // checkThread(); if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) { mIsAnimating = true; } @@ -1915,16 +1917,10 @@ public final class ViewRootImpl implements ViewParent, } contentInsets = ensureInsetsNonNegative(contentInsets, "content"); stableInsets = ensureInsetsNonNegative(stableInsets, "stable"); - if (sNewInsetsMode != NEW_INSETS_MODE_NONE) { - mLastWindowInsets = mInsetsController.calculateInsets( - mContext.getResources().getConfiguration().isScreenRound(), - mAttachInfo.mAlwaysConsumeSystemBars, displayCutout, - contentInsets, stableInsets, mWindowAttributes.softInputMode); - } else { - mLastWindowInsets = new WindowInsets(contentInsets, stableInsets, - mContext.getResources().getConfiguration().isScreenRound(), - mAttachInfo.mAlwaysConsumeSystemBars, displayCutout); - } + mLastWindowInsets = mInsetsController.calculateInsets( + mContext.getResources().getConfiguration().isScreenRound(), + mAttachInfo.mAlwaysConsumeSystemBars, displayCutout, + contentInsets, stableInsets, mWindowAttributes.softInputMode); } return mLastWindowInsets; } @@ -1985,7 +1981,6 @@ public final class ViewRootImpl implements ViewParent, mIsInTraversal = true; mWillDrawSoon = true; boolean windowSizeMayChange = false; - boolean newSurface = false; boolean surfaceChanged = false; WindowManager.LayoutParams lp = mWindowAttributes; @@ -2386,13 +2381,7 @@ public final class ViewRootImpl implements ViewParent, if (!hadSurface) { if (mSurface.isValid()) { // If we are creating a new surface, then we need to - // completely redraw it. Also, when we get to the - // point of drawing it we will hold off and schedule - // a new traversal instead. This is so we can tell the - // window manager about all of the windows being displayed - // before actually drawing them, so it can display then - // all at once. - newSurface = true; + // completely redraw it. mFullRedrawNeeded = true; mPreviousTransparentRegion.setEmpty(); @@ -2777,7 +2766,7 @@ public final class ViewRootImpl implements ViewParent, boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; - if (!cancelDraw && !newSurface) { + if (!cancelDraw) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); @@ -2811,8 +2800,7 @@ public final class ViewRootImpl implements ViewParent, MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager .getMainContentCaptureSession(); for (int i = 0; i < mAttachInfo.mContentCaptureEvents.size(); i++) { - String sessionId = mAttachInfo.mContentCaptureEvents - .keyAt(i); + int sessionId = mAttachInfo.mContentCaptureEvents.keyAt(i); mainSession.notifyViewTreeEvent(sessionId, /* started= */ true); ArrayList<Object> events = mAttachInfo.mContentCaptureEvents .valueAt(i); @@ -2827,8 +2815,8 @@ public final class ViewRootImpl implements ViewParent, Log.w(mTag, "no content capture session on view: " + view); continue for_each_event; } - String actualId = session.getId().toString(); - if (!actualId.equals(sessionId)) { + int actualId = session.getId(); + if (actualId != sessionId) { Log.w(mTag, "content capture session mismatch for view (" + view + "): was " + sessionId + " before, it's " + actualId + " now"); continue for_each_event; @@ -3986,7 +3974,7 @@ public final class ViewRootImpl implements ViewParent, void systemGestureExclusionChanged() { final List<Rect> rectsForWindowManager = mGestureExclusionTracker.computeChangedRects(); - if (rectsForWindowManager != null) { + if (rectsForWindowManager != null && mView != null) { try { mWindowSession.reportSystemGestureExclusionChanged(mWindow, rectsForWindowManager); } catch (RemoteException e) { diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index ffa769a424a9..2d292ef7b25c 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -29,9 +29,6 @@ import static android.view.WindowInsets.Type.TOP_BAR; import static android.view.WindowInsets.Type.all; import static android.view.WindowInsets.Type.compatSystemInsets; import static android.view.WindowInsets.Type.indexOf; -import static android.view.WindowInsets.Type.mandatorySystemGestures; -import static android.view.WindowInsets.Type.systemGestures; -import static android.view.WindowInsets.Type.tappableElement; import android.annotation.IntDef; import android.annotation.IntRange; @@ -225,10 +222,6 @@ public final class WindowInsets { } Insets[] typeInsetMap = new Insets[SIZE]; assignCompatInsets(typeInsetMap, insets); - // TODO: set system gesture insets based on actual system gesture area. - typeInsetMap[indexOf(systemGestures())] = Insets.of(insets); - typeInsetMap[indexOf(mandatorySystemGestures())] = Insets.of(insets); - typeInsetMap[indexOf(tappableElement())] = Insets.of(insets); return typeInsetMap; } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 385d491f49ef..774a359e5d6c 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -1682,17 +1682,29 @@ public class AccessibilityNodeInfo implements Parcelable { } /** - * Gets the node bounds in parent coordinates. + * Gets the node bounds in the viewParent's coordinates. + * {@link #getParent()} does not represent the source's viewParent. + * Instead it represents the result of {@link View#getParentForAccessibility()}, + * which returns the closest ancestor where {@link View#isImportantForAccessibility()} is true. + * So this method is not reliable. * * @param outBounds The output node bounds. + * @deprecated Use {@link #getBoundsInScreen(Rect)} instead. + * */ + @Deprecated public void getBoundsInParent(Rect outBounds) { outBounds.set(mBoundsInParent.left, mBoundsInParent.top, mBoundsInParent.right, mBoundsInParent.bottom); } /** - * Sets the node bounds in parent coordinates. + * Sets the node bounds in the viewParent's coordinates. + * {@link #getParent()} does not represent the source's viewParent. + * Instead it represents the result of {@link View#getParentForAccessibility()}, + * which returns the closest ancestor where {@link View#isImportantForAccessibility()} is true. + * So this method is not reliable. + * * <p> * <strong>Note:</strong> Cannot be called from an * {@link android.accessibilityservice.AccessibilityService}. @@ -1702,7 +1714,9 @@ public class AccessibilityNodeInfo implements Parcelable { * @param bounds The node bounds. * * @throws IllegalStateException If called from an AccessibilityService. + * @deprecated Accessibility services should not care about these bounds. */ + @Deprecated public void setBoundsInParent(Rect bounds) { enforceNotSealed(); mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom); diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 77a0c4c5f4ee..6503a800acb7 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -2178,6 +2178,18 @@ public final class AutofillManager { boolean saveOnAllViewsInvisible, boolean saveOnFinish, @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) { synchronized (mLock) { + if (sVerbose) { + Log.v(TAG, "setTrackedViews(): sessionId=" + sessionId + + ", trackedIds=" + Arrays.toString(trackedIds) + + ", saveOnAllViewsInvisible=" + saveOnAllViewsInvisible + + ", saveOnFinish=" + saveOnFinish + + ", fillableIds=" + Arrays.toString(fillableIds) + + ", saveTrigerId=" + saveTriggerId + + ", mFillableIds=" + mFillableIds + + ", mEnabled=" + mEnabled + + ", mSessionId=" + mSessionId); + + } if (mEnabled && mSessionId == sessionId) { if (saveOnAllViewsInvisible) { mTrackedViews = new TrackedViews(trackedIds); @@ -2192,10 +2204,6 @@ public final class AutofillManager { for (AutofillId id : fillableIds) { mFillableIds.add(id); } - if (sVerbose) { - Log.v(TAG, "setTrackedViews(): fillableIds=" + Arrays.toString(fillableIds) - + ", mFillableIds" + mFillableIds); - } } if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) { diff --git a/core/java/android/view/autofill/AutofillManagerInternal.java b/core/java/android/view/autofill/AutofillManagerInternal.java index d5862bd2f942..3de1a03d98e5 100644 --- a/core/java/android/view/autofill/AutofillManagerInternal.java +++ b/core/java/android/view/autofill/AutofillManagerInternal.java @@ -33,7 +33,10 @@ public abstract class AutofillManagerInternal { public abstract void onBackKeyPressed(); /** - * Gets autofill options for a package + * Gets autofill options for a package. + * + * <p><b>NOTE: </b>this method is called by the {@code ActivityManager} service and hence cannot + * hold the main service lock. * * @param packageName The package for which to query. * @param versionCode The package version code. diff --git a/cmds/statsd/src/logd/LogListener.cpp b/core/java/android/view/contentcapture/ContentCaptureCondition.aidl index ddb26f9fe565..99f8894408b3 100644 --- a/cmds/statsd/src/logd/LogListener.cpp +++ b/core/java/android/view/contentcapture/ContentCaptureCondition.aidl @@ -1,11 +1,11 @@ -/* - * Copyright (C) 2017 The Android Open Source Project +/** + * Copyright (c) 2019, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,18 +14,6 @@ * limitations under the License. */ -#include "logd/LogListener.h" +package android.view.contentcapture; -namespace android { -namespace os { -namespace statsd { - -LogListener::LogListener() { -} - -LogListener::~LogListener() { -} - -} // namespace statsd -} // namespace os -} // namespace android +parcelable ContentCaptureCondition; diff --git a/core/java/android/view/contentcapture/ContentCaptureCondition.java b/core/java/android/view/contentcapture/ContentCaptureCondition.java index ed872578d069..cf171d738524 100644 --- a/core/java/android/view/contentcapture/ContentCaptureCondition.java +++ b/core/java/android/view/contentcapture/ContentCaptureCondition.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.content.LocusId; import android.os.Parcel; import android.os.Parcelable; +import android.util.DebugUtils; import com.android.internal.util.Preconditions; @@ -58,7 +59,6 @@ public final class ContentCaptureCondition implements Parcelable { public ContentCaptureCondition(@NonNull LocusId locusId, @Flags int flags) { this.mLocusId = Preconditions.checkNotNull(locusId); this.mFlags = flags; - // TODO(b/129267994): check flags, add test case for null and invalid flags } /** @@ -79,6 +79,42 @@ public final class ContentCaptureCondition implements Parcelable { } @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mFlags; + result = prime * result + ((mLocusId == null) ? 0 : mLocusId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + final ContentCaptureCondition other = (ContentCaptureCondition) obj; + if (mFlags != other.mFlags) return false; + if (mLocusId == null) { + if (other.mLocusId != null) return false; + } else { + if (!mLocusId.equals(other.mLocusId)) return false; + } + return true; + } + + @Override + public String toString() { + final StringBuilder string = new StringBuilder(mLocusId.toString()); // LocusID is PII safe + if (mFlags != 0) { + string + .append(" (") + .append(DebugUtils.flagsToString(ContentCaptureCondition.class, "FLAG_", mFlags)) + .append(')'); + } + return string.toString(); + } + + @Override public int describeContents() { return 0; } diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java index 5a27e94aa495..94e548fa0eeb 100644 --- a/core/java/android/view/contentcapture/ContentCaptureContext.java +++ b/core/java/android/view/contentcapture/ContentCaptureContext.java @@ -15,6 +15,8 @@ */ package android.view.contentcapture; +import static android.view.contentcapture.ContentCaptureSession.NO_SESSION_ID; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -35,9 +37,9 @@ import com.android.internal.util.Preconditions; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; - /** - * Context associated with a {@link ContentCaptureSession}. + * Context associated with a {@link ContentCaptureSession} - see {@link ContentCaptureManager} for + * more info. */ public final class ContentCaptureContext implements Parcelable { @@ -50,8 +52,7 @@ public final class ContentCaptureContext implements Parcelable { /** * Flag used to indicate that the app explicitly disabled content capture for the activity - * (using - * {@link android.view.contentcapture.ContentCaptureManager#setContentCaptureEnabled(boolean)}), + * (using {@link ContentCaptureManager#setContentCaptureEnabled(boolean)}), * in which case the service will just receive activity-level events. * * @hide @@ -107,7 +108,7 @@ public final class ContentCaptureContext implements Parcelable { private final int mDisplayId; // Fields below are set by the service upon "delivery" and are not marshalled in the parcel - private @Nullable String mParentSessionId; + private int mParentSessionId = NO_SESSION_ID; /** @hide */ public ContentCaptureContext(@Nullable ContentCaptureContext clientContext, @@ -198,11 +199,12 @@ public final class ContentCaptureContext implements Parcelable { @SystemApi @TestApi public @Nullable ContentCaptureSessionId getParentSessionId() { - return mParentSessionId == null ? null : new ContentCaptureSessionId(mParentSessionId); + return mParentSessionId == NO_SESSION_ID ? null + : new ContentCaptureSessionId(mParentSessionId); } /** @hide */ - public void setParentSessionId(@NonNull String parentSessionId) { + public void setParentSessionId(int parentSessionId) { mParentSessionId = parentSessionId; } @@ -260,9 +262,10 @@ public final class ContentCaptureContext implements Parcelable { * <li>A unique identifier of the application state (for example, a conversation between * 2 users in a chat app). * + * <p>See {@link ContentCaptureManager} for more info about the content capture context. + * * @param id id associated with this context. */ - // TODO(b/123577059): make sure this is well documented and understandable public Builder(@NonNull LocusId id) { mId = Preconditions.checkNotNull(id); } @@ -316,7 +319,7 @@ public final class ContentCaptureContext implements Parcelable { } pw.print(", taskId="); pw.print(mTaskId); pw.print(", displayId="); pw.print(mDisplayId); - if (mParentSessionId != null) { + if (mParentSessionId != NO_SESSION_ID) { pw.print(", parentId="); pw.print(mParentSessionId); } if (mFlags > 0) { @@ -348,7 +351,7 @@ public final class ContentCaptureContext implements Parcelable { builder.append(", hasExtras"); } } - if (mParentSessionId != null) { + if (mParentSessionId != NO_SESSION_ID) { builder.append(", parentId=").append(mParentSessionId); } return builder.append(']').toString(); diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java index 8188e0592b9d..bd386292f95d 100644 --- a/core/java/android/view/contentcapture/ContentCaptureEvent.java +++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java @@ -16,6 +16,7 @@ package android.view.contentcapture; import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString; +import static android.view.contentcapture.ContentCaptureSession.NO_SESSION_ID; import android.annotation.IntDef; import android.annotation.NonNull; @@ -126,25 +127,25 @@ public final class ContentCaptureEvent implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface EventType{} - private final @NonNull String mSessionId; + private final int mSessionId; private final int mType; private final long mEventTime; private @Nullable AutofillId mId; private @Nullable ArrayList<AutofillId> mIds; private @Nullable ViewNode mNode; private @Nullable CharSequence mText; - private @Nullable String mParentSessionId; + private int mParentSessionId = NO_SESSION_ID; private @Nullable ContentCaptureContext mClientContext; /** @hide */ - public ContentCaptureEvent(@NonNull String sessionId, int type, long eventTime) { + public ContentCaptureEvent(int sessionId, int type, long eventTime) { mSessionId = sessionId; mType = type; mEventTime = eventTime; } /** @hide */ - public ContentCaptureEvent(@NonNull String sessionId, int type) { + public ContentCaptureEvent(int sessionId, int type) { this(sessionId, type, System.currentTimeMillis()); } @@ -185,7 +186,7 @@ public final class ContentCaptureEvent implements Parcelable { * * @hide */ - public ContentCaptureEvent setParentSessionId(@NonNull String parentSessionId) { + public ContentCaptureEvent setParentSessionId(int parentSessionId) { mParentSessionId = parentSessionId; return this; } @@ -202,7 +203,7 @@ public final class ContentCaptureEvent implements Parcelable { /** @hide */ @NonNull - public String getSessionId() { + public int getSessionId() { return mSessionId; } @@ -212,7 +213,7 @@ public final class ContentCaptureEvent implements Parcelable { * @hide */ @Nullable - public String getParentSessionId() { + public int getParentSessionId() { return mParentSessionId; } @@ -357,10 +358,10 @@ public final class ContentCaptureEvent implements Parcelable { if (mNode != null) { pw.print(", mNode.id="); pw.print(mNode.getAutofillId()); } - if (mSessionId != null) { + if (mSessionId != NO_SESSION_ID) { pw.print(", sessionId="); pw.print(mSessionId); } - if (mParentSessionId != null) { + if (mParentSessionId != NO_SESSION_ID) { pw.print(", parentSessionId="); pw.print(mParentSessionId); } if (mText != null) { @@ -377,7 +378,7 @@ public final class ContentCaptureEvent implements Parcelable { final StringBuilder string = new StringBuilder("ContentCaptureEvent[type=") .append(getTypeAsString(mType)); string.append(", session=").append(mSessionId); - if (mType == TYPE_SESSION_STARTED && mParentSessionId != null) { + if (mType == TYPE_SESSION_STARTED && mParentSessionId != NO_SESSION_ID) { string.append(", parent=").append(mParentSessionId); } if (mId != null) { @@ -409,7 +410,7 @@ public final class ContentCaptureEvent implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeString(mSessionId); + parcel.writeInt(mSessionId); parcel.writeInt(mType); parcel.writeLong(mEventTime); parcel.writeParcelable(mId, flags); @@ -417,7 +418,7 @@ public final class ContentCaptureEvent implements Parcelable { ViewNode.writeToParcel(parcel, mNode, flags); parcel.writeCharSequence(mText); if (mType == TYPE_SESSION_STARTED || mType == TYPE_SESSION_FINISHED) { - parcel.writeString(mParentSessionId); + parcel.writeInt(mParentSessionId); } if (mType == TYPE_SESSION_STARTED || mType == TYPE_CONTEXT_UPDATED) { parcel.writeParcelable(mClientContext, flags); @@ -430,7 +431,7 @@ public final class ContentCaptureEvent implements Parcelable { @Override @NonNull public ContentCaptureEvent createFromParcel(Parcel parcel) { - final String sessionId = parcel.readString(); + final int sessionId = parcel.readInt(); final int type = parcel.readInt(); final long eventTime = parcel.readLong(); final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type, eventTime); @@ -448,7 +449,7 @@ public final class ContentCaptureEvent implements Parcelable { } event.setText(parcel.readCharSequence()); if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) { - event.setParentSessionId(parcel.readString()); + event.setParentSessionId(parcel.readInt()); } if (type == TYPE_SESSION_STARTED || type == TYPE_CONTEXT_UPDATED) { event.setClientContext(parcel.readParcelable(null)); diff --git a/core/java/android/view/contentcapture/ContentCaptureHelper.java b/core/java/android/view/contentcapture/ContentCaptureHelper.java index 6bc382907457..c7ca2209d387 100644 --- a/core/java/android/view/contentcapture/ContentCaptureHelper.java +++ b/core/java/android/view/contentcapture/ContentCaptureHelper.java @@ -23,9 +23,14 @@ import static android.view.contentcapture.ContentCaptureManager.LOGGING_LEVEL_VE import android.annotation.Nullable; import android.os.Build; import android.provider.DeviceConfig; +import android.util.ArraySet; import android.util.Log; import android.view.contentcapture.ContentCaptureManager.LoggingLevel; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + /** * Helper class for this package and server's. * @@ -101,6 +106,22 @@ public final class ContentCaptureHelper { } } + /** + * Converts a set to a list. + */ + @Nullable + public static <T> ArrayList<T> toList(@Nullable Set<T> set) { + return set == null ? null : new ArrayList<T>(set); + } + + /** + * Converts a list to a set. + */ + @Nullable + public static <T> ArraySet<T> toSet(@Nullable List<T> list) { + return list == null ? null : new ArraySet<T>(list); + } + private ContentCaptureHelper() { throw new UnsupportedOperationException("contains only static methods"); } diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 817b13011402..253935680cb8 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -17,6 +17,7 @@ package android.view.contentcapture; import static android.view.contentcapture.ContentCaptureHelper.sDebug; import static android.view.contentcapture.ContentCaptureHelper.sVerbose; +import static android.view.contentcapture.ContentCaptureHelper.toSet; import android.annotation.IntDef; import android.annotation.NonNull; @@ -28,12 +29,15 @@ import android.annotation.UiThread; import android.content.ComponentName; import android.content.ContentCaptureOptions; import android.content.Context; +import android.graphics.Canvas; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; +import android.view.View; +import android.view.ViewStructure; import android.view.contentcapture.ContentCaptureSession.FlushReason; import com.android.internal.annotations.GuardedBy; @@ -43,10 +47,152 @@ import com.android.internal.util.SyncResultReceiver; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Set; /** - * TODO(b/123577059): add javadocs / mention it can be null + * <p>The {@link ContentCaptureManager} provides additional ways for for apps to + * integrate with the content capture subsystem. + * + * <p>Content capture provides real-time, continuous capture of application activity, display and + * events to an intelligence service that is provided by the Android system. The intelligence + * service then uses that info to mediate and speed user journey through different apps. For + * example, when the user receives a restaurant address in a chat app and switchs to a map app + * to search for that restaurant, the intelligence service could offer an autofill dialog to + * let the user automatically select its address. + * + * <p>Content capture was designed with two major concerns in mind: privacy and performance. + * + * <ul> + * <li><b>Privacy:</b> the intelligence service is a trusted component provided that is provided + * by the device manufacturer and that cannot be changed by the user (although the user can + * globaly disable content capture using the Android Settings app). This service can only use the + * data for in-device machine learning, which is enforced both by process isolation and + * <a href="https://source.android.com/compatibility/cdd">CDD requirements</a>. + * <li><b>Performance:</b> content capture is highly optimized to minimize its impact in the app + * jankiness and overall device system health. For example, its only enabled on apps (or even + * specific activities from an app) that were explicitly whitelisted by the intelligence service, + * and it buffers the events so they are sent in a batch to the service (see + * {@link #isContentCaptureEnabled()} for other cases when its disabled). + * </ul> + * + * <p>In fact, before using this manager, the app developer should check if it's available. Example: + * <code> + * ContentCaptureManager mgr = context.getSystemService(ContentCaptureManager.class); + * if (mgr != null && mgr.isContentCaptureEnabled()) { + * // ... + * } + * </code> + * + * <p>App developers usually don't need to explicitly interact with content capture, except when the + * app: + * + * <ul> + * <li>Can define a contextual {@link android.content.LocusId} to identify unique state (such as a + * conversation between 2 chat users). + * <li>Can have multiple view hierarchies with different contextual meaning (for example, a + * browser app with multiple tabs, each representing a different URL). + * <li>Contains custom views (that extend View directly and are not provided by the standard + * Android SDK. + * <li>Contains views that provide their own virtual hierarchy (like a web browser that render the + * HTML elements using a Canvas). + * </ul> + * + * <p>The main integration point with content capture is the {@link ContentCaptureSession}. A "main" + * session is automatically created by the Android System when content capture is enabled for the + * activity and its used by the standard Android views to notify the content capture service of + * events such as views being added, views been removed, and text changed by user input. The session + * could have a {@link ContentCaptureContext} to provide more contextual info about it, such as + * the locus associated with the view hierarchy (see {@link android.content.LocusId} for more info + * about locus). By default, the main session doesn't have a {@code ContentCaptureContext}, but you + * can change it after its created. Example: + * + * <pre><code> + * protected void onCreate(Bundle savedInstanceState) { + * // Initialize view structure + * ContentCaptureSession session = rootView.getContentCaptureSession(); + * if (session != null) { + * session.setContentCaptureContext(ContentCaptureContext.forLocusId("chat_UserA_UserB")); + * } + * } + * </code></pre> + * + * <p>If your activity contains view hierarchies with a different contextual meaning, you should + * created child sessions for each view hierarchy root. For example, if your activity is a browser, + * you could use the main session for the main URL being rendered, then child sessions for each + * {@code IFRAME}: + * + * <pre><code> + * ContentCaptureSession mMainSession; + * + * protected void onCreate(Bundle savedInstanceState) { + * // Initialize view structure... + * mMainSession = rootView.getContentCaptureSession(); + * if (mMainSession != null) { + * mMainSession.setContentCaptureContext( + * ContentCaptureContext.forLocusId("https://example.com")); + * } + * } + * + * private void loadIFrame(View iframeRootView, String url) { + * if (mMainSession != null) { + * ContentCaptureSession iFrameSession = mMainSession.newChild( + * ContentCaptureContext.forLocusId(url)); + * } + * iframeRootView.setContentCaptureSession(iFrameSession); + * } + * // Load iframe... + * } + * </code></pre> + * + * <p>If your activity has custom views (i.e., views that extend {@link View} directly and provide + * just one logical view, not a virtual tree hiearchy) and it provides content that's relevant for + * content capture (as of {@link android.os.Build.VERSION_CODES#Q Android Q}, the only relevant + * content is text), then your view implementation should: + * + * <ul> + * <li>Set it as important for content capture. + * <li>Fill {@link ViewStructure} used for content capture. + * <li>Notify the {@link ContentCaptureSession} when the text is changed by user input. + * </ul> + * + * <p>Here's an example of the relevant methods for an {@code EditText}-like view: + * + * <pre><code> + * public class MyEditText extends View { + * + * public MyEditText(...) { + * if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) { + * setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES); + * } + * } + * + * public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) { + * super.onProvideContentCaptureStructure(structure, flags); + * + * structure.setText(getText(), getSelectionStart(), getSelectionEnd()); + * structure.setHint(getHint()); + * structure.setInputType(getInputType()); + * // set other properties like setTextIdEntry(), setTextLines(), setTextStyle(), + * // setMinTextEms(), setMaxTextEms(), setMaxTextLength() + * } + * + * private void onTextChanged() { + * if (isLaidOut() && isImportantForContentCapture() && isTextEditable()) { + * ContentCaptureManager mgr = mContext.getSystemService(ContentCaptureManager.class); + * if (cm != null && cm.isContentCaptureEnabled()) { + * ContentCaptureSession session = getContentCaptureSession(); + * if (session != null) { + * session.notifyViewTextChanged(getAutofillId(), getText()); + * } + * } + * } + * </code></pre> + * + * <p>If your view provides its own virtual hierarchy (for example, if it's a browser that draws + * the HTML using {@link Canvas} or native libraries in a different render process), then the view + * is also responsible to notify the session when the virtual elements appear and disappear - see + * {@link View#onProvideContentCaptureStructure(ViewStructure, int)} for more info. */ @SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE) public final class ContentCaptureManager { @@ -69,7 +215,7 @@ public final class ContentCaptureManager { /** * DeviceConfig property used by {@code com.android.server.SystemServer} on start to decide - * whether the Content Capture service should be created or not + * whether the content capture service should be created or not * * <p>By default it should *NOT* be set (or set to {@code "default"}, so the decision is based * on whether the OEM provides an implementation for the service), but it can be overridden to: @@ -340,12 +486,12 @@ public final class ContentCaptureManager { * <p>There are many reasons it could be disabled, such as: * <ul> * <li>App itself disabled content capture through {@link #setContentCaptureEnabled(boolean)}. - * <li>Service disabled content capture for this specific activity. - * <li>Service disabled content capture for all activities of this package. - * <li>Service disabled content capture globally. - * <li>User disabled content capture globally (through Settings). - * <li>OEM disabled content capture globally. - * <li>Transient errors. + * <li>Intelligence service did not whitelist content capture for this activity's package. + * <li>Intelligence service did not whitelist content capture for this specific activity. + * <li>Intelligence service disabled content capture globally. + * <li>User disabled content capture globally through the Android Settings app. + * <li>Device manufacturer (OEM) disabled content capture globally. + * <li>Transient errors, such as intelligence service package being updated. * </ul> */ public boolean isContentCaptureEnabled() { @@ -369,11 +515,22 @@ public final class ContentCaptureManager { * capture events for websites the content capture service is not interested on. * * @return list of conditions, or {@code null} if the service didn't set any restriction - * (in which case content capture events should always be generated). + * (in which case content capture events should always be generated). If the list is empty, + * then it should not generate any event at all. */ @Nullable public Set<ContentCaptureCondition> getContentCaptureConditions() { - return null; // TODO(b/129267994): implement + // NOTE: we could cache the conditions on ContentCaptureOptions, but then it would be stick + // to the lifetime of the app. OTOH, by dynamically calling the server every time, we allow + // the service to fine tune how long-lived apps (like browsers) are whitelisted. + if (!isContentCaptureEnabled() && !mOptions.lite) return null; + + final SyncResultReceiver resultReceiver = syncRun( + (r) -> mService.getContentCaptureConditions(mContext.getPackageName(), r)); + + final ArrayList<ContentCaptureCondition> result = resultReceiver + .getParcelableListResult(); + return toSet(result); } /** @@ -393,12 +550,12 @@ public final class ContentCaptureManager { } /** - * Gets whether Content Capture is enabled for the given user. + * Gets whether content capture is enabled for the given user. * - * <p>This method is typically used by the Content Capture Service settings page, so it can + * <p>This method is typically used by the content capture service settings page, so it can * provide a toggle to enable / disable it. * - * @throws SecurityException if caller is not the app that owns the Content Capture service + * @throws SecurityException if caller is not the app that owns the content capture service * associated with the user. * * @hide @@ -406,21 +563,14 @@ public final class ContentCaptureManager { @SystemApi @TestApi public boolean isContentCaptureFeatureEnabled() { - final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); - final int resultCode; - try { - mService.isContentCaptureFeatureEnabled(resultReceiver); - resultCode = resultReceiver.getIntResult(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + final SyncResultReceiver resultReceiver = syncRun( + (r) -> mService.isContentCaptureFeatureEnabled(r)); + final int resultCode = resultReceiver.getIntResult(); switch (resultCode) { case RESULT_CODE_TRUE: return true; case RESULT_CODE_FALSE: return false; - case RESULT_CODE_SECURITY_EXCEPTION: - throw new SecurityException("caller is not user's ContentCapture service"); default: Log.wtf(TAG, "received invalid result: " + resultCode); return false; @@ -428,7 +578,7 @@ public final class ContentCaptureManager { } /** - * Called by the app to request the Content Capture service to remove user-data associated with + * Called by the app to request the content capture service to remove user-data associated with * some context. * * @param request object specifying what user data should be removed. @@ -443,6 +593,26 @@ public final class ContentCaptureManager { } } + /** + * Runs a sync method in the service, properly handling exceptions. + * + * @throws SecurityException if caller is not allowed to execute the method. + */ + @NonNull + private SyncResultReceiver syncRun(@NonNull MyRunnable r) { + final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); + try { + r.run(resultReceiver); + final int resultCode = resultReceiver.getIntResult(); + if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) { + throw new SecurityException(resultReceiver.getStringResult()); + } + return resultReceiver; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @hide */ public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.println("ContentCaptureManager"); @@ -466,4 +636,8 @@ public final class ContentCaptureManager { } } } + + private interface MyRunnable { + void run(@NonNull SyncResultReceiver receiver) throws RemoteException; + } } diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index ed1ca2a48850..7761038f8af1 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -38,7 +38,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.UUID; +import java.util.Random; /** * Session used to notify a system-provided Content Capture service about events associated with @@ -48,6 +48,11 @@ public abstract class ContentCaptureSession implements AutoCloseable { private static final String TAG = ContentCaptureSession.class.getSimpleName(); + private static final Random sIdGenerator = new Random(); + + /** @hide */ + public static final int NO_SESSION_ID = 0; + /** * Initial state, when there is no session. * @@ -186,7 +191,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { /** @hide */ @Nullable - protected final String mId; + protected final int mId; private int mState = UNKNOWN_STATE; @@ -210,13 +215,14 @@ public abstract class ContentCaptureSession implements AutoCloseable { /** @hide */ protected ContentCaptureSession() { - this(UUID.randomUUID().toString()); + this(getRandomSessionId()); } /** @hide */ @VisibleForTesting - public ContentCaptureSession(@NonNull String id) { - mId = Preconditions.checkNotNull(id); + public ContentCaptureSession(int id) { + Preconditions.checkArgument(id != NO_SESSION_ID); + mId = id; } // Used by ChildCOntentCaptureSession @@ -241,15 +247,8 @@ public abstract class ContentCaptureSession implements AutoCloseable { } /** @hide */ - @VisibleForTesting - public int getIdAsInt() { - // TODO(b/121197119): use sessionId instead of hashcode once it's changed to int - return mId.hashCode(); - } - - /** @hide */ @NonNull - public String getId() { + public int getId() { return mId; } @@ -415,7 +414,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { // TODO(b/123036895): use a internalNotifyViewsDisappeared that optimizes how the event is // parcelized for (long id : virtualIds) { - internalNotifyViewDisappeared(new AutofillId(hostId, id, getIdAsInt())); + internalNotifyViewDisappeared(new AutofillId(hostId, id, mId)); } } @@ -464,7 +463,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { public @NonNull AutofillId newAutofillId(@NonNull AutofillId hostId, long virtualChildId) { Preconditions.checkNotNull(hostId); Preconditions.checkArgument(hostId.isNonVirtual(), "hostId cannot be virtual: %s", hostId); - return new AutofillId(hostId, virtualChildId, getIdAsInt()); + return new AutofillId(hostId, virtualChildId, mId); } /** @@ -480,7 +479,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { @NonNull public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId, long virtualId) { - return new ViewNode.ViewStructureImpl(parentId, virtualId, getIdAsInt()); + return new ViewNode.ViewStructureImpl(parentId, virtualId, mId); } boolean isContentCaptureEnabled() { @@ -511,7 +510,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { @Override public String toString() { - return mId; + return Integer.toString(mId); } /** @hide */ @@ -541,4 +540,12 @@ public abstract class ContentCaptureSession implements AutoCloseable { return "UNKOWN-" + reason; } } + + private static int getRandomSessionId() { + int id; + do { + id = sIdGenerator.nextInt(); + } while (id == NO_SESSION_ID); + return id; + } } diff --git a/core/java/android/view/contentcapture/ContentCaptureSessionId.java b/core/java/android/view/contentcapture/ContentCaptureSessionId.java index e7c350a12b92..2d350b27c4a3 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSessionId.java +++ b/core/java/android/view/contentcapture/ContentCaptureSessionId.java @@ -27,7 +27,7 @@ import java.io.PrintWriter; */ public final class ContentCaptureSessionId implements Parcelable { - private final @NonNull String mValue; + private final @NonNull int mValue; /** * Creates a new instance. @@ -36,14 +36,14 @@ public final class ContentCaptureSessionId implements Parcelable { * * @hide */ - public ContentCaptureSessionId(@NonNull String value) { + public ContentCaptureSessionId(@NonNull int value) { mValue = value; } /** * @hide */ - public String getValue() { + public int getValue() { return mValue; } @@ -51,7 +51,7 @@ public final class ContentCaptureSessionId implements Parcelable { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((mValue == null) ? 0 : mValue.hashCode()); + result = prime * result + mValue; return result; } @@ -61,11 +61,7 @@ public final class ContentCaptureSessionId implements Parcelable { if (obj == null) return false; if (getClass() != obj.getClass()) return false; final ContentCaptureSessionId other = (ContentCaptureSessionId) obj; - if (mValue == null) { - if (other.mValue != null) return false; - } else if (!mValue.equals(other.mValue)) { - return false; - } + if (mValue != other.mValue) return false; return true; } @@ -77,9 +73,10 @@ public final class ContentCaptureSessionId implements Parcelable { */ @Override public String toString() { - return mValue; + return Integer.toString(mValue); } + /** @hide */ // TODO(b/111276913): dump to proto as well public void dump(PrintWriter pw) { @@ -93,16 +90,16 @@ public final class ContentCaptureSessionId implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeString(mValue); + parcel.writeInt(mValue); } - public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureSessionId> CREATOR = + public static final @NonNull Parcelable.Creator<ContentCaptureSessionId> CREATOR = new Parcelable.Creator<ContentCaptureSessionId>() { @Override @NonNull public ContentCaptureSessionId createFromParcel(Parcel parcel) { - return new ContentCaptureSessionId(parcel.readString()); + return new ContentCaptureSessionId(parcel.readInt()); } @Override diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl index 15fbaa2d7dab..7335073c59e0 100644 --- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl +++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl @@ -42,13 +42,13 @@ oneway interface IContentCaptureManager { * {@link IContentCaptureContext#flags}). */ void startSession(IBinder activityToken, in ComponentName componentName, - String sessionId, int flags, in IResultReceiver result); + int sessionId, int flags, in IResultReceiver result); /** * Marks the end of a session for the calling user identified by * the corresponding {@code startSession}'s {@code sessionId}. */ - void finishSession(String sessionId); + void finishSession(int sessionId); /** * Returns the content capture service's component name (if enabled and @@ -72,4 +72,9 @@ oneway interface IContentCaptureManager { * Returns a ComponentName with the name of custom service activity, if defined. */ void getServiceSettingsActivity(in IResultReceiver result); + + /** + * Returns a list with the ContentCaptureConditions for the package (or null if not defined). + */ + void getContentCaptureConditions(String packageName, in IResultReceiver result); } diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 790b8f9dde46..784cf9c32557 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -318,7 +318,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) { final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1); if (lastEvent.getType() == TYPE_VIEW_DISAPPEARED - && event.getSessionId().equals(lastEvent.getSessionId())) { + && event.getSessionId() == lastEvent.getSessionId()) { if (sVerbose) { Log.v(TAG, "Buffering TYPE_VIEW_DISAPPEARED events for session " + lastEvent.getSessionId()); @@ -581,37 +581,35 @@ public final class MainContentCaptureSession extends ContentCaptureSession { // TODO(b/122454205): refactor "notifyXXXX" methods below to a common "Buffer" object that is // shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such // change should also get get rid of the "internalNotifyXXXX" methods above - void notifyChildSessionStarted(@NonNull String parentSessionId, - @NonNull String childSessionId, @NonNull ContentCaptureContext clientContext) { + void notifyChildSessionStarted(int parentSessionId, int childSessionId, + @NonNull ContentCaptureContext clientContext) { sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED) .setParentSessionId(parentSessionId).setClientContext(clientContext), FORCE_FLUSH); } - void notifyChildSessionFinished(@NonNull String parentSessionId, - @NonNull String childSessionId) { + void notifyChildSessionFinished(int parentSessionId, int childSessionId) { sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED) .setParentSessionId(parentSessionId), FORCE_FLUSH); } - void notifyViewAppeared(@NonNull String sessionId, @NonNull ViewStructureImpl node) { + void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) { sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED) .setViewNode(node.mNode)); } /** Public because is also used by ViewRootImpl */ - public void notifyViewDisappeared(@NonNull String sessionId, @NonNull AutofillId id) { + public void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) { sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id)); } - void notifyViewTextChanged(@NonNull String sessionId, @NonNull AutofillId id, - @Nullable CharSequence text) { + void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) { sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED).setAutofillId(id) .setText(text)); } /** Public because is also used by ViewRootImpl */ - public void notifyViewTreeEvent(@NonNull String sessionId, boolean started) { + public void notifyViewTreeEvent(int sessionId, boolean started) { final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED; sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH); } @@ -622,8 +620,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { sendEvent(new ContentCaptureEvent(mId, type), FORCE_FLUSH); } - void notifyContextUpdated(@NonNull String sessionId, - @Nullable ContentCaptureContext context) { + void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) { sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED) .setClientContext(context)); } diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index 4413585535f2..678a25223ef5 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -321,45 +321,6 @@ public final class WebViewFactory { } } - /** - * If the ApplicationInfo provided is for a stub WebView, fix up the object to include the - * required values from the donor package. If the ApplicationInfo is for a full WebView, - * leave it alone. Throws MissingWebViewPackageException if the donor is missing. - */ - private static void fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm) - throws MissingWebViewPackageException { - String donorPackageName = null; - if (ai.metaData != null) { - donorPackageName = ai.metaData.getString("com.android.webview.WebViewDonorPackage"); - } - if (donorPackageName != null) { - PackageInfo donorPackage; - try { - donorPackage = pm.getPackageInfo( - donorPackageName, - PackageManager.GET_SHARED_LIBRARY_FILES - | PackageManager.MATCH_DEBUG_TRIAGED_MISSING - | PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_FACTORY_ONLY); - } catch (PackageManager.NameNotFoundException e) { - throw new MissingWebViewPackageException("Failed to find donor package: " + - donorPackageName); - } - ApplicationInfo donorInfo = donorPackage.applicationInfo; - - // Replace the stub's code locations with the donor's. - ai.sourceDir = donorInfo.sourceDir; - ai.splitSourceDirs = donorInfo.splitSourceDirs; - ai.nativeLibraryDir = donorInfo.nativeLibraryDir; - ai.secondaryNativeLibraryDir = donorInfo.secondaryNativeLibraryDir; - - // Copy the donor's primary and secondary ABIs, since the stub doesn't have native code - // and so they are unset. - ai.primaryCpuAbi = donorInfo.primaryCpuAbi; - ai.secondaryCpuAbi = donorInfo.secondaryCpuAbi; - } - } - @UnsupportedAppUsage private static Context getWebViewContextAndSetProvider() throws MissingWebViewPackageException { Application initialApplication = AppGlobals.getInitialApplication(); @@ -411,7 +372,6 @@ public final class WebViewFactory { verifyPackageInfo(response.packageInfo, newPackageInfo); ApplicationInfo ai = newPackageInfo.applicationInfo; - fixupStubApplicationInfo(ai, pm); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "initialApplication.createApplicationContext"); @@ -494,18 +454,14 @@ public final class WebViewFactory { */ public static int onWebViewProviderChanged(PackageInfo packageInfo) { int startedRelroProcesses = 0; - ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo); try { - fixupStubApplicationInfo(packageInfo.applicationInfo, - AppGlobals.getInitialApplication().getPackageManager()); - startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo); } catch (Throwable t) { // Log and discard errors at this stage as we must not crash the system server. Log.e(LOGTAG, "error preparing webview native library", t); } - WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo); + WebViewZygote.onWebViewProviderChanged(packageInfo); return startedRelroProcesses; } diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java index 09aa066549cb..62f54b943e11 100644 --- a/core/java/android/webkit/WebViewZygote.java +++ b/core/java/android/webkit/WebViewZygote.java @@ -16,8 +16,6 @@ package android.webkit; -import android.app.LoadedApk; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.os.AsyncTask; import android.os.Build; @@ -29,10 +27,6 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; -import java.io.File; -import java.util.ArrayList; -import java.util.List; - /** @hide */ public class WebViewZygote { private static final String LOGTAG = "WebViewZygote"; @@ -56,13 +50,6 @@ public class WebViewZygote { private static PackageInfo sPackage; /** - * Original ApplicationInfo for the selected WebView package before stub fixup. This is set from - * #onWebViewProviderChanged(). - */ - @GuardedBy("sLock") - private static ApplicationInfo sPackageOriginalAppInfo; - - /** * Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote * will not be started. */ @@ -110,11 +97,9 @@ public class WebViewZygote { } } - public static void onWebViewProviderChanged(PackageInfo packageInfo, - ApplicationInfo originalAppInfo) { + static void onWebViewProviderChanged(PackageInfo packageInfo) { synchronized (sLock) { sPackage = packageInfo; - sPackageOriginalAppInfo = originalAppInfo; // If multi-process is not enabled, then do not start the zygote service. if (!sMultiprocessEnabled) { @@ -165,34 +150,7 @@ public class WebViewZygote { Process.FIRST_ISOLATED_UID, Integer.MAX_VALUE); // TODO(b/123615476) deal with user-id ranges properly ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress()); - - if (sPackageOriginalAppInfo.sourceDir.equals(sPackage.applicationInfo.sourceDir)) { - // No stub WebView is involved here, so we can preload the package the "clean" way - // using the ApplicationInfo. - sZygote.preloadApp(sPackage.applicationInfo, abi); - } else { - // Legacy path to support the stub WebView. - // Reuse the logic from LoadedApk to determine the correct paths and pass them to - // the zygote as strings. - final List<String> zipPaths = new ArrayList<>(10); - final List<String> libPaths = new ArrayList<>(10); - LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths); - final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths); - final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) : - TextUtils.join(File.pathSeparator, zipPaths); - - String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo); - - // Use the original ApplicationInfo to determine what the original classpath would - // have been to use as a cache key. - LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null); - final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) : - TextUtils.join(File.pathSeparator, zipPaths); - - Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath); - sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey, - Build.SUPPORTED_ABIS[0]); - } + sZygote.preloadApp(sPackage.applicationInfo, abi); } catch (Exception e) { Log.e(LOGTAG, "Error connecting to webview zygote", e); stopZygoteLocked(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index a5a1a80cca88..a961783dab7c 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -10081,7 +10081,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Return true iff there is a selection inside this text view. + * Return true iff there is a selection of nonzero length inside this text view. */ public boolean hasSelection() { final int selectionStart = getSelectionStart(); diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index f29174b54383..07d28eba793d 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -24,7 +24,6 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.IntDef; -import android.annotation.UnsupportedAppUsage; import android.app.Activity; import android.app.ActivityManager; import android.app.prediction.AppPredictionContext; @@ -197,6 +196,11 @@ public class ChooserActivity extends ResolverActivity { private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2; private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 3; private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 4; + private static final int LIST_VIEW_UPDATE_MESSAGE = 5; + + private static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250; + + private boolean mListViewDataChanged = false; @Retention(SOURCE) @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT}) @@ -213,10 +217,13 @@ public class ChooserActivity extends ResolverActivity { private final Handler mChooserHandler = new Handler() { @Override public void handleMessage(Message msg) { + if (mChooserListAdapter == null || isDestroyed()) { + return; + } + switch (msg.what) { case CHOOSER_TARGET_SERVICE_RESULT: if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT"); - if (isDestroyed()) break; final ServiceResultInfo sri = (ServiceResultInfo) msg.obj; if (!mServiceConnections.contains(sri.connection)) { Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection @@ -240,17 +247,22 @@ public class ChooserActivity extends ResolverActivity { if (DEBUG) { Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services"); } - if (mChooserListAdapter == null || isDestroyed()) { - break; - } + unbindRemainingServices(); sendVoiceChoicesIfNeeded(); mChooserListAdapter.completeServiceTargetLoading(); break; + case LIST_VIEW_UPDATE_MESSAGE: + if (DEBUG) { + Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; "); + } + + mChooserListAdapter.refreshListView(); + break; + case SHORTCUT_MANAGER_SHARE_TARGET_RESULT: if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT"); - if (isDestroyed()) break; final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj; if (resultInfo.resultTargets != null) { mChooserListAdapter.addServiceResults(resultInfo.originalTarget, @@ -829,6 +841,7 @@ public class ChooserActivity extends ResolverActivity { mRefinementResultReceiver = null; } unbindRemainingServices(); + mChooserHandler.removeMessages(LIST_VIEW_UPDATE_MESSAGE); mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT); if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { @@ -1594,7 +1607,7 @@ public class ChooserActivity extends ResolverActivity { if (info == null) return null; // Now fetch app icon and raster with no badging even in work profile - Bitmap appIcon = (new ActivityInfoPresentationGetter(info)).getIconBitmap(); + Bitmap appIcon = makePresentationGetter(info).getIconBitmap(); // Raster target drawable with appIcon as a badge SimpleIconFactory sif = SimpleIconFactory.obtain(ChooserActivity.this); @@ -1865,12 +1878,30 @@ public class ChooserActivity extends ResolverActivity { ri.noResourceId = true; ri.icon = 0; } + ResolveInfoPresentationGetter getter = makePresentationGetter(ri); mCallerTargets.add(new DisplayResolveInfo(ii, ri, - ri.loadLabel(pm), null, ii)); + getter.getLabel(), getter.getSubLabel(), ii)); } } } + @Override + public void notifyDataSetChanged() { + if (!mListViewDataChanged) { + mChooserHandler.sendEmptyMessageDelayed(LIST_VIEW_UPDATE_MESSAGE, + LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS); + mListViewDataChanged = true; + } + } + + private void refreshListView() { + if (mListViewDataChanged) { + super.notifyDataSetChanged(); + } + mListViewDataChanged = false; + } + + private void createPlaceHolders() { mServiceTargets.clear(); for (int i = 0; i < MAX_SERVICE_TARGETS; i++) { @@ -1879,12 +1910,6 @@ public class ChooserActivity extends ResolverActivity { } @Override - public boolean showsExtendedInfo(TargetInfo info) { - // We have badges so we don't need this text shown. - return false; - } - - @Override public View onCreateView(ViewGroup parent) { return mInflater.inflate( com.android.internal.R.layout.resolve_grid_item, parent, false); @@ -1898,7 +1923,7 @@ public class ChooserActivity extends ResolverActivity { } if (mServiceTargets != null) { - if (getDisplayInfoCount() == 0) { + if (getDisplayResolveInfoCount() == 0) { // b/109676071: When packages change, onListRebuilt() is called before // ResolverActivity.mDisplayList is re-populated; pruning now would cause the // list to disappear briefly, so instead we detect this case (the @@ -1911,12 +1936,14 @@ public class ChooserActivity extends ResolverActivity { if (DEBUG) { Log.d(TAG, "querying direct share targets from ShortcutManager"); } + queryDirectShareTargets(this); } if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) { if (DEBUG) { Log.d(TAG, "List built querying services"); } + queryTargetServices(this); } } @@ -2012,7 +2039,7 @@ public class ChooserActivity extends ResolverActivity { offset += callerTargetCount; return filtered ? super.getItem(position - offset) - : getDisplayInfoAt(position - offset); + : getDisplayResolveInfo(position - offset); } public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) { @@ -2301,8 +2328,10 @@ public class ChooserActivity extends ResolverActivity { final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); int columnCount = holder.getColumnCount(); + final boolean isDirectShare = holder instanceof DirectShareViewHolder; + for (int i = 0; i < columnCount; i++) { - final View v = mChooserListAdapter.createView(holder.getRow(i)); + final View v = mChooserListAdapter.createView(holder.getRowByIndex(i)); final int column = i; v.setOnClickListener(new OnClickListener() { @Override @@ -2321,27 +2350,31 @@ public class ChooserActivity extends ResolverActivity { }); ViewGroup row = holder.addView(i, v); + // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll = + // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be + // done before measuring. + if (isDirectShare) { + final ViewHolder vh = (ViewHolder) v.getTag(); + vh.text.setLines(2); + vh.text.setHorizontallyScrolling(false); + vh.text2.setVisibility(View.GONE); + } + // Force height to be a given so we don't have visual disruption during scaling. - LayoutParams lp = v.getLayoutParams(); v.measure(spec, spec); - if (lp == null) { - lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight()); - row.setLayoutParams(lp); - } else { - lp.height = v.getMeasuredHeight(); - } + setViewHeight(v, v.getMeasuredHeight()); } final ViewGroup viewGroup = holder.getViewGroup(); - // Pre-measure so we can scale later. + // Pre-measure and fix height so we can scale later. holder.measure(); - LayoutParams lp = viewGroup.getLayoutParams(); - if (lp == null) { - lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight()); - viewGroup.setLayoutParams(lp); - } else { - lp.height = holder.getMeasuredRowHeight(); + setViewHeight(viewGroup, holder.getMeasuredRowHeight()); + + if (isDirectShare) { + DirectShareViewHolder dsvh = (DirectShareViewHolder) holder; + setViewHeight(dsvh.getRow(0), holder.getMeasuredRowHeight()); + setViewHeight(dsvh.getRow(1), holder.getMeasuredRowHeight()); } viewGroup.setTag(holder); @@ -2349,6 +2382,16 @@ public class ChooserActivity extends ResolverActivity { return holder; } + private void setViewHeight(View view, int heightPx) { + LayoutParams lp = view.getLayoutParams(); + if (lp == null) { + lp = new LayoutParams(LayoutParams.MATCH_PARENT, heightPx); + view.setLayoutParams(lp); + } else { + lp.height = heightPx; + } + } + RowViewHolder createViewHolder(int viewType, ViewGroup parent) { if (viewType == VIEW_TYPE_DIRECT_SHARE) { ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate( @@ -2386,7 +2429,7 @@ public class ChooserActivity extends ResolverActivity { if (startType != lastStartType || rowPosition == getContentPreviewRowCount()) { row.setBackground(mChooserRowLayer); - setVertPadding(row, mChooserRowServiceSpacing, 0); + setVertPadding(row, 0, 0); } else { row.setBackground(null); setVertPadding(row, 0, 0); @@ -2483,7 +2526,9 @@ public class ChooserActivity extends ResolverActivity { abstract ViewGroup getViewGroup(); - abstract ViewGroup getRow(int index); + abstract ViewGroup getRowByIndex(int index); + + abstract ViewGroup getRow(int rowNumber); abstract void setViewVisibility(int i, int visibility); @@ -2532,10 +2577,15 @@ public class ChooserActivity extends ResolverActivity { return mRow; } - public ViewGroup getRow(int index) { + public ViewGroup getRowByIndex(int index) { return mRow; } + public ViewGroup getRow(int rowNumber) { + if (rowNumber == 0) return mRow; + return null; + } + public ViewGroup addView(int index, View v) { mRow.addView(v); mCells[index] = v; @@ -2574,7 +2624,7 @@ public class ChooserActivity extends ResolverActivity { } public ViewGroup addView(int index, View v) { - ViewGroup row = getRow(index); + ViewGroup row = getRowByIndex(index); row.addView(v); mCells[index] = v; @@ -2589,10 +2639,14 @@ public class ChooserActivity extends ResolverActivity { return mParent; } - public ViewGroup getRow(int index) { + public ViewGroup getRowByIndex(int index) { return mRows.get(index / mCellCountPerRow); } + public ViewGroup getRow(int rowNumber) { + return mRows.get(rowNumber); + } + public void measure() { final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); getRow(0).measure(spec, spec); diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 84a1bed36e55..f47469a0e1b3 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -83,7 +83,6 @@ import com.android.internal.widget.ResolverDrawerLayout; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; @@ -499,33 +498,40 @@ public class ResolverActivity extends Activity { /** - * Loads the icon for the provided ApplicationInfo. Defaults to using the application icon over - * any IntentFilter or Activity icon to increase user understanding, with an exception for - * applications that hold the right permission. Always attempts to use icon resources over - * PackageManager loading mechanisms so badging can be done by iconloader. + * Loads the icon and label for the provided ApplicationInfo. Defaults to using the application + * icon and label over any IntentFilter or Activity icon to increase user understanding, with an + * exception for applications that hold the right permission. Always attempts to use available + * resources over PackageManager loading mechanisms so badging can be done by iconloader. Uses + * Strings to strip creative formatting. */ - private abstract class TargetPresentationGetter { - @Nullable abstract Drawable getIconSubstitute(); - @Nullable abstract String getAppSubLabel(); + private abstract static class TargetPresentationGetter { + @Nullable abstract Drawable getIconSubstituteInternal(); + @Nullable abstract String getAppSubLabelInternal(); + private Context mCtx; + protected PackageManager mPm; private final ApplicationInfo mAi; + private final int mIconDpi; private final boolean mHasSubstitutePermission; - TargetPresentationGetter(ApplicationInfo ai) { + TargetPresentationGetter(Context ctx, int iconDpi, ApplicationInfo ai) { + mCtx = ctx; + mPm = ctx.getPackageManager(); mAi = ai; + mIconDpi = iconDpi; mHasSubstitutePermission = PackageManager.PERMISSION_GRANTED == mPm.checkPermission( android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON, mAi.packageName); } - Drawable getIcon() { - return new BitmapDrawable(getResources(), getIconBitmap()); + public Drawable getIcon() { + return new BitmapDrawable(mCtx.getResources(), getIconBitmap()); } - Bitmap getIconBitmap() { + public Bitmap getIconBitmap() { Drawable dr = null; if (mHasSubstitutePermission) { - dr = getIconSubstitute(); + dr = getIconSubstituteInternal(); } if (dr == null) { @@ -542,18 +548,18 @@ public class ResolverActivity extends Activity { dr = mAi.loadIcon(mPm); } - SimpleIconFactory sif = SimpleIconFactory.obtain(ResolverActivity.this); + SimpleIconFactory sif = SimpleIconFactory.obtain(mCtx); Bitmap icon = sif.createUserBadgedIconBitmap(dr, Process.myUserHandle()); sif.recycle(); return icon; } - String getLabel() { + public String getLabel() { String label = null; // Apps with the substitute permission will always show the sublabel as their label if (mHasSubstitutePermission) { - label = getAppSubLabel(); + label = getAppSubLabelInternal(); } if (label == null) { @@ -563,10 +569,14 @@ public class ResolverActivity extends Activity { return label; } - String getSubLabel() { + public String getSubLabel() { // Apps with the substitute permission will never have a sublabel if (mHasSubstitutePermission) return null; - return getAppSubLabel(); + return getAppSubLabelInternal(); + } + + protected String loadLabelFromResource(Resources res, int resId) { + return res.getString(resId); } @Nullable @@ -576,17 +586,19 @@ public class ResolverActivity extends Activity { } - protected class ResolveInfoPresentationGetter extends TargetPresentationGetter { - + /** + * Loads the icon and label for the provided ResolveInfo. + */ + @VisibleForTesting + public static class ResolveInfoPresentationGetter extends TargetPresentationGetter { private final ResolveInfo mRi; - - ResolveInfoPresentationGetter(ResolveInfo ri) { - super(ri.activityInfo.applicationInfo); + public ResolveInfoPresentationGetter(Context ctx, int iconDpi, ResolveInfo ri) { + super(ctx, iconDpi, ri.activityInfo.applicationInfo); mRi = ri; } @Override - Drawable getIconSubstitute() { + Drawable getIconSubstituteInternal() { Drawable dr = null; try { // Do not use ResolveInfo#getIconResource() as it defaults to the app @@ -603,20 +615,31 @@ public class ResolverActivity extends Activity { } @Override - String getAppSubLabel() { + String getAppSubLabelInternal() { + // Will default to app name if no intent filter or activity label set, make sure to + // check if subLabel matches label before final display return (String) mRi.loadLabel(mPm); } } - protected class ActivityInfoPresentationGetter extends TargetPresentationGetter { + ResolveInfoPresentationGetter makePresentationGetter(ResolveInfo ri) { + return new ResolveInfoPresentationGetter(this, mIconDpi, ri); + } + + /** + * Loads the icon and label for the provided ActivityInfo. + */ + @VisibleForTesting + public static class ActivityInfoPresentationGetter extends TargetPresentationGetter { private final ActivityInfo mActivityInfo; - protected ActivityInfoPresentationGetter(ActivityInfo activityInfo) { - super(activityInfo.applicationInfo); + public ActivityInfoPresentationGetter(Context ctx, int iconDpi, + ActivityInfo activityInfo) { + super(ctx, iconDpi, activityInfo.applicationInfo); mActivityInfo = activityInfo; } @Override - Drawable getIconSubstitute() { + Drawable getIconSubstituteInternal() { Drawable dr = null; try { // Do not use ActivityInfo#getIconResource() as it defaults to the app @@ -634,13 +657,19 @@ public class ResolverActivity extends Activity { } @Override - String getAppSubLabel() { + String getAppSubLabelInternal() { + // Will default to app name if no activity label set, make sure to check if subLabel + // matches label before final display return (String) mActivityInfo.loadLabel(mPm); } } + protected ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo ai) { + return new ActivityInfoPresentationGetter(this, mIconDpi, ai); + } + Drawable loadIconForResolveInfo(ResolveInfo ri) { - return (new ResolveInfoPresentationGetter(ri)).getIcon(); + return makePresentationGetter(ri).getIcon(); } @Override @@ -1201,7 +1230,7 @@ public class ResolverActivity extends Activity { final ImageView iconView = findViewById(R.id.icon); final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem(); if (iconView != null && iconInfo != null) { - new LoadIconIntoViewTask(iconInfo, iconView).execute(); + new LoadIconTask(iconInfo, iconView).execute(); } } @@ -1713,34 +1742,13 @@ public class ResolverActivity extends Activity { } } - // Check for applications with same name and use application name or - // package name if necessary - ResolvedComponentInfo rci0 = sortedComponents.get(0); - ResolveInfo r0 = rci0.getResolveInfoAt(0); - int start = 0; - CharSequence r0Label = r0.loadLabel(mPm); - mHasExtendedInfo = false; - for (int i = 1; i < N; i++) { - if (r0Label == null) { - r0Label = r0.activityInfo.packageName; - } - ResolvedComponentInfo rci = sortedComponents.get(i); - ResolveInfo ri = rci.getResolveInfoAt(0); - CharSequence riLabel = ri.loadLabel(mPm); - if (riLabel == null) { - riLabel = ri.activityInfo.packageName; - } - if (riLabel.equals(r0Label)) { - continue; + for (ResolvedComponentInfo rci : sortedComponents) { + final ResolveInfo ri = rci.getResolveInfoAt(0); + if (ri != null) { + ResolveInfoPresentationGetter pg = makePresentationGetter(ri); + addResolveInfoWithAlternates(rci, pg.getSubLabel(), pg.getLabel()); } - processGroup(sortedComponents, start, (i - 1), rci0, r0Label); - rci0 = rci; - r0 = ri; - r0Label = riLabel; - start = i; } - // Process last group - processGroup(sortedComponents, start, (N - 1), rci0, r0Label); } postListReadyRunnable(); @@ -1782,55 +1790,6 @@ public class ResolverActivity extends Activity { return mFilterLastUsed; } - private void processGroup(List<ResolvedComponentInfo> rList, int start, int end, - ResolvedComponentInfo ro, CharSequence roLabel) { - // Process labels from start to i - int num = end - start+1; - if (num == 1) { - // No duplicate labels. Use label for entry at start - addResolveInfoWithAlternates(ro, null, roLabel); - } else { - mHasExtendedInfo = true; - boolean usePkg = false; - final ApplicationInfo ai = ro.getResolveInfoAt(0).activityInfo.applicationInfo; - final CharSequence startApp = ai.loadLabel(mPm); - if (startApp == null) { - usePkg = true; - } - if (!usePkg) { - // Use HashSet to track duplicates - HashSet<CharSequence> duplicates = - new HashSet<CharSequence>(); - duplicates.add(startApp); - for (int j = start+1; j <= end ; j++) { - ResolveInfo jRi = rList.get(j).getResolveInfoAt(0); - CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm); - if ( (jApp == null) || (duplicates.contains(jApp))) { - usePkg = true; - break; - } else { - duplicates.add(jApp); - } - } - // Clear HashSet for later use - duplicates.clear(); - } - for (int k = start; k <= end; k++) { - final ResolvedComponentInfo rci = rList.get(k); - final ResolveInfo add = rci.getResolveInfoAt(0); - final CharSequence extraInfo; - if (usePkg) { - // Use package name for all entries from start to end-1 - extraInfo = add.activityInfo.packageName; - } else { - // Use application name for all entries from start to end-1 - extraInfo = add.activityInfo.applicationInfo.loadLabel(mPm); - } - addResolveInfoWithAlternates(rci, extraInfo, roLabel); - } - } - } - private void addResolveInfoWithAlternates(ResolvedComponentInfo rci, CharSequence extraInfo, CharSequence roLabel) { final int count = rci.getCount(); @@ -1912,14 +1871,6 @@ public class ResolverActivity extends Activity { return mDisplayList.size(); } - public int getDisplayInfoCount() { - return mDisplayList.size(); - } - - public DisplayResolveInfo getDisplayInfoAt(int index) { - return mDisplayList.get(index); - } - @Nullable public TargetInfo getItem(int position) { if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) { @@ -1979,36 +1930,38 @@ public class ResolverActivity extends Activity { com.android.internal.R.layout.resolve_list_item, parent, false); } - public boolean showsExtendedInfo(TargetInfo info) { - return !TextUtils.isEmpty(info.getExtendedInfo()); - } - public final void bindView(int position, View view) { onBindView(view, getItem(position)); } - private void onBindView(View view, TargetInfo info) { + protected void onBindView(View view, TargetInfo info) { final ViewHolder holder = (ViewHolder) view.getTag(); if (info == null) { holder.icon.setImageDrawable( getDrawable(R.drawable.resolver_icon_placeholder)); return; } + final CharSequence label = info.getDisplayLabel(); if (!TextUtils.equals(holder.text.getText(), label)) { holder.text.setText(info.getDisplayLabel()); } - if (showsExtendedInfo(info)) { - holder.text2.setVisibility(View.VISIBLE); - holder.text2.setText(info.getExtendedInfo()); - } else { - holder.text2.setVisibility(View.GONE); + + // Always show a subLabel for visual consistency across list items. Show an empty + // subLabel if the subLabel is the same as the label + CharSequence subLabel = info.getExtendedInfo(); + if (TextUtils.equals(label, subLabel)) subLabel = null; + + if (!TextUtils.equals(holder.text2.getText(), subLabel)) { + holder.text2.setText(subLabel); } + if (info instanceof DisplayResolveInfo && !((DisplayResolveInfo) info).hasDisplayIcon()) { - new LoadAdapterIconTask((DisplayResolveInfo) info).execute(); + new LoadIconTask((DisplayResolveInfo) info, holder.icon).execute(); + } else { + holder.icon.setImageDrawable(info.getDisplayIcon()); } - holder.icon.setImageDrawable(info.getDisplayIcon()); } } @@ -2127,13 +2080,15 @@ public class ResolverActivity extends Activity { } - abstract class LoadIconTask extends AsyncTask<Void, Void, Drawable> { + class LoadIconTask extends AsyncTask<Void, Void, Drawable> { protected final DisplayResolveInfo mDisplayResolveInfo; private final ResolveInfo mResolveInfo; + private final ImageView mTargetView; - public LoadIconTask(DisplayResolveInfo dri) { + LoadIconTask(DisplayResolveInfo dri, ImageView target) { mDisplayResolveInfo = dri; mResolveInfo = dri.getResolveInfo(); + mTargetView = target; } @Override @@ -2143,37 +2098,12 @@ public class ResolverActivity extends Activity { @Override protected void onPostExecute(Drawable d) { - mDisplayResolveInfo.setDisplayIcon(d); - } - } - - class LoadAdapterIconTask extends LoadIconTask { - public LoadAdapterIconTask(DisplayResolveInfo dri) { - super(dri); - } - - @Override - protected void onPostExecute(Drawable d) { - super.onPostExecute(d); if (mProfileView != null && mAdapter.getOtherProfile() == mDisplayResolveInfo) { bindProfileView(); + } else { + mDisplayResolveInfo.setDisplayIcon(d); + mTargetView.setImageDrawable(d); } - mAdapter.notifyDataSetChanged(); - } - } - - class LoadIconIntoViewTask extends LoadIconTask { - private final ImageView mTargetView; - - public LoadIconIntoViewTask(DisplayResolveInfo dri, ImageView target) { - super(dri); - mTargetView = target; - } - - @Override - protected void onPostExecute(Drawable d) { - super.onPostExecute(d); - mTargetView.setImageDrawable(d); } } diff --git a/core/java/com/android/internal/infra/GlobalWhitelistState.java b/core/java/com/android/internal/infra/GlobalWhitelistState.java new file mode 100644 index 000000000000..dfa59b7bd0ac --- /dev/null +++ b/core/java/com/android/internal/infra/GlobalWhitelistState.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.infra; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.ComponentName; +import android.util.ArraySet; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +import java.io.PrintWriter; +import java.util.List; + +/** + * Helper class used to manage a {@link WhitelistHelper} per user instance when the main service + * cannot hold a lock when external entities (typically {@code ActivityManagerService}) needs to + * get whitelist info. + * + * <p>This class is thread safe. + */ +public class GlobalWhitelistState { + + // Uses full-name to avoid collision with service-provided mLock + protected final Object mGlobalWhitelistStateLock = new Object(); + + @Nullable + @GuardedBy("mGlobalWhitelistStateLock") + protected SparseArray<WhitelistHelper> mWhitelisterHelpers; + + /** + * Sets the whitelist for the given user. + */ + public void setWhitelist(@UserIdInt int userId, @Nullable List<String> packageNames, + @Nullable List<ComponentName> components) { + synchronized (mGlobalWhitelistStateLock) { + if (mWhitelisterHelpers == null) { + mWhitelisterHelpers = new SparseArray<>(1); + } + WhitelistHelper helper = mWhitelisterHelpers.get(userId); + if (helper == null) { + helper = new WhitelistHelper(); + mWhitelisterHelpers.put(userId, helper); + } + helper.setWhitelist(packageNames, components); + } + } + + /** + * Checks if the given package is whitelisted for the given user. + */ + public boolean isWhitelisted(@UserIdInt int userId, @NonNull String packageName) { + synchronized (mGlobalWhitelistStateLock) { + if (mWhitelisterHelpers == null) return false; + final WhitelistHelper helper = mWhitelisterHelpers.get(userId); + return helper == null ? false : helper.isWhitelisted(packageName); + } + } + + /** + * Checks if the given component is whitelisted for the given user. + */ + public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) { + synchronized (mGlobalWhitelistStateLock) { + if (mWhitelisterHelpers == null) return false; + final WhitelistHelper helper = mWhitelisterHelpers.get(userId); + return helper == null ? false : helper.isWhitelisted(componentName); + } + } + + /** + * Gets the whitelisted components for the given package and user. + */ + public ArraySet<ComponentName> getWhitelistedComponents(@UserIdInt int userId, + @NonNull String packageName) { + synchronized (mGlobalWhitelistStateLock) { + if (mWhitelisterHelpers == null) return null; + final WhitelistHelper helper = mWhitelisterHelpers.get(userId); + return helper == null ? null : helper.getWhitelistedComponents(packageName); + } + } + + /** + * Resets the whitelist for the given user. + */ + public void resetWhitelist(@NonNull int userId) { + synchronized (mGlobalWhitelistStateLock) { + if (mWhitelisterHelpers == null) return; + mWhitelisterHelpers.remove(userId); + if (mWhitelisterHelpers.size() == 0) { + mWhitelisterHelpers = null; + } + } + } + + /** + * Dumps it! + */ + public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { + pw.print(prefix); pw.print("State: "); + synchronized (mGlobalWhitelistStateLock) { + if (mWhitelisterHelpers == null) { + pw.println("empty"); + return; + } + pw.print(mWhitelisterHelpers.size()); pw.println(" services"); + final String prefix2 = prefix + " "; + for (int i = 0; i < mWhitelisterHelpers.size(); i++) { + final int userId = mWhitelisterHelpers.keyAt(i); + final WhitelistHelper helper = mWhitelisterHelpers.valueAt(i); + helper.dump(prefix2, "Whitelist for userId " + userId, pw); + } + } + } +} diff --git a/core/java/com/android/internal/infra/WhitelistHelper.java b/core/java/com/android/internal/infra/WhitelistHelper.java index 183b465afbfd..d7753db6b0f7 100644 --- a/core/java/com/android/internal/infra/WhitelistHelper.java +++ b/core/java/com/android/internal/infra/WhitelistHelper.java @@ -31,6 +31,7 @@ import java.util.List; /** * Helper class for keeping track of whitelisted packages/activities. * + * <p><b>NOTE: </b>this class is not thread safe. * @hide */ public final class WhitelistHelper { diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java index 524a5cc353f3..b0855f494ffd 100644 --- a/core/java/com/android/internal/os/RoSystemProperties.java +++ b/core/java/com/android/internal/os/RoSystemProperties.java @@ -59,6 +59,8 @@ public class RoSystemProperties { // ------ ro.fw.* ------------ // public static final boolean FW_SYSTEM_USER_SPLIT = SystemProperties.getBoolean("ro.fw.system_user_split", false); + public static final boolean MULTIUSER_HEADLESS_SYSTEM_USER = + SystemProperties.getBoolean("ro.fw.multiuser.headless_system_user", false); // ------ ro.crypto.* -------- // public static final CryptoProperties.state_values CRYPTO_STATE = diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 3303374fd6c4..e8691fa5e23e 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -527,6 +527,13 @@ public class ArrayUtils { return (array != null) ? array.clone() : null; } + /** + * Clones an array or returns null if the array is null. + */ + public static @Nullable <T> T[] cloneOrNull(@Nullable T[] array) { + return (array != null) ? array.clone() : null; + } + public static @Nullable <T> ArraySet<T> cloneOrNull(@Nullable ArraySet<T> array) { return (array != null) ? new ArraySet<T>(array) : null; } diff --git a/core/java/com/android/internal/util/SyncResultReceiver.java b/core/java/com/android/internal/util/SyncResultReceiver.java index 60af5117489b..00e91017a9eb 100644 --- a/core/java/com/android/internal/util/SyncResultReceiver.java +++ b/core/java/com/android/internal/util/SyncResultReceiver.java @@ -19,10 +19,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; import android.os.Parcelable; -import android.os.RemoteException; import com.android.internal.os.IResultReceiver; +import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -97,6 +97,15 @@ public final class SyncResultReceiver extends IResultReceiver.Stub { } /** + * Gets the result from an operation that returns a {@code Parcelable} list. + */ + @Nullable + public <P extends Parcelable> ArrayList<P> getParcelableListResult() throws TimeoutException { + waitResult(); + return mBundle == null ? null : mBundle.getParcelableArrayList(EXTRA); + } + + /** * Gets the optional result from an operation that returns an extra {@code int} (besides the * result code). * @@ -150,6 +159,17 @@ public final class SyncResultReceiver extends IResultReceiver.Stub { } /** + * Creates a bundle for a {@code Parcelable} list so it can be retrieved by + * {@link #getParcelableResult()}. + */ + @NonNull + public static Bundle bundleFor(@Nullable ArrayList<? extends Parcelable> value) { + final Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(EXTRA, value); + return bundle; + } + + /** * Creates a bundle for an {@code int} value so it can be retrieved by * {@link #getParcelableResult()} - typically used to return an extra {@code int} (as the 1st * is returned as the result code). @@ -162,7 +182,7 @@ public final class SyncResultReceiver extends IResultReceiver.Stub { } /** @hide */ - public static final class TimeoutException extends RemoteException { + public static final class TimeoutException extends RuntimeException { private TimeoutException(String msg) { super(msg); } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 664f7f47cf18..000c0449358a 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -303,6 +303,7 @@ cc_library_shared { "libnativewindow", "libhwui", "libdl", + "libdl_android", "libstatslog", "server_configurable_flags", ], diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index f40b461a6dfd..bd4862dfb08d 100644 --- a/core/jni/android_content_res_ApkAssets.cpp +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -106,8 +106,7 @@ static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr); - (void)apk_assets; - return JNI_TRUE; + return apk_assets->IsUpToDate() ? JNI_TRUE : JNI_FALSE; } static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) { diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 88713d10bb2d..a296d647657b 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2229,6 +2229,11 @@ android_media_AudioSystem_isHapticPlaybackSupported(JNIEnv *env, jobject thiz) return AudioSystem::isHapticPlaybackSupported(); } +static jint +android_media_AudioSystem_setAllowedCapturePolicy(JNIEnv *env, jobject thiz, jint uid, jint flags) { + return AudioSystem::setAllowedCapturePolicy(uid, flags); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { @@ -2304,6 +2309,7 @@ static const JNINativeMethod gMethods[] = { {"isHapticPlaybackSupported", "()Z", (void *)android_media_AudioSystem_isHapticPlaybackSupported}, {"getHwOffloadEncodingFormatsSupportedForA2DP", "(Ljava/util/ArrayList;)I", (void*)android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP}, + {"setAllowedCapturePolicy", "(II)I", (void *)android_media_AudioSystem_setAllowedCapturePolicy}, }; static const JNINativeMethod gEventHandlerMethods[] = { diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index d7a981ed3e9d..82acf6fb432b 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -470,6 +470,7 @@ static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, std::vector<uint8_t> buf(MAXPACKETSIZE, 0); int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE); + jniSetFileDescriptorOfFD(env, javaFd, -1); if (res < 0) { throwErrnoException(env, "resNetworkResult", -res); return nullptr; @@ -490,6 +491,7 @@ static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) { int fd = jniGetFDFromFileDescriptor(env, javaFd); resNetworkCancel(fd); + jniSetFileDescriptorOfFD(env, javaFd, -1); } static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) { diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index bf33ac67e333..d0295274dbe4 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2328,4 +2328,7 @@ enum PageId { // OPEN: Settings > System > Aware > Info dialog DIALOG_AWARE_STATUS = 1701; + + // Open: Settings > app > bubble settings > confirmation dialog + DIALOG_APP_BUBBLE_SETTINGS = 1702; } diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index dfb6c0817043..9a9c9d14154b 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -48,6 +48,7 @@ import "frameworks/base/core/proto/android/service/notification.proto"; import "frameworks/base/core/proto/android/service/package.proto"; import "frameworks/base/core/proto/android/service/print.proto"; import "frameworks/base/core/proto/android/service/procstats.proto"; +import "frameworks/base/core/proto/android/service/restricted_image.proto"; import "frameworks/base/core/proto/android/service/usb.proto"; import "frameworks/base/core/proto/android/util/event_log_tags.proto"; import "frameworks/base/core/proto/android/util/log.proto"; @@ -314,6 +315,12 @@ message IncidentProto { (section).args = "role --proto" ]; + optional android.service.restricted_image.RestrictedImagesDumpProto restricted_images = 3025 [ + (section).type = SECTION_DUMPSYS, + (section).userdebug_and_eng_only = true, + (section).args = "incidentcompanion --restricted_image" + ]; + // Reserved for OEMs. extensions 50000 to 100000; } diff --git a/core/proto/android/service/restricted_image.proto b/core/proto/android/service/restricted_image.proto new file mode 100644 index 000000000000..4a33d478fbde --- /dev/null +++ b/core/proto/android/service/restricted_image.proto @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +package android.service.restricted_image; + +option java_multiple_files = true; +option java_outer_classname = "RestrictedImage"; + +import "frameworks/base/core/proto/android/privacy.proto"; + +// Restricted Image proto is for collecting images from the user with their +// permission for the purpose of debugging photos. +message RestrictedImagesDumpProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + repeated RestrictedImageSetProto sets = 1; +} + +message RestrictedImageSetProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + // Name of the service producing the data. + optional string category = 1; + + // The images + repeated RestrictedImageProto images = 2; + + // Additional metadata + optional bytes metadata = 3; +} + +message RestrictedImageProto { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + // Type of image data + optional string mime_type = 1; + + // The image data + optional bytes image_data = 2; + + // Metadata about the image. Typically this has another proto schema, + // but it is undefined exactly what that is in AOSP code. + optional bytes metadata = 3; +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index cffa46c64b36..653ced5ea5ce 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -629,6 +629,9 @@ <protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" /> + <!-- For tether entitlement recheck--> + <protected-broadcast + android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" /> <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> <!-- ====================================================================== --> @@ -1011,6 +1014,9 @@ call with the option to redirect the call to a different number or abort the call altogether. <p>Protection level: dangerous + + @deprecated Applications should use {@link android.telecom.CallRedirectionService} instead + of the {@link android.content.Intent#ACTION_NEW_OUTGOING_CALL} broadcast. --> <permission android:name="android.permission.PROCESS_OUTGOING_CALLS" android:permissionGroup="android.permission-group.UNDEFINED" @@ -1600,6 +1606,14 @@ <permission android:name="android.permission.NETWORK_MANAGED_PROVISIONING" android:protectionLevel="signature" /> + <!-- Allows Carrier Provisioning to call methods in Networking services + <p>Not for use by any other third-party or privileged applications. + @SystemApi + @hide This should only be used by CarrierProvisioning. + --> + <permission android:name="android.permission.NETWORK_CARRIER_PROVISIONING" + android:protectionLevel="signature|privileged" /> + <!-- #SystemApi @hide Allows applications to access information about LoWPAN interfaces. <p>Not for use by third-party applications. --> <permission android:name="android.permission.ACCESS_LOWPAN_STATE" diff --git a/packages/SystemUI/res/drawable/ic_signal_airplane.xml b/core/res/res/drawable/ic_qs_airplane.xml index f708ed9cb8a6..166d415a6dcc 100644 --- a/packages/SystemUI/res/drawable/ic_signal_airplane.xml +++ b/core/res/res/drawable/ic_qs_airplane.xml @@ -15,12 +15,11 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="18dp" + android:height="18dp" android:viewportWidth="24" android:viewportHeight="24"> - -<path - android:fillColor="#FFFFFF" - android:pathData="M21,16v-2l-8-5V3.5C13,2.67,12.33,2,11.5,2S10,2.67,10,3.5V9l-8,5v2l8-2.5V19l-2,1.5V22l3.5-1l3.5,1v-1.5L13,19v-5.5L21,16z" /> + <path + android:fillColor="#FFFFFF" + android:pathData="M21,16v-2l-8-5V3.5C13,2.67,12.33,2,11.5,2S10,2.67,10,3.5V9l-8,5v2l8-2.5V19l-2,1.5V22l3.5-1l3.5,1v-1.5L13,19v-5.5L21,16z" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_auto_rotate.xml b/core/res/res/drawable/ic_qs_auto_rotate.xml index 47e1059fab44..47e1059fab44 100644 --- a/packages/SystemUI/res/drawable/ic_qs_auto_rotate.xml +++ b/core/res/res/drawable/ic_qs_auto_rotate.xml diff --git a/core/res/res/drawable/ic_qs_bluetooth.xml b/core/res/res/drawable/ic_qs_bluetooth.xml new file mode 100644 index 000000000000..91fcff0dfab6 --- /dev/null +++ b/core/res/res/drawable/ic_qs_bluetooth.xml @@ -0,0 +1,25 @@ +<!-- + Copyright (C) 2016 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/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41L11,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59L13,5.83zM14.88,16.29L13,18.17v-3.76l1.88,1.88z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_dnd.xml b/core/res/res/drawable/ic_qs_dnd.xml index 09a6aabf40bb..b361169ce1af 100644 --- a/packages/SystemUI/res/drawable/ic_dnd.xml +++ b/core/res/res/drawable/ic_qs_dnd.xml @@ -14,17 +14,12 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:height="24dp" + android:height="17dp" + android:width="17dp" android:viewportHeight="24.0" - android:viewportWidth="24.0" - android:width="24dp" - android:tint="?android:attr/colorControlNormal"> + android:viewportWidth="24.0" > <path - android:fillColor="#FFFFFFFF" - android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10c5.52,0 10,-4.48 10,-10C22,6.48 17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8c0,-4.41 3.59,-8 8,-8c4.41,0 8,3.59 8,8C20,16.41 16.41,20 12,20z"/> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M7,11h10v2h-10z"/> - + android:fillColor="@android:color/white" + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM7,11h10v2L7,13z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_signal_flashlight.xml b/core/res/res/drawable/ic_qs_flashlight.xml index e63595300d5f..e63595300d5f 100644 --- a/packages/SystemUI/res/drawable/ic_signal_flashlight.xml +++ b/core/res/res/drawable/ic_qs_flashlight.xml diff --git a/core/res/res/drawable/ic_settings_bluetooth.xml b/core/res/res/drawable/ic_settings_bluetooth.xml index 6e32e1a7f631..91fcff0dfab6 100644 --- a/core/res/res/drawable/ic_settings_bluetooth.xml +++ b/core/res/res/drawable/ic_settings_bluetooth.xml @@ -14,12 +14,12 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24.0dp" - android:height="24.0dp" + android:width="24dp" + android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0" android:tint="?android:attr/colorControlNormal"> <path - android:fillColor="#FFFFFFFF" - android:pathData="M13.5,12l3.8,-3.7c0.4,-0.4 0.4,-1.1 0,-1.5l-4.5,-4.5c-0.4,-0.4 -1.1,-0.4 -1.5,0.1C11.1,2.5 11,2.8 11,3v6.4L6.9,5.4C6.5,5 5.9,5 5.5,5.4s-0.4,1.1 0,1.5l5.1,5.1l-5.1,5.1c-0.4,0.4 -0.4,1.1 0,1.5s1.1,0.4 1.5,0l4.1,-4V21c0,0.6 0.5,1 1,1c0.3,0 0.5,-0.1 0.7,-0.3l0.1,0l4.5,-4.5c0.4,-0.4 0.4,-1.1 0,-1.5L13.5,12zM13,9.7V5.4l2.1,2.2L13,9.7zM13,18.6v-4.3l2.1,2.2L13,18.6z"/> -</vector> + android:fillColor="@android:color/white" + android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41L11,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59L13,5.83zM14.88,16.29L13,18.17v-3.76l1.88,1.88z"/> +</vector>
\ No newline at end of file diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml index 4a3dfba63c6d..7065149e268e 100644 --- a/core/res/res/layout/resolve_grid_item.xml +++ b/core/res/res/layout/resolve_grid_item.xml @@ -22,46 +22,43 @@ android:layout_height="wrap_content" android:minHeight="100dp" android:gravity="center" - android:paddingTop="8dp" + android:paddingTop="24dp" android:paddingBottom="8dp" + android:paddingLeft="2dp" + android:paddingRight="2dp" android:focusable="true" android:background="?attr/selectableItemBackgroundBorderless"> <ImageView android:id="@+id/icon" android:layout_width="@dimen/resolver_icon_size" android:layout_height="@dimen/resolver_icon_size" - android:layout_marginLeft="3dp" - android:layout_marginRight="3dp" - android:layout_marginBottom="3dp" android:scaleType="fitCenter" /> - <!-- Activity name --> + <!-- Size manually tuned to match specs --> + <Space android:layout_width="1dp" + android:layout_height="7dp"/> + + <!-- App name or Direct Share target name, DS set to 2 lines --> <TextView android:id="@android:id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="4dp" - android:layout_marginLeft="4dp" - android:layout_marginRight="4dp" android:textAppearance="?attr/textAppearanceSmall" android:textColor="?attr/textColorPrimary" - android:textSize="12sp" + android:textSize="14sp" android:fontFamily="sans-serif-condensed" android:gravity="top|center_horizontal" - android:minLines="2" - android:maxLines="2" - android:ellipsize="marquee" /> - <!-- Extended activity info to distinguish between duplicate activity names --> + android:lines="1" + android:ellipsize="end" /> + + <!-- Activity name if set, gone for Direct Share targets --> <TextView android:id="@android:id/text2" android:textAppearance="?android:attr/textAppearanceSmall" android:textSize="12sp" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="4dp" - android:layout_marginRight="4dp" - android:minLines="2" - android:maxLines="2" + android:lines="1" android:gravity="top|center_horizontal" - android:ellipsize="marquee" - android:visibility="gone" /> + android:ellipsize="end"/> + </LinearLayout> diff --git a/core/res/res/values-sw600dp/config.xml b/core/res/res/values-sw600dp/config.xml index 6edb88eb5347..e2c8d8a57e85 100644 --- a/core/res/res/values-sw600dp/config.xml +++ b/core/res/res/values-sw600dp/config.xml @@ -51,5 +51,9 @@ 4 - Snap to the long edges in each orientation and magnet to corners --> <integer name="config_pictureInPictureSnapMode">3</integer> + + <!-- Controls whether the nav bar can move from the bottom to the side in landscape. + Only applies if the device display is not square. --> + <bool name="config_navBarCanMove">false</bool> </resources> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 362d01c30487..8f3f25aad182 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1687,6 +1687,17 @@ - {@code false} for apps with targetSdkVersion < 29. --> <attr name="allowAudioPlaybackCapture" format="boolean" /> + <!-- If {@code true} this app allows shared/external storage media to be + a sandboxed view that only contains files owned by the app. + <p> + Sandboxed apps can continue to discover and read media belonging to other + apps via {@code MediaStore}. + <p> + The default value is: + - {@code true} for apps with targetSdkVersion >= 29 (Q). + - {@code false} for apps with targetSdkVersion < 29. + --> + <attr name="allowExternalStorageSandbox" format="boolean" /> </declare-styleable> <!-- The <code>permission</code> tag declares a security permission that can be used to control access from other packages to specific components or diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5f3b328f41b6..5cbe0038f871 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3237,6 +3237,16 @@ 2: gestures only for back, home and overview --> <integer name="config_navBarInteractionMode">0</integer> + <!-- Controls whether the nav bar can move from the bottom to the side in landscape. + Only applies if the device display is not square. --> + <bool name="config_navBarCanMove">true</bool> + + <!-- Controls whether the navigation bar lets through taps. --> + <bool name="config_navBarTapThrough">false</bool> + + <!-- Controls the size of the back gesture inset. --> + <dimen name="config_backGestureInset">0dp</dimen> + <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows. These values are in DPs and will be converted to pixel sizes internally. --> <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string> @@ -3978,4 +3988,30 @@ <!-- Whether or not to enable automatic heap dumps for the system server on debuggable builds. --> <bool name="config_debugEnableAutomaticSystemServerHeapDumps">false</bool> + + <!-- Trigger a heap dump if the system server pss usage exceeds this threshold. 400 MB --> + <integer name="config_debugSystemServerPssThresholdBytes">419430400</integer> + + <!-- See DropBoxManagerService. + The minimum period in milliseconds between broadcasts for entries with low priority + dropbox tags. --> + <integer name="config_dropboxLowPriorityBroadcastRateLimitPeriod">2000</integer> + + <!-- See DropBoxManagerService. + An array of dropbox entry tags to marked as low priority. Low priority broadcasts will be + rated limited to a period defined by config_dropboxLowPriorityBroadcastRateLimitPeriod + (high frequency broadcasts for the tag will be dropped) --> + <string-array name="config_dropboxLowPriorityTags" translatable="false"> + <item>data_app_strictmode</item> + <item>data_app_wtf</item> + <item>keymaster</item> + <item>netstats</item> + <item>system_app_strictmode</item> + <item>system_app_wtf</item> + <item>system_server_strictmode</item> + <item>system_server_wtf</item> + </string-array> + + <!-- Which binder services to include in incident reports containing restricted images. --> + <string-array name="config_restrictedImagesServices" translatable="false"/> </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index feecd020b087..c646fefad9d6 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -47,6 +47,9 @@ <dimen name="navigation_bar_height_landscape">48dp</dimen> <!-- Width of the navigation bar when it is placed vertically on the screen --> <dimen name="navigation_bar_width">48dp</dimen> + <!-- How much we expand the touchable region of the status bar below the notch to catch touches + that just start below the notch. --> + <dimen name="display_cutout_touchable_region_size">12dp</dimen> <!-- EXPERIMENT BEGIN --> <!-- Height of the bottom navigation bar frame; this is different than navigation_bar_height diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 3505994060d8..3bbc03f17efc 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2942,6 +2942,7 @@ <public name="allowClearUserDataOnFailedRestore"/> <public name="allowAudioPlaybackCapture"/> <public name="secureElementName" /> + <public name="allowExternalStorageSandbox"/> </public-group> <public-group type="drawable" first-id="0x010800b4"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index bb473705b416..3a1f30dd3b09 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1582,25 +1582,25 @@ </string-array> <!-- Error message shown when the face hardware can't be accessed. [CHAR LIMIT=50] --> - <string name="face_error_hw_not_available">Face hardware not available.</string> + <string name="face_error_hw_not_available">Can\u2019t verify face. Hardware not available.</string> <!-- Error message shown when the face hardware timer has expired and the user needs to restart the operation. [CHAR LIMIT=50] --> <string name="face_error_timeout">Face timeout reached. Try again.</string> - <!-- Error message shown when the face hardware has run out of room for storing faces. [CHAR LIMIT=50] --> - <string name="face_error_no_space">Face can\u2019t be stored.</string> + <!-- Error message shown when the face hardware has run out of room for storing faces. [CHAR LIMIT=60] --> + <string name="face_error_no_space">Can\u2019t store new face data. Delete an old one first.</string> <!-- Generic error message shown when the face operation (e.g. enrollment or authentication) is canceled. Generally not shown to the user. [CHAR LIMIT=50] --> - <string name="face_error_canceled">Face operation canceled.</string> + <string name="face_error_canceled">Face operation canceled</string> <!-- Generic error message shown when the face authentication operation is canceled due to user input. Generally not shown to the user [CHAR LIMIT=50] --> - <string name="face_error_user_canceled">Face authentication canceled by user.</string> + <string name="face_error_user_canceled">Face authentication canceled by user</string> <!-- Generic error message shown when the face operation fails because too many attempts have been made. [CHAR LIMIT=50] --> <string name="face_error_lockout">Too many attempts. Try again later.</string> - <!-- Generic error message shown when the face operation fails because strong authentication is required. [CHAR LIMIT=50] --> - <string name="face_error_lockout_permanent">Too many attempts. Facial authentication disabled.</string> + <!-- Generic error message shown when the face operation fails because strong authentication is required. [CHAR LIMIT=60] --> + <string name="face_error_lockout_permanent">Too many attempts. Face authentication disabled.</string> <!-- Generic error message shown when the face hardware can't recognize the face. [CHAR LIMIT=50] --> <string name="face_error_unable_to_process">Can\u2019t verify face. Try again.</string> <!-- Generic error message shown when the user has no enrolled face. [CHAR LIMIT=50] --> - <string name="face_error_not_enrolled">You haven\u2019t set up face authentication.</string> + <string name="face_error_not_enrolled">You haven\u2019t set up face authentication</string> <!-- Generic error message shown when the app requests face authentication on a device without a sensor. [CHAR LIMIT=60] --> - <string name="face_error_hw_not_present">Face authentication is not supported on this device.</string> + <string name="face_error_hw_not_present">Face authentication is not supported on this device</string> <!-- Template to be used to name enrolled faces by default. [CHAR LIMIT=10] --> <string name="face_name_template">Face <xliff:g id="faceId" example="1">%d</xliff:g></string> @@ -3305,13 +3305,13 @@ <xliff:g id="proc" example="Android System">%1$s</xliff:g> process has exceeded its memory limit of <xliff:g id="size" example="350MB">%2$s</xliff:g>. A heap dump is available for you to share. Be careful: this heap dump can contain any sensitive personal information - that the process has access to.</string> + that the process has access to, which may include things you\u2019ve typed.</string> <!-- Text of dialog prompting the user to share a heap dump that they initiated [CHAR LIMIT=NONE] --> <string name="dump_heap_ready_text">A heap dump of <xliff:g id="proc" example="com.android.example">%1$s</xliff:g>\u2019s process is available for you to share. Be careful: this heap dump may contain any sensitive personal information - that the process has access to.</string> + that the process has access to, which may include things you\u2019ve typed.</string> <!-- Displayed in the title of the chooser for things to do with text that is to be sent to another application. For example, I can send @@ -4325,10 +4325,10 @@ <!-- Activity starter --> <!-- Toast message for blocking background activity starts feature running in permissive mode --> - <string name="activity_starter_block_bg_activity_starts_permissive">This background activity start from <xliff:g id="packageName" example="com.example">%1$s</xliff:g> will be blocked in future Q builds. See go/q-bg-block.</string> + <string name="activity_starter_block_bg_activity_starts_permissive">This background activity start from <xliff:g id="packageName" example="com.example">%1$s</xliff:g> will be blocked in future Q builds. See g.co/dev/bgblock.</string> <!-- Toast message for blocking background activity starts feature running in enforcing mode --> - <string name="activity_starter_block_bg_activity_starts_enforcing">Background activity start from <xliff:g id="packageName" example="com.example">%1$s</xliff:g> blocked. See go/q-bg-block. </string> + <string name="activity_starter_block_bg_activity_starts_enforcing">Background activity start from <xliff:g id="packageName" example="com.example">%1$s</xliff:g> blocked. See g.co/dev/bgblock. </string> <!-- Keyguard strings --> <!-- Message shown in pattern unlock after some number of unsuccessful attempts --> @@ -4835,7 +4835,7 @@ <string name="package_deleted_device_owner">Deleted by your admin</string> <!-- [CHAR LIMIT=25] String for confirmation button to enable a feature gated by the battery saver warning--> - <string name="confirm_battery_saver">Confirm</string> + <string name="confirm_battery_saver">OK</string> <!-- [CHAR_LIMIT=NONE] Battery saver: Feature description, with a "learn more" link. --> <string name="battery_saver_description_with_learn_more">Battery Saver turns off or restricts background activity, some visual effects \u0026 other high-power features to extend battery life. <annotation id="url">Learn More</annotation></string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 0ce6851f812d..a29a4f8845f5 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1443,6 +1443,11 @@ <java-symbol type="drawable" name="ic_instant_icon_badge_bolt" /> <java-symbol type="drawable" name="emulator_circular_window_overlay" /> <java-symbol type="drawable" name="ic_qs_battery_saver" /> + <java-symbol type="drawable" name="ic_qs_bluetooth" /> + <java-symbol type="drawable" name="ic_qs_airplane" /> + <java-symbol type="drawable" name="ic_qs_flashlight" /> + <java-symbol type="drawable" name="ic_qs_auto_rotate" /> + <java-symbol type="drawable" name="ic_qs_dnd" /> <java-symbol type="drawable" name="sim_light_blue" /> <java-symbol type="drawable" name="sim_light_green" /> @@ -1745,6 +1750,7 @@ <java-symbol type="dimen" name="navigation_bar_height_landscape_car_mode" /> <java-symbol type="dimen" name="navigation_bar_width_car_mode" /> <java-symbol type="dimen" name="status_bar_height" /> + <java-symbol type="dimen" name="display_cutout_touchable_region_size" /> <java-symbol type="dimen" name="quick_qs_offset_height" /> <java-symbol type="dimen" name="quick_qs_total_height" /> <java-symbol type="drawable" name="ic_jog_dial_sound_off" /> @@ -2838,6 +2844,9 @@ <java-symbol type="bool" name="config_forceWindowDrawsStatusBarBackground" /> <java-symbol type="integer" name="config_navBarOpacityMode" /> <java-symbol type="integer" name="config_navBarInteractionMode" /> + <java-symbol type="bool" name="config_navBarCanMove" /> + <java-symbol type="bool" name="config_navBarTapThrough" /> + <java-symbol type="dimen" name="config_backGestureInset" /> <java-symbol type="color" name="system_bar_background_semi_transparent" /> <!-- EditText suggestion popup. --> @@ -3224,6 +3233,7 @@ <java-symbol type="bool" name="config_batterymeterDualTone" /> <java-symbol type="bool" name="config_debugEnableAutomaticSystemServerHeapDumps" /> + <java-symbol type="integer" name="config_debugSystemServerPssThresholdBytes" /> <!-- Accessibility Shortcut --> <java-symbol type="string" name="accessibility_shortcut_warning_dialog_title" /> @@ -3604,6 +3614,7 @@ <java-symbol type="style" name="Theme.DeviceDefault.Light.Dialog.Alert.UserSwitchingDialog" /> <java-symbol type="string" name="battery_saver_description_with_learn_more" /> + <java-symbol type="string" name="confirm_battery_saver" /> <java-symbol type="attr" name="opticalInsetLeft" /> <java-symbol type="attr" name="opticalInsetTop" /> @@ -3679,6 +3690,7 @@ <java-symbol type="integer" name="config_attentionApiTimeout" /> <java-symbol type="string" name="config_incidentReportApproverPackage" /> + <java-symbol type="array" name="config_restrictedImagesServices" /> <!-- Display White-Balance --> <java-symbol type="integer" name="config_displayWhiteBalanceBrightnessSensorRate" /> @@ -3730,4 +3742,7 @@ <java-symbol type="dimen" name="resolver_icon_size"/> <java-symbol type="dimen" name="resolver_badge_size"/> + <!-- For DropBox --> + <java-symbol type="integer" name="config_dropboxLowPriorityBroadcastRateLimitPeriod" /> + <java-symbol type="array" name="config_dropboxLowPriorityTags" /> </resources> diff --git a/core/tests/coretests/src/android/database/TranslatingCursorTest.java b/core/tests/coretests/src/android/database/TranslatingCursorTest.java new file mode 100644 index 000000000000..baca7ef24259 --- /dev/null +++ b/core/tests/coretests/src/android/database/TranslatingCursorTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 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.database; + +import static com.google.common.truth.Truth.assertThat; + +import android.database.TranslatingCursor.Translator; +import android.net.Uri; + +import junit.framework.TestCase; + +public class TranslatingCursorTest extends TestCase { + + public void testDuplicateColumnName() { + MatrixCursor base = new MatrixCursor(new String[] {"_id", "colA", "colB", "colA"}); + base.addRow(new Object[] { 0, "r1_a", "r1_b", "r1_a"}); + base.addRow(new Object[] { 1, "r2_a", "r2_b", "r2_a"}); + Translator translator = (data, idIndex, matchingColumn, cursor) -> data.toUpperCase(); + TranslatingCursor.Config config = new TranslatingCursor.Config(Uri.EMPTY, "_id", "colA"); + TranslatingCursor translating = new TranslatingCursor(base, config, translator, false); + + translating.moveToNext(); + String[] expected = new String[] { "ignored", "R1_A", "r1_b", "R1_A" }; + for (int i = 1; i < translating.getColumnCount(); i++) { + assertThat(translating.getString(i)).isEqualTo(expected[i]); + } + translating.moveToNext(); + expected = new String[] { "ignored", "R2_A", "r2_b", "R2_A" }; + for (int i = 1; i < translating.getColumnCount(); i++) { + assertThat(translating.getString(i)).isEqualTo(expected[i]); + } + } + +} diff --git a/core/tests/coretests/src/android/view/ViewGroupTest.java b/core/tests/coretests/src/android/view/ViewGroupTest.java new file mode 100644 index 000000000000..979a839d2ffe --- /dev/null +++ b/core/tests/coretests/src/android/view/ViewGroupTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static androidx.test.InstrumentationRegistry.getContext; + +import static org.junit.Assert.assertEquals; + +import android.content.Context; +import android.graphics.Region; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + + +/** + * Test basic functions of ViewGroup. + * + * Build/Install/Run: + * atest FrameworksCoreTests:ViewGroupTest + */ +@Presubmit +@SmallTest +public class ViewGroupTest { + + /** + * Test if {@link ViewGroup#subtractObscuredTouchableRegion} works as expected. + * + * The view hierarchy: + * A---B---C + * \ \ + * \ --D + * \ + * E---F + * + * The layer and bounds of each view: + * F -- (invisible) + * E -- + * D ---- + * C ---------- + * B ------ + * A -------- + */ + @Test + public void testSubtractObscuredTouchableRegion() { + final Context context = getContext(); + final TestView viewA = new TestView(context, 8 /* right */); + final TestView viewB = new TestView(context, 6 /* right */); + final TestView viewC = new TestView(context, 10 /* right */); + final TestView viewD = new TestView(context, 4 /* right */); + final TestView viewE = new TestView(context, 2 /* right */); + final TestView viewF = new TestView(context, 2 /* right */); + + viewA.addView(viewB); + viewA.addView(viewE); + viewB.addView(viewC); + viewB.addView(viewD); + viewE.addView(viewF); + + viewF.setVisibility(View.INVISIBLE); + + final Region r = new Region(); + + getUnobscuredTouchableRegion(r, viewA); + assertRegionContainPoint(1 /* x */, r, true /* contain */); + assertRegionContainPoint(3 /* x */, r, true /* contain */); + assertRegionContainPoint(5 /* x */, r, true /* contain */); + assertRegionContainPoint(7 /* x */, r, true /* contain */); + assertRegionContainPoint(9 /* x */, r, false /* contain */); // Outside of bounds + + getUnobscuredTouchableRegion(r, viewB); + assertRegionContainPoint(1 /* x */, r, false /* contain */); // Obscured by E + assertRegionContainPoint(3 /* x */, r, true /* contain */); + assertRegionContainPoint(5 /* x */, r, true /* contain */); + assertRegionContainPoint(7 /* x */, r, false /* contain */); // Outside of bounds + + getUnobscuredTouchableRegion(r, viewC); + assertRegionContainPoint(1 /* x */, r, false /* contain */); // Obscured by D and E + assertRegionContainPoint(3 /* x */, r, false /* contain */); // Obscured by D + assertRegionContainPoint(5 /* x */, r, true /* contain */); + assertRegionContainPoint(7 /* x */, r, false /* contain */); // Outside of parent bounds + + getUnobscuredTouchableRegion(r, viewD); + assertRegionContainPoint(1 /* x */, r, false /* contain */); // Obscured by E + assertRegionContainPoint(3 /* x */, r, true /* contain */); + assertRegionContainPoint(5 /* x */, r, false /* contain */); // Outside of bounds + + getUnobscuredTouchableRegion(r, viewE); + assertRegionContainPoint(1 /* x */, r, true /* contain */); + assertRegionContainPoint(3 /* x */, r, false /* contain */); // Outside of bounds + } + + private static void getUnobscuredTouchableRegion(Region outRegion, View view) { + outRegion.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); + final ViewParent parent = view.getParent(); + if (parent != null) { + parent.subtractObscuredTouchableRegion(outRegion, view); + } + } + + private static void assertRegionContainPoint(int x, Region region, boolean contain) { + assertEquals(String.format("Touchable region must%s contain (%s, 0).", + (contain ? "" : " not"), x), contain, region.contains(x, 0 /* y */)); + } + + private static class TestView extends ViewGroup { + TestView(Context context, int right) { + super(context); + setFrame(0 /* left */, 0 /* top */, right, 1 /* bottom */); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + // We don't layout this view. + } + } +} diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java index 2416de1b7d36..2008537c040a 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureEventTest.java @@ -49,13 +49,15 @@ public class ContentCaptureEventTest { private static final LocusId ID = new LocusId("WHATEVER"); + private static final int NO_SESSION_ID = 0; + // Not using @Mock because it's final - no need to be fancy here.... private final ContentCaptureContext mClientContext = new ContentCaptureContext.Builder(ID).build(); @Test public void testSetAutofillId_null() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED); + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED); assertThrows(NullPointerException.class, () -> event.setAutofillId(null)); assertThat(event.getId()).isNull(); @@ -64,7 +66,7 @@ public class ContentCaptureEventTest { @Test public void testSetAutofillIds_null() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED); + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED); assertThrows(NullPointerException.class, () -> event.setAutofillIds(null)); assertThat(event.getId()).isNull(); @@ -73,7 +75,7 @@ public class ContentCaptureEventTest { @Test public void testAddAutofillId_null() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED); + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED); assertThrows(NullPointerException.class, () -> event.addAutofillId(null)); assertThat(event.getId()).isNull(); @@ -82,7 +84,7 @@ public class ContentCaptureEventTest { @Test public void testSetAutofillId() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED); + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED); final AutofillId id = new AutofillId(108); event.setAutofillId(id); @@ -92,7 +94,7 @@ public class ContentCaptureEventTest { @Test public void testSetAutofillIds() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED); + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED); final AutofillId id = new AutofillId(108); final ArrayList<AutofillId> ids = new ArrayList<>(1); @@ -104,7 +106,7 @@ public class ContentCaptureEventTest { @Test public void testAddAutofillId() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED); + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED); final AutofillId id1 = new AutofillId(108); event.addAutofillId(id1); @@ -119,7 +121,7 @@ public class ContentCaptureEventTest { @Test public void testAddAutofillId_afterSetId() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED); + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED); final AutofillId id1 = new AutofillId(108); event.setAutofillId(id1); @@ -134,7 +136,7 @@ public class ContentCaptureEventTest { @Test public void testAddAutofillId_afterSetIds() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED); + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED); final AutofillId id1 = new AutofillId(108); final ArrayList<AutofillId> ids = new ArrayList<>(1); @@ -163,9 +165,9 @@ public class ContentCaptureEventTest { } private ContentCaptureEvent newEventForSessionStarted() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_SESSION_STARTED) + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_SESSION_STARTED) .setClientContext(mClientContext) - .setParentSessionId("108"); + .setParentSessionId(108); assertThat(event).isNotNull(); return event; } @@ -173,8 +175,8 @@ public class ContentCaptureEventTest { private void assertSessionStartedEvent(ContentCaptureEvent event) { assertThat(event.getType()).isEqualTo(TYPE_SESSION_STARTED); assertThat(event.getEventTime()).isAtLeast(MY_EPOCH); - assertThat(event.getSessionId()).isEqualTo("42"); - assertThat(event.getParentSessionId()).isEqualTo("108"); + assertThat(event.getSessionId()).isEqualTo(42); + assertThat(event.getParentSessionId()).isEqualTo(108); assertThat(event.getId()).isNull(); assertThat(event.getIds()).isNull(); assertThat(event.getText()).isNull(); @@ -186,17 +188,17 @@ public class ContentCaptureEventTest { @Test public void testSessionFinished_directly() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_SESSION_FINISHED) - .setParentSessionId("108"); + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_SESSION_FINISHED) + .setParentSessionId(108); assertThat(event).isNotNull(); assertSessionFinishedEvent(event); } @Test public void testSessionFinished_throughParcel() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_SESSION_FINISHED) + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_SESSION_FINISHED) .setClientContext(mClientContext) // should not be writting to parcel - .setParentSessionId("108"); + .setParentSessionId(108); assertThat(event).isNotNull(); final ContentCaptureEvent clone = cloneThroughParcel(event); assertSessionFinishedEvent(clone); @@ -205,8 +207,8 @@ public class ContentCaptureEventTest { private void assertSessionFinishedEvent(ContentCaptureEvent event) { assertThat(event.getType()).isEqualTo(TYPE_SESSION_FINISHED); assertThat(event.getEventTime()).isAtLeast(MY_EPOCH); - assertThat(event.getSessionId()).isEqualTo("42"); - assertThat(event.getParentSessionId()).isEqualTo("108"); + assertThat(event.getSessionId()).isEqualTo(42); + assertThat(event.getParentSessionId()).isEqualTo(108); assertThat(event.getId()).isNull(); assertThat(event.getIds()).isNull(); assertThat(event.getText()).isNull(); @@ -216,7 +218,7 @@ public class ContentCaptureEventTest { @Test public void testContextUpdated_directly() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_CONTEXT_UPDATED) + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_CONTEXT_UPDATED) .setClientContext(mClientContext); assertThat(event).isNotNull(); assertContextUpdatedEvent(event); @@ -224,7 +226,7 @@ public class ContentCaptureEventTest { @Test public void testContextUpdated_throughParcel() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_CONTEXT_UPDATED) + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_CONTEXT_UPDATED) .setClientContext(mClientContext); assertThat(event).isNotNull(); final ContentCaptureEvent clone = cloneThroughParcel(event); @@ -233,9 +235,9 @@ public class ContentCaptureEventTest { @Test public void testMergeEvent_typeViewTextChanged() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_TEXT_CHANGED) + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_TEXT_CHANGED) .setText("test"); - final ContentCaptureEvent event2 = new ContentCaptureEvent("43", TYPE_VIEW_TEXT_CHANGED) + final ContentCaptureEvent event2 = new ContentCaptureEvent(43, TYPE_VIEW_TEXT_CHANGED) .setText("empty"); event.mergeEvent(event2); @@ -244,14 +246,14 @@ public class ContentCaptureEventTest { @Test public void testMergeEvent_typeViewDisappeared() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED) + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED) .setAutofillId(new AutofillId(1)); - final ContentCaptureEvent event2 = new ContentCaptureEvent("43", TYPE_VIEW_DISAPPEARED) + final ContentCaptureEvent event2 = new ContentCaptureEvent(43, TYPE_VIEW_DISAPPEARED) .setAutofillId(new AutofillId(2)); final ArrayList<AutofillId> autofillIds = new ArrayList<>(); autofillIds.add(new AutofillId(3)); autofillIds.add(new AutofillId(4)); - final ContentCaptureEvent event3 = new ContentCaptureEvent("17", TYPE_VIEW_DISAPPEARED) + final ContentCaptureEvent event3 = new ContentCaptureEvent(17, TYPE_VIEW_DISAPPEARED) .setAutofillIds(autofillIds); event.mergeEvent(event2); @@ -264,24 +266,24 @@ public class ContentCaptureEventTest { @Test public void testMergeEvent_typeViewDisappeared_noIds() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED) + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED) .setAutofillId(new AutofillId(1)); - final ContentCaptureEvent event2 = new ContentCaptureEvent("43", TYPE_VIEW_DISAPPEARED); + final ContentCaptureEvent event2 = new ContentCaptureEvent(43, TYPE_VIEW_DISAPPEARED); assertThrows(IllegalArgumentException.class, () -> event.mergeEvent(event2)); } @Test public void testMergeEvent_nullArgument() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED); + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED); assertThrows(NullPointerException.class, () -> event.mergeEvent(null)); } @Test public void testMergeEvent_differentEventTypes() { - final ContentCaptureEvent event = new ContentCaptureEvent("42", TYPE_VIEW_DISAPPEARED) + final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED) .setText("test").setAutofillId(new AutofillId(1)); - final ContentCaptureEvent event2 = new ContentCaptureEvent("17", TYPE_VIEW_TEXT_CHANGED) + final ContentCaptureEvent event2 = new ContentCaptureEvent(17, TYPE_VIEW_TEXT_CHANGED) .setText("empty").setAutofillId(new AutofillId(2)); event.mergeEvent(event2); @@ -296,8 +298,8 @@ public class ContentCaptureEventTest { private void assertContextUpdatedEvent(ContentCaptureEvent event) { assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_UPDATED); assertThat(event.getEventTime()).isAtLeast(MY_EPOCH); - assertThat(event.getSessionId()).isEqualTo("42"); - assertThat(event.getParentSessionId()).isNull(); + assertThat(event.getSessionId()).isEqualTo(42); + assertThat(event.getParentSessionId()).isEqualTo(NO_SESSION_ID); assertThat(event.getId()).isNull(); assertThat(event.getIds()).isNull(); assertThat(event.getText()).isNull(); diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java index 013408e0bd12..81ce15a4d8d2 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java @@ -39,9 +39,9 @@ import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class ContentCaptureSessionTest { - private ContentCaptureSession mSession1 = new MyContentCaptureSession("111"); + private ContentCaptureSession mSession1 = new MyContentCaptureSession(111); - private ContentCaptureSession mSession2 = new MyContentCaptureSession("2222"); + private ContentCaptureSession mSession2 = new MyContentCaptureSession(2222); @Mock private View mMockView; @@ -60,12 +60,12 @@ public class ContentCaptureSessionTest { assertThat(childId.getViewId()).isEqualTo(42); assertThat(childId.getVirtualChildLongId()).isEqualTo(108L); assertThat(childId.getVirtualChildIntId()).isEqualTo(View.NO_ID); - assertThat(childId.getSessionId()).isEqualTo(mSession1.getIdAsInt()); + assertThat(childId.getSessionId()).isEqualTo(mSession1.getId()); } @Test public void testNewAutofillId_differentSessions() { - assertThat(mSession1.getIdAsInt()).isNotSameAs(mSession2.getIdAsInt()); //sanity check + assertThat(mSession1.getId()).isNotEqualTo(mSession2.getId()); //sanity check final AutofillId parentId = new AutofillId(42); final AutofillId childId1 = mSession1.newAutofillId(parentId, 108L); final AutofillId childId2 = mSession2.newAutofillId(parentId, 108L); @@ -117,7 +117,7 @@ public class ContentCaptureSessionTest { // Cannot use @Spy because we need to pass the session id on constructor private class MyContentCaptureSession extends ContentCaptureSession { - private MyContentCaptureSession(String id) { + private MyContentCaptureSession(int id) { super(id); } diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index 7430c7ab23fc..453bddd9e962 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -23,6 +23,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo; import static com.android.internal.app.ResolverWrapperActivity.sOverrides; import static org.hamcrest.CoreMatchers.is; @@ -32,6 +33,7 @@ import static org.mockito.Mockito.when; import android.content.Intent; import android.content.pm.ResolveInfo; +import android.text.TextUtils; import android.view.View; import android.widget.RelativeLayout; @@ -40,7 +42,10 @@ import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; +import com.android.internal.app.ResolverActivity.ActivityInfoPresentationGetter; +import com.android.internal.app.ResolverActivity.ResolveInfoPresentationGetter; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; +import com.android.internal.app.ResolverDataProvider.PackageManagerMockedInfo; import com.android.internal.widget.ResolverDrawerLayout; import org.junit.Before; @@ -319,6 +324,50 @@ public class ResolverActivityTest { assertThat(chosen[0], is(toChoose)); } + @Test + public void getActivityLabelAndSubLabel() throws Exception { + ActivityInfoPresentationGetter pg; + PackageManagerMockedInfo info; + + info = createPackageManagerMockedInfo(false); + pg = new ActivityInfoPresentationGetter( + info.ctx, 0, info.activityInfo); + assertThat("Label should match app label", pg.getLabel().equals( + info.setAppLabel)); + assertThat("Sublabel should match activity label if set", + pg.getSubLabel().equals(info.setActivityLabel)); + + info = createPackageManagerMockedInfo(true); + pg = new ActivityInfoPresentationGetter( + info.ctx, 0, info.activityInfo); + assertThat("With override permission label should match activity label if set", + pg.getLabel().equals(info.setActivityLabel)); + assertThat("With override permission sublabel should be empty", + TextUtils.isEmpty(pg.getSubLabel())); + } + + @Test + public void getResolveInfoLabelAndSubLabel() throws Exception { + ResolveInfoPresentationGetter pg; + PackageManagerMockedInfo info; + + info = createPackageManagerMockedInfo(false); + pg = new ResolveInfoPresentationGetter( + info.ctx, 0, info.resolveInfo); + assertThat("Label should match app label", pg.getLabel().equals( + info.setAppLabel)); + assertThat("Sublabel should match resolve info label if set", + pg.getSubLabel().equals(info.setResolveInfoLabel)); + + info = createPackageManagerMockedInfo(true); + pg = new ResolveInfoPresentationGetter( + info.ctx, 0, info.resolveInfo); + assertThat("With override permission label should match resolve info label if set", + pg.getLabel().equals(info.setResolveInfoLabel)); + assertThat("With override permission sublabel should be empty", + TextUtils.isEmpty(pg.getSubLabel())); + } + private Intent createSendImageIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java index 850b466ec755..59634f6d261c 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java @@ -17,11 +17,17 @@ package com.android.internal.app; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.os.UserHandle; +import android.test.mock.MockContext; +import android.test.mock.MockPackageManager; +import android.test.mock.MockResources; /** * Utility class used by resolver tests to create mock data @@ -71,6 +77,86 @@ class ResolverDataProvider { return ai; } + static class PackageManagerMockedInfo { + public Context ctx; + public ApplicationInfo appInfo; + public ActivityInfo activityInfo; + public ResolveInfo resolveInfo; + public String setAppLabel; + public String setActivityLabel; + public String setResolveInfoLabel; + } + + static PackageManagerMockedInfo createPackageManagerMockedInfo(boolean hasOverridePermission) { + final String appLabel = "app_label"; + final String activityLabel = "activity_label"; + final String resolveInfoLabel = "resolve_info_label"; + + MockContext ctx = new MockContext() { + @Override + public PackageManager getPackageManager() { + return new MockPackageManager() { + @Override + public int checkPermission(String permName, String pkgName) { + if (hasOverridePermission) return PERMISSION_GRANTED; + return PERMISSION_DENIED; + } + }; + } + + @Override + public Resources getResources() { + return new MockResources() { + @Override + public String getString(int id) throws NotFoundException { + if (id == 1) return appLabel; + if (id == 2) return activityLabel; + if (id == 3) return resolveInfoLabel; + return null; + } + }; + } + }; + + ApplicationInfo appInfo = new ApplicationInfo() { + @Override + public CharSequence loadLabel(PackageManager pm) { + return appLabel; + } + }; + appInfo.labelRes = 1; + + ActivityInfo activityInfo = new ActivityInfo() { + @Override + public CharSequence loadLabel(PackageManager pm) { + return activityLabel; + } + }; + activityInfo.labelRes = 2; + activityInfo.applicationInfo = appInfo; + + ResolveInfo resolveInfo = new ResolveInfo() { + @Override + public CharSequence loadLabel(PackageManager pm) { + return resolveInfoLabel; + } + }; + resolveInfo.activityInfo = activityInfo; + resolveInfo.resolvePackageName = "super.fake.packagename"; + resolveInfo.labelRes = 3; + + PackageManagerMockedInfo mockedInfo = new PackageManagerMockedInfo(); + mockedInfo.activityInfo = activityInfo; + mockedInfo.appInfo = appInfo; + mockedInfo.ctx = ctx; + mockedInfo.resolveInfo = resolveInfo; + mockedInfo.setAppLabel = appLabel; + mockedInfo.setActivityLabel = activityLabel; + mockedInfo.setResolveInfoLabel = resolveInfoLabel; + + return mockedInfo; + } + static Intent createResolverIntent(int i) { return new Intent("intentAction" + i); } diff --git a/core/xsd/Android.bp b/core/xsd/Android.bp index 81669eb290db..738f33076ac9 100644 --- a/core/xsd/Android.bp +++ b/core/xsd/Android.bp @@ -2,5 +2,5 @@ xsd_config { name: "permission", srcs: ["permission.xsd"], api_dir: "schema", - package_name: "com.android.xml.permission", + package_name: "com.android.xml.permission.configfile", } diff --git a/core/xsd/permission.xsd b/core/xsd/permission.xsd index d90863b2c716..2ef2d04d3b34 100644 --- a/core/xsd/permission.xsd +++ b/core/xsd/permission.xsd @@ -60,8 +60,6 @@ <xs:attribute name="uid" type="xs:int"/> </xs:complexType> <xs:complexType name="split-permission"> - <xs:attribute name="name" type="xs:string"/> - <xs:attribute name="targetSdk" type="xs:int"/> <xs:sequence> <xs:element name="library" maxOccurs="unbounded"> <xs:complexType> @@ -69,6 +67,8 @@ </xs:complexType> </xs:element> </xs:sequence> + <xs:attribute name="name" type="xs:string"/> + <xs:attribute name="targetSdk" type="xs:int"/> </xs:complexType> <xs:complexType name="library"> <xs:attribute name="name" type="xs:string"/> @@ -78,6 +78,7 @@ <xs:complexType name="feature"> <xs:attribute name="name" type="xs:string"/> <xs:attribute name="notLowRam" type="xs:string"/> + <xs:attribute name="version" type="xs:int"/> </xs:complexType> <xs:complexType name="unavailable-feature"> <xs:attribute name="name" type="xs:string"/> @@ -124,7 +125,6 @@ <xs:attribute name="package" type="xs:string"/> </xs:complexType> <xs:complexType name="privapp-permissions"> - <xs:attribute name="package" type="xs:string"/> <xs:sequence> <xs:element name="permission" maxOccurs="unbounded"> <xs:complexType> @@ -137,9 +137,9 @@ </xs:complexType> </xs:element> </xs:sequence> + <xs:attribute name="package" type="xs:string"/> </xs:complexType> <xs:complexType name="oem-permissions"> - <xs:attribute name="package" type="xs:string"/> <xs:sequence> <xs:element name="permission" maxOccurs="unbounded"> <xs:complexType> @@ -152,6 +152,7 @@ </xs:complexType> </xs:element> </xs:sequence> + <xs:attribute name="package" type="xs:string"/> </xs:complexType> <xs:complexType name="hidden-api-whitelisted-app"> <xs:attribute name="package" type="xs:string"/> diff --git a/core/xsd/schema/current.txt b/core/xsd/schema/current.txt index 82bb0feac089..c25bc146045b 100644 --- a/core/xsd/schema/current.txt +++ b/core/xsd/schema/current.txt @@ -1,5 +1,5 @@ // Signature format: 2.0 -package com.android.xml.permission { +package com.android.xml.permission.configfile { public class AllowAssociation { ctor public AllowAssociation(); @@ -97,8 +97,10 @@ package com.android.xml.permission { ctor public Feature(); method public String getName(); method public String getNotLowRam(); + method public int getVersion(); method public void setName(String); method public void setNotLowRam(String); + method public void setVersion(int); } public class Group { @@ -125,8 +127,8 @@ package com.android.xml.permission { public class OemPermissions { ctor public OemPermissions(); - method public java.util.List<com.android.xml.permission.OemPermissions.DenyPermission> getDenyPermission(); - method public java.util.List<com.android.xml.permission.OemPermissions.Permission> getPermission(); + method public java.util.List<com.android.xml.permission.configfile.OemPermissions.DenyPermission> getDenyPermission(); + method public java.util.List<com.android.xml.permission.configfile.OemPermissions.Permission> getPermission(); method public String get_package(); method public void set_package(String); } @@ -151,37 +153,37 @@ package com.android.xml.permission { public class Permissions { ctor public Permissions(); - method public java.util.List<com.android.xml.permission.AllowAssociation> getAllowAssociation(); - method public java.util.List<com.android.xml.permission.AllowIgnoreLocationSettings> getAllowIgnoreLocationSettings(); - method public java.util.List<com.android.xml.permission.AllowImplicitBroadcast> getAllowImplicitBroadcast(); - method public java.util.List<com.android.xml.permission.AllowInDataUsageSave> getAllowInDataUsageSave(); - method public java.util.List<com.android.xml.permission.AllowInPowerSave> getAllowInPowerSave(); - method public java.util.List<com.android.xml.permission.AllowInPowerSaveExceptIdle> getAllowInPowerSaveExceptIdle(); - method public java.util.List<com.android.xml.permission.AllowUnthrottledLocation> getAllowUnthrottledLocation(); - method public java.util.List<com.android.xml.permission.AppLink> getAppLink(); - method public java.util.List<com.android.xml.permission.AssignPermission> getAssignPermission(); - method public java.util.List<com.android.xml.permission.BackupTransportWhitelistedService> getBackupTransportWhitelistedService(); - method public java.util.List<com.android.xml.permission.BugreportWhitelisted> getBugreportWhitelisted(); - method public java.util.List<com.android.xml.permission.DefaultEnabledVrApp> getDefaultEnabledVrApp(); - method public java.util.List<com.android.xml.permission.DisabledUntilUsedPreinstalledCarrierApp> getDisabledUntilUsedPreinstalledCarrierApp(); - method public java.util.List<com.android.xml.permission.DisabledUntilUsedPreinstalledCarrierAssociatedApp> getDisabledUntilUsedPreinstalledCarrierAssociatedApp(); - method public java.util.List<com.android.xml.permission.Feature> getFeature(); - method public java.util.List<com.android.xml.permission.Group> getGroup(); - method public java.util.List<com.android.xml.permission.HiddenApiWhitelistedApp> getHiddenApiWhitelistedApp(); - method public java.util.List<com.android.xml.permission.Library> getLibrary(); - method public java.util.List<com.android.xml.permission.OemPermissions> getOemPermissions(); - method public java.util.List<com.android.xml.permission.Permission> getPermission(); - method public java.util.List<com.android.xml.permission.PrivappPermissions> getPrivappPermissions(); - method public java.util.List<com.android.xml.permission.SplitPermission> getSplitPermission(); - method public java.util.List<com.android.xml.permission.SystemUserBlacklistedApp> getSystemUserBlacklistedApp(); - method public java.util.List<com.android.xml.permission.SystemUserWhitelistedApp> getSystemUserWhitelistedApp(); - method public java.util.List<com.android.xml.permission.UnavailableFeature> getUnavailableFeature(); + method public java.util.List<com.android.xml.permission.configfile.AllowAssociation> getAllowAssociation(); + method public java.util.List<com.android.xml.permission.configfile.AllowIgnoreLocationSettings> getAllowIgnoreLocationSettings(); + method public java.util.List<com.android.xml.permission.configfile.AllowImplicitBroadcast> getAllowImplicitBroadcast(); + method public java.util.List<com.android.xml.permission.configfile.AllowInDataUsageSave> getAllowInDataUsageSave(); + method public java.util.List<com.android.xml.permission.configfile.AllowInPowerSave> getAllowInPowerSave(); + method public java.util.List<com.android.xml.permission.configfile.AllowInPowerSaveExceptIdle> getAllowInPowerSaveExceptIdle(); + method public java.util.List<com.android.xml.permission.configfile.AllowUnthrottledLocation> getAllowUnthrottledLocation(); + method public java.util.List<com.android.xml.permission.configfile.AppLink> getAppLink(); + method public java.util.List<com.android.xml.permission.configfile.AssignPermission> getAssignPermission(); + method public java.util.List<com.android.xml.permission.configfile.BackupTransportWhitelistedService> getBackupTransportWhitelistedService(); + method public java.util.List<com.android.xml.permission.configfile.BugreportWhitelisted> getBugreportWhitelisted(); + method public java.util.List<com.android.xml.permission.configfile.DefaultEnabledVrApp> getDefaultEnabledVrApp(); + method public java.util.List<com.android.xml.permission.configfile.DisabledUntilUsedPreinstalledCarrierApp> getDisabledUntilUsedPreinstalledCarrierApp(); + method public java.util.List<com.android.xml.permission.configfile.DisabledUntilUsedPreinstalledCarrierAssociatedApp> getDisabledUntilUsedPreinstalledCarrierAssociatedApp(); + method public java.util.List<com.android.xml.permission.configfile.Feature> getFeature(); + method public java.util.List<com.android.xml.permission.configfile.Group> getGroup(); + method public java.util.List<com.android.xml.permission.configfile.HiddenApiWhitelistedApp> getHiddenApiWhitelistedApp(); + method public java.util.List<com.android.xml.permission.configfile.Library> getLibrary(); + method public java.util.List<com.android.xml.permission.configfile.OemPermissions> getOemPermissions(); + method public java.util.List<com.android.xml.permission.configfile.Permission> getPermission(); + method public java.util.List<com.android.xml.permission.configfile.PrivappPermissions> getPrivappPermissions(); + method public java.util.List<com.android.xml.permission.configfile.SplitPermission> getSplitPermission(); + method public java.util.List<com.android.xml.permission.configfile.SystemUserBlacklistedApp> getSystemUserBlacklistedApp(); + method public java.util.List<com.android.xml.permission.configfile.SystemUserWhitelistedApp> getSystemUserWhitelistedApp(); + method public java.util.List<com.android.xml.permission.configfile.UnavailableFeature> getUnavailableFeature(); } public class PrivappPermissions { ctor public PrivappPermissions(); - method public java.util.List<com.android.xml.permission.PrivappPermissions.DenyPermission> getDenyPermission(); - method public java.util.List<com.android.xml.permission.PrivappPermissions.Permission> getPermission(); + method public java.util.List<com.android.xml.permission.configfile.PrivappPermissions.DenyPermission> getDenyPermission(); + method public java.util.List<com.android.xml.permission.configfile.PrivappPermissions.Permission> getPermission(); method public String get_package(); method public void set_package(String); } @@ -200,7 +202,7 @@ package com.android.xml.permission { public class SplitPermission { ctor public SplitPermission(); - method public java.util.List<com.android.xml.permission.SplitPermission.Library> getLibrary(); + method public java.util.List<com.android.xml.permission.configfile.SplitPermission.Library> getLibrary(); method public String getName(); method public int getTargetSdk(); method public void setName(String); @@ -233,7 +235,7 @@ package com.android.xml.permission { public class XmlParser { ctor public XmlParser(); - method public static com.android.xml.permission.Permissions read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static com.android.xml.permission.configfile.Permissions read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 347edc5dbbd7..ed198e60902b 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -258,6 +258,7 @@ applications that come with the platform <permission name="android.permission.ACTIVITY_EMBEDDING"/> <permission name="android.permission.FORCE_STOP_PACKAGES"/> <permission name="android.permission.GET_APP_OPS_STATS"/> + <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/> <permission name="android.permission.INSTALL_LOCATION_PROVIDER"/> <permission name="android.permission.INSTALL_PACKAGES"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> @@ -271,7 +272,10 @@ applications that come with the platform <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <permission name="android.permission.MOVE_PACKAGE"/> <permission name="android.permission.OBSERVE_APP_USAGE"/> + <permission name="android.permission.NETWORK_SCAN"/> <permission name="android.permission.PACKAGE_USAGE_STATS" /> + <!-- Needed for test only --> + <permission name="android.permission.PACKET_KEEPALIVE_OFFLOAD" /> <permission name="android.permission.POWER_SAVER" /> <permission name="android.permission.READ_FRAME_BUFFER"/> <permission name="android.permission.READ_LOWPAN_CREDENTIAL"/> diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index e4142a933f0f..adc04fb03e2a 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -688,20 +688,20 @@ public abstract class Drawable { * {@link #setColorFilter(int, PorterDuff.Mode)} overrides tint. * </p> * - * @param tintMode A Porter-Duff blending mode + * @param tintMode A Porter-Duff blending mode to apply to the drawable, a value of null sets + * the default Porter-Diff blending mode value + * of {@link PorterDuff.Mode#SRC_IN} * @see #setTint(int) * @see #setTintList(ColorStateList) * * @deprecated use {@link #setTintMode(BlendMode)} instead */ @Deprecated - public void setTintMode(@NonNull PorterDuff.Mode tintMode) { + public void setTintMode(@Nullable PorterDuff.Mode tintMode) { if (!mSetTintModeInvoked) { mSetTintModeInvoked = true; - BlendMode mode = BlendMode.fromValue(tintMode.nativeInt); - if (mode != null) { - setTintMode(mode); - } + BlendMode mode = tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null; + setTintMode(mode != null ? mode : Drawable.DEFAULT_BLEND_MODE); mSetTintModeInvoked = false; } } @@ -716,17 +716,16 @@ public abstract class Drawable { * {@link #setColorFilter(ColorFilter)} * </p> * - * @param blendMode + * @param blendMode BlendMode to apply to the drawable, a value of null sets the default + * blend mode value of {@link BlendMode#SRC_IN} * @see #setTint(int) * @see #setTintList(ColorStateList) */ - public void setTintMode(@NonNull BlendMode blendMode) { + public void setTintMode(@Nullable BlendMode blendMode) { if (!mSetBlendModeInvoked) { mSetBlendModeInvoked = true; PorterDuff.Mode mode = BlendMode.blendModeToPorterDuffMode(blendMode); - if (mode != null) { - setTintMode(mode); - } + setTintMode(mode != null ? mode : Drawable.DEFAULT_TINT_MODE); mSetBlendModeInvoked = false; } } diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index cdb3441d5225..6948bc4fd61b 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -1312,6 +1312,10 @@ public class GradientDrawable extends Drawable { y0 = r.top + (r.bottom - r.top) * st.mCenterY; float radius = st.mGradientRadius; + if (Float.compare(radius, 0.0f) == -1 || Float.isNaN(radius)) { + throw new IllegalArgumentException("Gradient radius must be a valid " + + "number greater than or equal to 0"); + } if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) { // Fall back to parent width or height if intrinsic // size is not specified. @@ -1759,75 +1763,68 @@ public class GradientDrawable extends Drawable { st.mGradientColors[1] = endColor; } - if (st.mGradient == LINEAR_GRADIENT) { - int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle); - angle %= 360; + int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle); + angle %= 360; - if (angle % 45 != 0) { - throw new XmlPullParserException(a.getPositionDescription() - + "<gradient> tag requires 'angle' attribute to " - + "be a multiple of 45"); - } + if (angle % 45 != 0) { + throw new XmlPullParserException(a.getPositionDescription() + + "<gradient> tag requires 'angle' attribute to " + + "be a multiple of 45"); + } - st.mAngle = angle; - - switch (angle) { - case 0: - st.mOrientation = Orientation.LEFT_RIGHT; - break; - case 45: - st.mOrientation = Orientation.BL_TR; - break; - case 90: - st.mOrientation = Orientation.BOTTOM_TOP; - break; - case 135: - st.mOrientation = Orientation.BR_TL; - break; - case 180: - st.mOrientation = Orientation.RIGHT_LEFT; - break; - case 225: - st.mOrientation = Orientation.TR_BL; - break; - case 270: - st.mOrientation = Orientation.TOP_BOTTOM; - break; - case 315: - st.mOrientation = Orientation.TL_BR; - break; - } - } else { - final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius); - if (tv != null) { - final float radius; - final @RadiusType int radiusType; - if (tv.type == TypedValue.TYPE_FRACTION) { - radius = tv.getFraction(1.0f, 1.0f); - - final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT) - & TypedValue.COMPLEX_UNIT_MASK; - if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) { - radiusType = RADIUS_TYPE_FRACTION_PARENT; - } else { - radiusType = RADIUS_TYPE_FRACTION; - } - } else if (tv.type == TypedValue.TYPE_DIMENSION) { - radius = tv.getDimension(r.getDisplayMetrics()); - radiusType = RADIUS_TYPE_PIXELS; + st.mAngle = angle; + + switch (angle) { + case 0: + st.mOrientation = Orientation.LEFT_RIGHT; + break; + case 45: + st.mOrientation = Orientation.BL_TR; + break; + case 90: + st.mOrientation = Orientation.BOTTOM_TOP; + break; + case 135: + st.mOrientation = Orientation.BR_TL; + break; + case 180: + st.mOrientation = Orientation.RIGHT_LEFT; + break; + case 225: + st.mOrientation = Orientation.TR_BL; + break; + case 270: + st.mOrientation = Orientation.TOP_BOTTOM; + break; + case 315: + st.mOrientation = Orientation.TL_BR; + break; + } + + final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius); + if (tv != null) { + final float radius; + final @RadiusType int radiusType; + if (tv.type == TypedValue.TYPE_FRACTION) { + radius = tv.getFraction(1.0f, 1.0f); + + final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT) + & TypedValue.COMPLEX_UNIT_MASK; + if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) { + radiusType = RADIUS_TYPE_FRACTION_PARENT; } else { - radius = tv.getFloat(); - radiusType = RADIUS_TYPE_PIXELS; + radiusType = RADIUS_TYPE_FRACTION; } - - st.mGradientRadius = radius; - st.mGradientRadiusType = radiusType; - } else if (st.mGradient == RADIAL_GRADIENT) { - throw new XmlPullParserException( - a.getPositionDescription() - + "<gradient> tag requires 'gradientRadius' " - + "attribute with radial type"); + } else if (tv.type == TypedValue.TYPE_DIMENSION) { + radius = tv.getDimension(r.getDisplayMetrics()); + radiusType = RADIUS_TYPE_PIXELS; + } else { + radius = tv.getFloat(); + radiusType = RADIUS_TYPE_PIXELS; } + + st.mGradientRadius = radius; + st.mGradientRadiusType = radiusType; } } diff --git a/keystore/tests/Android.bp b/keystore/tests/Android.bp new file mode 100644 index 000000000000..e9b22c140742 --- /dev/null +++ b/keystore/tests/Android.bp @@ -0,0 +1,26 @@ +// Copyright (C) 2017 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test { + name: "KeystoreTests", + // LOCAL_MODULE := keystore + srcs: ["src/**/*.java"], + static_libs: [ + "androidx.test.rules", + "hamcrest-library", + ], + platform_apis: true, + libs: ["android.test.runner"], + certificate: "platform", +} diff --git a/keystore/tests/Android.mk b/keystore/tests/Android.mk deleted file mode 100644 index 99d3197f8bf3..000000000000 --- a/keystore/tests/Android.mk +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (C) 2017 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -# LOCAL_MODULE := keystore -LOCAL_MODULE_TAGS := tests - -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -LOCAL_STATIC_JAVA_LIBRARIES := \ - androidx.test.rules hamcrest-library - -LOCAL_PACKAGE_NAME := KeystoreTests -LOCAL_PRIVATE_PLATFORM_APIS := true - -LOCAL_JAVA_LIBRARIES := android.test.runner - -LOCAL_CERTIFICATE := platform - -include $(BUILD_PACKAGE) - diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index 66a547723b2f..7b7599ff74ec 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -29,6 +29,7 @@ #include "androidfw/Asset.h" #include "androidfw/Idmap.h" +#include "androidfw/misc.h" #include "androidfw/ResourceTypes.h" #include "androidfw/Util.h" @@ -39,8 +40,10 @@ using base::unique_fd; static const std::string kResourcesArsc("resources.arsc"); -ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle, const std::string& path) - : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path) { +ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle, + const std::string& path, + time_t last_mod_time) + : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path), last_mod_time_(last_mod_time) { } std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) { @@ -116,8 +119,10 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl( return {}; } + time_t last_mod_time = getFileModDate(path.c_str()); + // Wrap the handle in a unique_ptr so it gets automatically closed. - std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path)); + std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time)); // Find the resource table. ::ZipString entry_name(kResourcesArsc.c_str()); @@ -248,4 +253,8 @@ bool ApkAssets::ForEachFile(const std::string& root_path, return result == -1; } +bool ApkAssets::IsUpToDate() const { + return last_mod_time_ == getFileModDate(path_.c_str()); +} + } // namespace android diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h index 35bbb5804df4..49fc82bff11e 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -84,6 +84,8 @@ class ApkAssets { return idmap_asset_.get() != nullptr; } + bool IsUpToDate() const; + private: DISALLOW_COPY_AND_ASSIGN(ApkAssets); @@ -95,12 +97,13 @@ class ApkAssets { // Creates an Asset from any file on the file system. static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path); - ApkAssets(ZipArchiveHandle unmanaged_handle, const std::string& path); + ApkAssets(ZipArchiveHandle unmanaged_handle, const std::string& path, time_t last_mod_time); using ZipArchivePtr = std::unique_ptr<ZipArchive, void(*)(ZipArchiveHandle)>; ZipArchivePtr zip_handle_; const std::string path_; + time_t last_mod_time_; std::unique_ptr<Asset> resources_asset_; std::unique_ptr<Asset> idmap_asset_; std::unique_ptr<const LoadedArsc> loaded_arsc_; diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp index fb60a966c48f..89a9b997af97 100644 --- a/libs/hwui/Readback.cpp +++ b/libs/hwui/Readback.cpp @@ -84,6 +84,7 @@ CopyResult Readback::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) { } CopyResult Readback::copyLayerInto(DeferredLayerUpdater* deferredLayer, SkBitmap* bitmap) { + ATRACE_CALL(); if (!mRenderThread.getGrContext()) { return CopyResult::UnknownError; } @@ -104,6 +105,7 @@ CopyResult Readback::copyLayerInto(DeferredLayerUpdater* deferredLayer, SkBitmap CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, Matrix4& texTransform, const Rect& srcRect, SkBitmap* bitmap) { + ATRACE_CALL(); if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { mRenderThread.requireGlContext(); } else { diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp index ceab407cb939..1b9e53b21adb 100644 --- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp @@ -19,6 +19,7 @@ #include "renderthread/VulkanManager.h" #include "renderthread/RenderThread.h" +#include <SkAndroidFrameworkUtils.h> #include <GrBackendDrawableInfo.h> #include <SkImage.h> #include <utils/Color.h> @@ -89,9 +90,29 @@ void VkFunctorDrawHandler::draw(const GrBackendDrawableInfo& info) { VkFunctorDrawable::~VkFunctorDrawable() { } -void VkFunctorDrawable::onDraw(SkCanvas* /*canvas*/) { - LOG_ALWAYS_FATAL("VkFunctorDrawable::onDraw() should never be called."); - // Instead of calling onDraw(), the call should come from onSnapGpuDrawHandler. +void VkFunctorDrawable::onDraw(SkCanvas* canvas) { + // "canvas" is either SkNWayCanvas created by SkiaPipeline::tryCapture (SKP capture use case) or + // AlphaFilterCanvas (used by RenderNodeDrawable to apply alpha in certain cases). + // "VkFunctorDrawable::onDraw" is not invoked for the most common case, when drawing in a GPU + // canvas. + + if (canvas->getGrContext() == nullptr) { + // We're dumping a picture, render a light-blue rectangle instead + SkPaint paint; + paint.setColor(0xFF81D4FA); + canvas->drawRect(mBounds, paint); + } else { + // Handle the case when "canvas" is AlphaFilterCanvas. Find the wrapped GPU canvas. + SkCanvas* gpuCanvas = SkAndroidFrameworkUtils::getBaseWrappedCanvas(canvas); + // Enforce "canvas" must be an AlphaFilterCanvas. For GPU canvas, the call should come from + // onSnapGpuDrawHandler. + LOG_ALWAYS_FATAL_IF( + gpuCanvas == canvas, + "VkFunctorDrawable::onDraw() should not be called with a GPU canvas!"); + + // This will invoke onSnapGpuDrawHandler and regular draw flow. + gpuCanvas->drawDrawable(this); + } } std::unique_ptr<FunctorDrawable::GpuDrawHandler> VkFunctorDrawable::onSnapGpuDrawHandler( diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 1bcb819509af..16240b4e177b 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -162,6 +162,7 @@ void RenderProxy::buildLayer(RenderNode* node) { } bool RenderProxy::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap) { + ATRACE_NAME("TextureView#getBitmap"); auto& thread = RenderThread::getInstance(); return thread.queue().runSync([&]() -> bool { return thread.readback().copyLayerInto(layer, &bitmap) == CopyResult::Success; @@ -347,6 +348,7 @@ void RenderProxy::prepareToDraw(Bitmap& bitmap) { } int RenderProxy::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) { + ATRACE_NAME("HardwareBitmap readback"); RenderThread& thread = RenderThread::getInstance(); if (gettid() == thread.getTid()) { // TODO: fix everything that hits this. We should never be triggering a readback ourselves. diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 6cce31943d03..b76e49ce94a0 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -209,8 +209,8 @@ void RenderThread::requireVkContext() { mVkManager->initialize(); GrContextOptions options; initGrContextOptions(options); - // TODO: get a string describing the SPIR-V compiler version and use it here - cacheManager().configureContext(&options, nullptr, 0); + auto vkDriverVersion = mVkManager->getDriverVersion(); + cacheManager().configureContext(&options, &vkDriverVersion, sizeof(vkDriverVersion)); sk_sp<GrContext> grContext = mVkManager->createContext(options); LOG_ALWAYS_FATAL_IF(!grContext.get()); setGrContext(grContext); @@ -408,12 +408,13 @@ bool RenderThread::isCurrent() { } void RenderThread::preload() { - std::thread eglInitThread([]() { - //TODO: don't load EGL drivers for Vulkan, when HW bitmap uploader is refactored. - eglGetDisplay(EGL_DEFAULT_DISPLAY); - }); - eglInitThread.detach(); - if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { + // EGL driver is always preloaded only if HWUI renders with GL. + if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { + std::thread eglInitThread([]() { + eglGetDisplay(EGL_DEFAULT_DISPLAY); + }); + eglInitThread.detach(); + } else { requireVkContext(); } HardwareBitmapUploader::initialize(); diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index c92909898652..ac62ff47f50a 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -170,6 +170,7 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe VkPhysicalDeviceProperties physDeviceProperties; mGetPhysicalDeviceProperties(mPhysicalDevice, &physDeviceProperties); LOG_ALWAYS_FATAL_IF(physDeviceProperties.apiVersion < VK_MAKE_VERSION(1, 1, 0)); + mDriverVersion = physDeviceProperties.driverVersion; // query to get the initial queue props size uint32_t queueCount; diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index c2d18029c731..54333f326d4f 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -82,6 +82,8 @@ public: sk_sp<GrContext> createContext(const GrContextOptions& options); + uint32_t getDriverVersion() const { return mDriverVersion; } + private: friend class VulkanSurface; // Sets up the VkInstance and VkDevice objects. Also fills out the passed in @@ -178,6 +180,7 @@ private: }; SwapBehavior mSwapBehavior = SwapBehavior::Discard; GrVkExtensions mExtensions; + uint32_t mDriverVersion = 0; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp index 1708d3c0e093..3fed6b09ede2 100644 --- a/libs/hwui/renderthread/VulkanSurface.cpp +++ b/libs/hwui/renderthread/VulkanSurface.cpp @@ -222,7 +222,17 @@ VulkanSurface* VulkanSurface::Create(ANativeWindow* window, ColorMode colorMode, const SkISize maxSize = SkISize::Make(caps.maxImageExtent.width, caps.maxImageExtent.height); ComputeWindowSizeAndTransform(&windowInfo, minSize, maxSize); - windowInfo.bufferCount = std::max<uint32_t>(VulkanSurface::sMaxBufferCount, caps.minImageCount); + int query_value; + int err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value); + if (err != 0 || query_value < 0) { + ALOGE("window->query failed: %s (%d) value=%d", strerror(-err), err, + query_value); + return nullptr; + } + auto min_undequeued_buffers = static_cast<uint32_t>(query_value); + + windowInfo.bufferCount = min_undequeued_buffers + + std::max(VulkanSurface::sTargetBufferCount, caps.minImageCount); if (caps.maxImageCount > 0 && windowInfo.bufferCount > caps.maxImageCount) { // Application must settle for fewer images than desired: windowInfo.bufferCount = caps.maxImageCount; @@ -357,10 +367,9 @@ bool VulkanSurface::UpdateWindow(ANativeWindow* window, const WindowInfo& window return false; } - // Lower layer insists that we have at least two buffers. - err = native_window_set_buffer_count(window, std::max(2, windowInfo.bufferCount)); + err = native_window_set_buffer_count(window, windowInfo.bufferCount); if (err != 0) { - ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffer_count(%d) failed: %s (%d)", + ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffer_count(%zu) failed: %s (%d)", windowInfo.bufferCount, strerror(-err), err); return false; } @@ -392,7 +401,7 @@ VulkanSurface::~VulkanSurface() { } void VulkanSurface::releaseBuffers() { - for (uint32_t i = 0; i < VulkanSurface::sMaxBufferCount; i++) { + for (uint32_t i = 0; i < mWindowInfo.bufferCount; i++) { VulkanSurface::NativeBufferInfo& bufferInfo = mNativeBuffers[i]; if (bufferInfo.buffer.get() != nullptr && bufferInfo.dequeued) { diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h index 418d40f8c056..305483fce2d5 100644 --- a/libs/hwui/renderthread/VulkanSurface.h +++ b/libs/hwui/renderthread/VulkanSurface.h @@ -83,14 +83,19 @@ private: * as private to this class. * */ - static constexpr int sMaxBufferCount = 3; + + // How many buffers we want to be able to use ourselves. We want 1 in active rendering with + // 1 more queued, so 2. This will be added to NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, which is + // how many buffers the consumer needs (eg, 1 for SurfaceFlinger), getting to a typically + // triple-buffered queue as a result. + static constexpr uint32_t sTargetBufferCount = 2; struct WindowInfo { SkISize size; PixelFormat pixelFormat; android_dataspace dataspace; int transform; - int bufferCount; + size_t bufferCount; uint64_t windowUsageFlags; // size of the ANativeWindow if the inverse of transform requires us to swap width/height @@ -111,7 +116,8 @@ private: const SkISize& maxSize); void releaseBuffers(); - NativeBufferInfo mNativeBuffers[VulkanSurface::sMaxBufferCount]; + // TODO: Just use a vector? + NativeBufferInfo mNativeBuffers[android::BufferQueueDefs::NUM_BUFFER_SLOTS]; sp<ANativeWindow> mNativeWindow; WindowInfo mWindowInfo; diff --git a/location/Android.mk b/location/Android.mk deleted file mode 100644 index 50509c6f1a3c..000000000000 --- a/location/Android.mk +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (C) 2010 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. - -LOCAL_PATH := $(call my-dir) - -include $(call all-subdir-makefiles, $(LOCAL_PATH))
\ No newline at end of file diff --git a/location/tests/Android.bp b/location/tests/Android.bp new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/location/tests/Android.bp @@ -0,0 +1 @@ + diff --git a/location/tests/Android.mk b/location/tests/Android.mk deleted file mode 100644 index 57848f353fd4..000000000000 --- a/location/tests/Android.mk +++ /dev/null @@ -1,3 +0,0 @@ -LOCAL_PATH:= $(call my-dir) - -include $(call all-makefiles-under, $(LOCAL_PATH))
\ No newline at end of file diff --git a/location/tests/locationtests/Android.bp b/location/tests/locationtests/Android.bp new file mode 100644 index 000000000000..1a4e2c7ba355 --- /dev/null +++ b/location/tests/locationtests/Android.bp @@ -0,0 +1,19 @@ +android_test { + name: "FrameworksLocationTests", + // Include all test java files. + srcs: ["src/**/*.java"], + libs: [ + "android.test.runner", + "android.test.base", + ], + platform_apis: true, + static_libs: [ + "androidx.test.rules", + "core-test-rules", + "guava", + "mockito-target-minus-junit4", + "frameworks-base-testutils", + "truth-prebuilt", + ], + test_suites: ["device-tests"], +} diff --git a/location/tests/locationtests/Android.mk b/location/tests/locationtests/Android.mk deleted file mode 100644 index 3dcf69426362..000000000000 --- a/location/tests/locationtests/Android.mk +++ /dev/null @@ -1,23 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -# We only want this apk build for tests. -LOCAL_MODULE_TAGS := tests - -# Include all test java files. -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base -LOCAL_PACKAGE_NAME := FrameworksLocationTests -LOCAL_PRIVATE_PLATFORM_APIS := true - -LOCAL_STATIC_JAVA_LIBRARIES := \ - androidx.test.rules \ - core-test-rules \ - guava \ - mockito-target-minus-junit4 \ - frameworks-base-testutils \ - truth-prebuilt \ - -LOCAL_COMPATIBILITY_SUITE := device-tests -include $(BUILD_PACKAGE) diff --git a/media/apex/java/android/media/MediaPlayer2.java b/media/apex/java/android/media/MediaPlayer2.java index 19bb2586af35..87035da3d513 100644 --- a/media/apex/java/android/media/MediaPlayer2.java +++ b/media/apex/java/android/media/MediaPlayer2.java @@ -1546,7 +1546,7 @@ public class MediaPlayer2 implements AutoCloseable, AudioRouting { * Returns the size of the video. * * @return the size of the video. The width and height of size could be 0 if there is no video, - * no display surface was set, or the size has not been determined yet. + * or the size has not been determined yet. * The {@code EventCallback} can be registered via * {@link #registerEventCallback(Executor, EventCallback)} to provide a * notification {@code EventCallback.onVideoSizeChanged} when the size @@ -2870,7 +2870,7 @@ public class MediaPlayer2 implements AutoCloseable, AudioRouting { * Called to indicate the video size * * The video size (width and height) could be 0 if there was no video, - * no display surface was set, or the value was not determined yet. + * or the value was not determined yet. * * @param mp the MediaPlayer2 associated with this callback * @param dsd the DataSourceDesc of this data source diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index efb7f4698797..bf1063d0907f 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; +import android.media.audiopolicy.AudioProductStrategies; import android.os.Build; import android.os.Bundle; import android.os.Parcel; @@ -556,7 +557,7 @@ public final class AudioAttributes implements Parcelable { private int mContentType = CONTENT_TYPE_UNKNOWN; private int mSource = MediaRecorder.AudioSource.AUDIO_SOURCE_INVALID; private int mFlags = 0x0; - private boolean mMuteHapticChannels = false; + private boolean mMuteHapticChannels = true; private HashSet<String> mTags = new HashSet<String>(); private Bundle mBundle; @@ -709,20 +710,7 @@ public final class AudioAttributes implements Parcelable { * @throws IllegalArgumentException if the argument is not a valid value. */ public @NonNull Builder setAllowedCapturePolicy(@CapturePolicy int capturePolicy) { - switch (capturePolicy) { - case ALLOW_CAPTURE_BY_NONE: - mFlags |= FLAG_NO_MEDIA_PROJECTION | FLAG_NO_SYSTEM_CAPTURE; - break; - case ALLOW_CAPTURE_BY_SYSTEM: - mFlags |= FLAG_NO_MEDIA_PROJECTION; - mFlags &= ~FLAG_NO_SYSTEM_CAPTURE; - break; - case ALLOW_CAPTURE_BY_ALL: - mFlags &= ~FLAG_NO_SYSTEM_CAPTURE & ~FLAG_NO_MEDIA_PROJECTION; - break; - default: - throw new IllegalArgumentException("Unknown allow playback capture policy"); - } + mFlags = capturePolicyToFlags(capturePolicy, mFlags); return this; } @@ -794,6 +782,13 @@ public final class AudioAttributes implements Parcelable { */ @UnsupportedAppUsage public Builder setInternalLegacyStreamType(int streamType) { + final AudioProductStrategies ps = new AudioProductStrategies(); + if (ps.size() > 0) { + AudioAttributes attributes = ps.getAudioAttributesForLegacyStreamType(streamType); + if (attributes != null) { + return new Builder(attributes); + } + } switch(streamType) { case AudioSystem.STREAM_VOICE_CALL: mContentType = CONTENT_TYPE_SPEECH; @@ -893,7 +888,7 @@ public final class AudioAttributes implements Parcelable { /** * Specifying if haptic should be muted or not when playing audio-haptic coupled data. - * By default, haptic channels are enabled. + * By default, haptic channels are disabled. * @param muted true to force muting haptic channels. * @return the same Builder instance. */ @@ -1169,6 +1164,10 @@ public final class AudioAttributes implements Parcelable { AudioSystem.STREAM_MUSIC : AudioSystem.STREAM_TTS; } + final AudioProductStrategies ps = new AudioProductStrategies(); + if (ps.size() > 0) { + return ps.getLegacyStreamTypeForAudioAttributes(aa); + } // usage to stream type mapping switch (aa.getUsage()) { case USAGE_MEDIA: @@ -1207,6 +1206,24 @@ public final class AudioAttributes implements Parcelable { } } + static int capturePolicyToFlags(@CapturePolicy int capturePolicy, int flags) { + switch (capturePolicy) { + case ALLOW_CAPTURE_BY_NONE: + flags |= FLAG_NO_MEDIA_PROJECTION | FLAG_NO_SYSTEM_CAPTURE; + break; + case ALLOW_CAPTURE_BY_SYSTEM: + flags |= FLAG_NO_MEDIA_PROJECTION; + flags &= ~FLAG_NO_SYSTEM_CAPTURE; + break; + case ALLOW_CAPTURE_BY_ALL: + flags &= ~FLAG_NO_SYSTEM_CAPTURE & ~FLAG_NO_MEDIA_PROJECTION; + break; + default: + throw new IllegalArgumentException("Unknown allow playback capture policy"); + } + return flags; + } + /** @hide */ @IntDef({ USAGE_UNKNOWN, diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 7cb5e00e7684..dc5c6639375b 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1478,6 +1478,30 @@ public class AudioManager { } } + /** + * Specifying if this audio may or may not be captured by other apps or the system. + * + * The default is {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL}. + * + * Note that each audio track can also set its policy, in which case the most + * restrictive policy is always applied. + * + * @param capturePolicy one of + * {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL}, + * {@link AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM}, + * {@link AudioAttributes#ALLOW_CAPTURE_BY_NONE}. + * @throws IllegalArgumentException if the argument is not a valid value. + */ + public void setAllowedCapturePolicy(@AudioAttributes.CapturePolicy int capturePolicy) { + int flags = AudioAttributes.capturePolicyToFlags(capturePolicy, 0x0); + // TODO: got trough AudioService and save a cache to restore in case of AP crash + // TODO: also pass the package in case multiple packages have the same UID + int result = AudioSystem.setAllowedCapturePolicy(Process.myUid(), flags); + if (result != AudioSystem.AUDIO_STATUS_OK) { + Log.e(TAG, "Could not setAllowedCapturePolicy: " + result); + } + } + //==================================================================== // Offload query /** diff --git a/media/java/android/media/AudioPlaybackCaptureConfiguration.java b/media/java/android/media/AudioPlaybackCaptureConfiguration.java index 9ee6f8740568..bcaef032093d 100644 --- a/media/java/android/media/AudioPlaybackCaptureConfiguration.java +++ b/media/java/android/media/AudioPlaybackCaptureConfiguration.java @@ -29,13 +29,17 @@ import com.android.internal.util.Preconditions; * Configuration for capturing audio played by other apps. * * Only the following audio can be captured: - * - usage MUST be UNKNOWN or GAME or MEDIA. All other usages CAN NOT be capturable. - * - audio attributes MUST NOT have the FLAG_NO_CAPTURE + * - usage MUST be {@link AudioAttributes#USAGE_UNKNOWN} or {@link AudioAttributes#USAGE_GAME} + * or {@link AudioAttributes#USAGE_MEDIA}. All other usages CAN NOT be captured. + * - audio attributes MUST have its ${@link AudioAttributes.Builder#setAllowedCapturePolicy} + * to {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL}. * - played by apps that MUST be in the same user profile as the capturing app * (eg work profile can not capture user profile apps and vice-versa). - * - played by apps that MUST NOT have in their manifest.xml the application - * attribute android:allowAudioPlaybackCapture="false" - * - played by apps that MUST have a targetSdkVersion higher or equal to 29 (Q). + * - played by apps for which the attribute allowAudioPlaybackCapture in their manifest + * MUST either be: + * * set to "true" + * * not set, and their targetSdkVersion MUST be equal or higher to + * {@link android.os.Build.VERSION_CODES#Q}. * * <p>An example for creating a capture configuration for capturing all media playback: * diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index a7760a80974d..2dd7f0fcd12b 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -25,6 +25,7 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; +import android.media.MediaRecorder.Source; import android.media.audiopolicy.AudioMix; import android.media.audiopolicy.AudioPolicy; import android.media.projection.MediaProjection; @@ -539,7 +540,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, * @return the same Builder instance. * @throws IllegalArgumentException */ - public Builder setAudioSource(int source) throws IllegalArgumentException { + public Builder setAudioSource(@Source int source) throws IllegalArgumentException { Preconditions.checkState( mAudioPlaybackCaptureConfiguration == null, ERROR_MESSAGE_SOURCE_MISMATCH); diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index ad255fe3d5a2..d105fa3d4587 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1022,6 +1022,11 @@ public class AudioSystem public static native float getStreamVolumeDB(int stream, int index, int device); + /** + * @see AudioManager#setAllowedCapturePolicy() + */ + public static native int setAllowedCapturePolicy(int uid, int flags); + static boolean isOffloadSupported(@NonNull AudioFormat format, @NonNull AudioAttributes attr) { return native_is_offload_supported(format.getEncoding(), format.getSampleRate(), format.getChannelMask(), format.getChannelIndexMask(), diff --git a/media/java/android/media/HwAudioSource.java b/media/java/android/media/HwAudioSource.java index e53b7e898c6d..01a02f1d9f05 100644 --- a/media/java/android/media/HwAudioSource.java +++ b/media/java/android/media/HwAudioSource.java @@ -224,14 +224,4 @@ public class HwAudioSource extends PlayerBase { return new HwAudioSource(mAudioDeviceInfo, mAudioAttributes); } } - - /** - * Eliminate {@link #deprecateStreamTypeForPlayback(int, String, String)} in API list. - * TODO: remove this pseudo-override function - * @hide - */ - public static void deprecateStreamTypeForPlayback(int streamType, String className, - String opName) throws IllegalArgumentException { - // Do nothing. - } } diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 575a0bb03f7b..63b22df12953 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -18,6 +18,7 @@ package android.media; import android.annotation.CallbackExecutor; import android.annotation.FloatRange; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -42,6 +43,8 @@ import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.RandomAccessFile; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -355,6 +358,22 @@ public class MediaRecorder implements AudioRouting, public static final int HOTWORD = 1999; } + /** @hide */ + @IntDef({ + AudioSource.DEFAULT, + AudioSource.MIC, + AudioSource.VOICE_UPLINK, + AudioSource.VOICE_DOWNLINK, + AudioSource.VOICE_CALL, + AudioSource.CAMCORDER, + AudioSource.VOICE_RECOGNITION, + AudioSource.VOICE_COMMUNICATION, + AudioSource.UNPROCESSED, + AudioSource.VOICE_PERFORMANCE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Source {} + // TODO make AudioSource static (API change) and move this method inside the AudioSource class /** * @hide @@ -547,11 +566,11 @@ public class MediaRecorder implements AudioRouting, * to be specified before setting recording-parameters or encoders. Call * this only before setOutputFormat(). * - * @param audio_source the audio source to use + * @param audioSource the audio source to use * @throws IllegalStateException if it is called after setOutputFormat() * @see android.media.MediaRecorder.AudioSource */ - public native void setAudioSource(int audio_source) + public native void setAudioSource(@Source int audioSource) throws IllegalStateException; /** diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp new file mode 100644 index 000000000000..f0fbc509cc9d --- /dev/null +++ b/media/tests/MediaFrameworkTest/Android.bp @@ -0,0 +1,14 @@ +android_test { + name: "mediaframeworktest", + srcs: ["**/*.java"], + libs: [ + "android.test.runner", + "android.test.base", + ], + static_libs: [ + "mockito-target-minus-junit4", + "androidx.test.rules", + "android-ex-camera2", + ], + platform_apis: true, +} diff --git a/media/tests/MediaFrameworkTest/Android.mk b/media/tests/MediaFrameworkTest/Android.mk deleted file mode 100644 index 167d255af74c..000000000000 --- a/media/tests/MediaFrameworkTest/Android.mk +++ /dev/null @@ -1,18 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_SRC_FILES := $(call all-subdir-java-files) - -LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base - -LOCAL_STATIC_JAVA_LIBRARIES := \ - mockito-target-minus-junit4 \ - androidx.test.rules \ - android-ex-camera2 - -LOCAL_PACKAGE_NAME := mediaframeworktest -LOCAL_PRIVATE_PLATFORM_APIS := true - -include $(BUILD_PACKAGE) diff --git a/media/tests/MtpTests/Android.bp b/media/tests/MtpTests/Android.bp new file mode 100644 index 000000000000..7d2c7c693fa5 --- /dev/null +++ b/media/tests/MtpTests/Android.bp @@ -0,0 +1,6 @@ +android_test { + name: "MtpTests", + srcs: ["**/*.java"], + static_libs: ["androidx.test.rules"], + platform_apis: true, +} diff --git a/media/tests/MtpTests/Android.mk b/media/tests/MtpTests/Android.mk deleted file mode 100644 index 4cee62e7dcbf..000000000000 --- a/media/tests/MtpTests/Android.mk +++ /dev/null @@ -1,13 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_SRC_FILES := $(call all-subdir-java-files) - -LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules - -LOCAL_PACKAGE_NAME := MtpTests -LOCAL_PRIVATE_PLATFORM_APIS := true - -include $(BUILD_PACKAGE) diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml index 44e0a659212a..a5286364dc26 100644 --- a/packages/CaptivePortalLogin/AndroidManifest.xml +++ b/packages/CaptivePortalLogin/AndroidManifest.xml @@ -18,7 +18,7 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.captiveportallogin" - android:versionCode="11" + android:versionCode="200000000" android:versionName="Q-initial"> <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" /> diff --git a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml index 395eac1d2ccb..2fe9d21b0489 100644 --- a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml +++ b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml @@ -18,8 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:fitsSystemWindows="true" - android:visibility="gone"> + android:fitsSystemWindows="true"> <LinearLayout android:id="@+id/container" diff --git a/packages/CarSystemUI/res/layout/notification_center_activity.xml b/packages/CarSystemUI/res/layout/notification_center_activity.xml index 383aba4e400a..5c915b874dde 100644 --- a/packages/CarSystemUI/res/layout/notification_center_activity.xml +++ b/packages/CarSystemUI/res/layout/notification_center_activity.xml @@ -19,7 +19,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/notification_view" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="@color/notification_shade_background_color"> <View android:id="@+id/glass_pane" diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml index 0594dce2ba22..9a074ddd7506 100644 --- a/packages/CarSystemUI/res/layout/super_status_bar.xml +++ b/packages/CarSystemUI/res/layout/super_status_bar.xml @@ -64,7 +64,6 @@ <include layout="@layout/car_top_navigation_bar" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1" /> </LinearLayout> @@ -80,6 +79,13 @@ android:layout_height="match_parent" android:visibility="invisible"/> + <include layout="@layout/notification_center_activity" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginBottom="112dp" + android:visibility="invisible" + /> + <com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_in_front" android:layout_width="match_parent" diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index c9f9dead6e81..d946fbc9520a 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -21,6 +21,7 @@ <string name="config_systemUIFactoryComponent" translatable="false"> com.android.systemui.CarSystemUIFactory </string> + <bool name="config_enableFullscreenUserSwitcher">true</bool> <!-- configure which system ui bars should be displayed --> @@ -28,30 +29,12 @@ <bool name="config_enableRightNavigationBar">false</bool> <bool name="config_enableBottomNavigationBar">true</bool> - <!-- SystemUI Services: The classes of the stuff to start. This is duplicated from core - SystemUi b/c it can't be overlayed at this level for now - --> - <string-array name="config_systemUIServiceComponents" translatable="false"> - <item>com.android.systemui.Dependency$DependencyCreator</item> - <item>com.android.systemui.util.NotificationChannels</item> - <item>com.android.systemui.notifications.NotificationsUI</item> - <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item> - <item>com.android.systemui.keyguard.KeyguardViewMediator</item> - <item>com.android.systemui.recents.Recents</item> - <item>com.android.systemui.volume.VolumeUI</item> - <item>com.android.systemui.stackdivider.Divider</item> - <item>com.android.systemui.SystemBars</item> - <item>com.android.systemui.usb.StorageNotification</item> - <item>com.android.systemui.power.PowerUI</item> - <item>com.android.systemui.media.RingtonePlayer</item> - <item>com.android.systemui.keyboard.KeyboardUI</item> - <item>com.android.systemui.pip.PipUI</item> - <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item> - <item>@string/config_systemUIVendorServiceComponent</item> - <item>com.android.systemui.util.leak.GarbageMonitor$Service</item> - <item>com.android.systemui.LatencyTester</item> - <item>com.android.systemui.globalactions.GlobalActionsComponent</item> - <item>com.android.systemui.ScreenDecorations</item> - <item>com.android.systemui.SliceBroadcastRelayHandler</item> - </string-array> + <bool name="config_hideNavWhenKeyguardBouncerShown">true</bool> + <bool name="config_enablePersistentDockedActivity">false</bool> + <string name="config_persistentDockedActivityIntentUri" translatable="false"></string> + + <!-- How many icons may be shown at once in the system bar. Includes any + slots that may be reused for things like IME control. --> + <integer name="config_maxNotificationIcons">0</integer> + </resources> diff --git a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java deleted file mode 100644 index 2e2f3b7bea11..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java +++ /dev/null @@ -1,469 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.notifications; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.SuppressLint; -import android.app.ActivityManager; -import android.car.Car; -import android.car.CarNotConnectedException; -import android.car.drivingstate.CarUxRestrictionsManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.ServiceConnection; -import android.content.res.Configuration; -import android.graphics.PixelFormat; -import android.os.IBinder; -import android.os.ServiceManager; -import android.util.Log; -import android.view.ContextThemeWrapper; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.WindowManager; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.android.car.notification.CarNotificationListener; -import com.android.car.notification.CarNotificationView; -import com.android.car.notification.CarUxRestrictionManagerWrapper; -import com.android.car.notification.NotificationClickHandlerFactory; -import com.android.car.notification.NotificationViewController; -import com.android.car.notification.PreprocessingManager; -import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.Dependency; -import com.android.systemui.R; -import com.android.systemui.SystemUI; -import com.android.systemui.statusbar.FlingAnimationUtils; -import com.android.systemui.statusbar.car.CarStatusBar; -import com.android.systemui.statusbar.policy.ConfigurationController; - -/** - * Standalone SystemUI for displaying Notifications that have been designed to be used in the car - */ -public class NotificationsUI extends SystemUI - implements ConfigurationController.ConfigurationListener { - - private static final String TAG = "NotificationsUI"; - // used to calculate how fast to open or close the window - private static final float DEFAULT_FLING_VELOCITY = 0; - // max time a fling animation takes - private static final float FLING_ANIMATION_MAX_TIME = 0.5f; - // acceleration rate for the fling animation - private static final float FLING_SPEED_UP_FACTOR = 0.6f; - private CarNotificationListener mCarNotificationListener; - private CarUxRestrictionsManager mCarUxRestrictionsManager; - private NotificationClickHandlerFactory mClickHandlerFactory; - private Car mCar; - private ViewGroup mCarNotificationWindow; - private NotificationViewController mNotificationViewController; - private boolean mIsShowing; - private boolean mIsTracking; - private boolean mNotificationListAtBottom; - private boolean mNotificationListAtBottomAtTimeOfTouch; - private CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper = - new CarUxRestrictionManagerWrapper(); - // Used in the Notification panel touch listener - private GestureDetector mGestureDetector; - // Used in scrollable content of the notifications - private GestureDetector mScrollUpDetector; - private View mContent; - private View.OnTouchListener mOnTouchListener; - private FlingAnimationUtils mFlingAnimationUtils; - private static int sSettleOpenPercentage; - private static int sSettleClosePercentage; - private CarStatusBar mCarStatusBar; - - /** - * Inits the window that hosts the notifications and establishes the connections - * to the car related services. - */ - @Override - public void start() { - sSettleOpenPercentage = mContext.getResources().getInteger( - R.integer.notification_settle_open_percentage); - sSettleClosePercentage = mContext.getResources().getInteger( - R.integer.notification_settle_close_percentage); - WindowManager windowManager = - (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - mFlingAnimationUtils = new FlingAnimationUtils(mContext, - FLING_ANIMATION_MAX_TIME, FLING_SPEED_UP_FACTOR); - mCarNotificationListener = new CarNotificationListener(); - // create a notification click handler that closes the notification ui if the an activity - // is launched successfully - mClickHandlerFactory = new NotificationClickHandlerFactory( - IStatusBarService.Stub.asInterface( - ServiceManager.getService(Context.STATUS_BAR_SERVICE)), - launchResult -> { - if (launchResult == ActivityManager.START_TASK_TO_FRONT - || launchResult == ActivityManager.START_SUCCESS) { - closeCarNotifications(DEFAULT_FLING_VELOCITY); - } - }); - mCarNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper, - mClickHandlerFactory); - mCar = Car.createCar(mContext, mCarConnectionListener); - mCar.connect(); - NotificationGestureListener gestureListener = new NotificationGestureListener(); - mGestureDetector = new GestureDetector(mContext, gestureListener); - mScrollUpDetector = new GestureDetector(mContext, new ScrollUpDetector()); - mOnTouchListener = new NotificationPanelTouchListener(); - mCarNotificationWindow = (ViewGroup) View.inflate(new ContextThemeWrapper(mContext, - R.style.Theme_Notification), - R.layout.navigation_bar_window, null); - mCarNotificationWindow - .setBackgroundColor(mContext.getColor(R.color.notification_shade_background_color)); - inflateNotificationContent(); - - WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, - PixelFormat.TRANSLUCENT); - layoutParams.setTitle("Car Notification Window"); - // start in the hidden state - mCarNotificationWindow.setVisibility(View.GONE); - windowManager.addView(mCarNotificationWindow, layoutParams); - - // Add this object to the SystemUI component registry such that the status bar - // can get a reference to it. - putComponent(NotificationsUI.class, this); - Dependency.get(ConfigurationController.class).addCallback(this); - } - - @SuppressLint("ClickableViewAccessibility") - private void inflateNotificationContent() { - if (mNotificationViewController != null) { - mNotificationViewController.disable(); - } - mCarNotificationWindow.removeAllViews(); - - mContent = View.inflate(new ContextThemeWrapper(mContext, - com.android.car.notification.R.style.Theme_Notification), - R.layout.notification_center_activity, - mCarNotificationWindow); - // set the click handler such that we can dismiss the UI when a notification is clicked - CarNotificationView noteView = mCarNotificationWindow.findViewById(R.id.notification_view); - noteView.setClickHandlerFactory(mClickHandlerFactory); - - mContent.setOnTouchListener(mOnTouchListener); - // set initial translation after size is calculated - mContent.getViewTreeObserver().addOnGlobalLayoutListener( - new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - mContent.getViewTreeObserver().removeOnGlobalLayoutListener(this); - if (!mIsShowing && !mIsTracking) { - mContent.setTranslationY(mContent.getHeight() * -1); - } - } - }); - - RecyclerView notificationList = mCarNotificationWindow.findViewById(R.id.notifications); - // register a scroll listener so we can figure out if we are at the bottom of the - // list of notifications - notificationList.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - if (!notificationList.canScrollVertically(1)) { - mNotificationListAtBottom = true; - return; - } - mNotificationListAtBottom = false; - mNotificationListAtBottomAtTimeOfTouch = false; - } - }); - // add a touch listener such that when the user scrolls up and they are at the bottom - // of the list we can start the closing of the view. - notificationList.setOnTouchListener(new NotificationListTouchListener()); - - // There's a view installed at a higher z-order such that we can intercept the ACTION_DOWN - // to set the initial click state. - mCarNotificationWindow.findViewById(R.id.glass_pane).setOnTouchListener((v, event) -> { - if (event.getActionMasked() == MotionEvent.ACTION_UP) { - mNotificationListAtBottomAtTimeOfTouch = false; - } - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom; - // register the down event with the gesture detectors so then know where the down - // started. This is needed because at this point we don't know which listener - // is going to handle scroll and fling events. - mGestureDetector.onTouchEvent(event); - mScrollUpDetector.onTouchEvent(event); - } - return false; - }); - - mNotificationViewController = new NotificationViewController( - mCarNotificationWindow - .findViewById(com.android.car.notification.R.id.notification_view), - PreprocessingManager.getInstance(mContext), - mCarNotificationListener, - mCarUxRestrictionManagerWrapper); - mNotificationViewController.enable(); - } - - // allows for day night switch - @Override - public void onConfigChanged(Configuration newConfig) { - inflateNotificationContent(); - } - - public void setStatusBar(CarStatusBar carStatusBar) { - mCarStatusBar = carStatusBar; - } - - public View.OnTouchListener getDragDownListener() { - return mOnTouchListener; - } - - /** - * This listener is attached to the notification list UI to intercept gestures if the user - * is scrolling up when the notification list is at the bottom - */ - private class ScrollUpDetector extends GestureDetector.SimpleOnGestureListener { - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - return distanceY > 0; - } - - @Override - public boolean onSingleTapUp(MotionEvent motionEvent) { - closeCarNotifications(DEFAULT_FLING_VELOCITY); - return false; - } - } - - private class NotificationListTouchListener implements View.OnTouchListener { - - @Override - public boolean onTouch(View v, MotionEvent event) { - // reset mNotificationListAtBottomAtTimeOfTouch here since the "glass pane" will not - // get the up event - if (event.getActionMasked() == MotionEvent.ACTION_UP) { - mNotificationListAtBottomAtTimeOfTouch = false; - } - boolean wasScrolledUp = mScrollUpDetector.onTouchEvent(event); - - if (mIsTracking - || (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom - && wasScrolledUp)) { - mOnTouchListener.onTouch(v, event); - // touch event should not be propagated further - return true; - } - return false; - } - } - - /** - * Touch listener installed on the notification panel. It is also used by the Nav and StatusBar - */ - private class NotificationPanelTouchListener implements View.OnTouchListener { - - @Override - public boolean onTouch(View v, MotionEvent event) { - boolean consumed = mGestureDetector.onTouchEvent(event); - if (consumed) { - return true; - } - if (!mIsTracking || event.getActionMasked() != MotionEvent.ACTION_UP) { - return false; - } - - float percentFromBottom = - Math.abs(mContent.getTranslationY() / mContent.getHeight()) * 100; - if (mIsShowing) { - if (percentFromBottom < sSettleOpenPercentage) { - // panel started to close but did not cross minimum threshold thus we open - // it back up - openCarNotifications(DEFAULT_FLING_VELOCITY); - return true; - } - // panel was lifted more than the threshold thus we close it the rest of the way - closeCarNotifications(DEFAULT_FLING_VELOCITY); - return true; - } - - if (percentFromBottom > sSettleClosePercentage) { - // panel was only peeked at thus close it back up - closeCarNotifications(DEFAULT_FLING_VELOCITY); - return true; - } - // panel has been open more than threshold thus open it the rest of the way - openCarNotifications(DEFAULT_FLING_VELOCITY); - return true; - - } - } - - /** - * Listener called by mGestureDetector. This will be initiated from the - * NotificationPanelTouchListener - */ - private class NotificationGestureListener extends GestureDetector.SimpleOnGestureListener { - private static final int SWIPE_UP_MIN_DISTANCE = 75; - private static final int SWIPE_DOWN_MIN_DISTANCE = 25; - private static final int SWIPE_MAX_OFF_PATH = 75; - private static final int SWIPE_THRESHOLD_VELOCITY = 200; - - @Override - public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, - float distanceY) { - if (mCarStatusBar == null || !mCarStatusBar.getIsUserSetup()) { - return true; - } - boolean isDown = event1.getY() - event2.getY() < 0; - // CarStatusBar and NavigationBar are identical so avoid the touch if it - // starts from NavigationBar to open. - if (event1.getRawY() > mCarNotificationWindow.getHeight() && isDown - && mCarNotificationWindow.getVisibility() == View.GONE) { - mIsTracking = false; - return true; - } - mIsTracking = true; - mCarNotificationWindow.setVisibility(View.VISIBLE); - - mContent.setTranslationY(Math.min(mContent.getTranslationY() - distanceY, 0)); - if (mContent.getTranslationY() == 0) { - mIsTracking = false; - } - return true; - } - - @Override - public boolean onFling(MotionEvent event1, MotionEvent event2, - float velocityX, float velocityY) { - if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH - || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { - // swipe was not vertical or was not fast enough - return false; - } - - boolean isUp = velocityY < 0; - float distanceDelta = Math.abs(event1.getY() - event2.getY()); - if (isUp && distanceDelta > SWIPE_UP_MIN_DISTANCE) { - // fling up - mIsTracking = false; - closeCarNotifications(Math.abs(velocityY)); - return true; - - } else if (!isUp && distanceDelta > SWIPE_DOWN_MIN_DISTANCE - && (event1.getRawY() < mCarNotificationWindow.getHeight() - || mCarNotificationWindow.getVisibility() == View.VISIBLE)) { - // fling down - mIsTracking = false; - openCarNotifications(velocityY); - return true; - } - - return false; - } - } - - /** - * Connection callback to establish UX Restrictions - */ - private ServiceConnection mCarConnectionListener = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - try { - mCarUxRestrictionsManager = (CarUxRestrictionsManager) mCar.getCarManager( - Car.CAR_UX_RESTRICTION_SERVICE); - mCarUxRestrictionManagerWrapper - .setCarUxRestrictionsManager(mCarUxRestrictionsManager); - PreprocessingManager preprocessingManager = PreprocessingManager.getInstance( - mContext); - preprocessingManager - .setCarUxRestrictionManagerWrapper(mCarUxRestrictionManagerWrapper); - } catch (CarNotConnectedException e) { - Log.e(TAG, "Car not connected in CarConnectionListener", e); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - Log.e(TAG, "Car service disconnected unexpectedly"); - } - }; - - /** - * Toggles the visibility of the notifications - */ - public void toggleShowingCarNotifications() { - if (mCarNotificationWindow.getVisibility() == View.VISIBLE) { - closeCarNotifications(DEFAULT_FLING_VELOCITY); - return; - } - openCarNotifications(DEFAULT_FLING_VELOCITY); - } - - /** - * Hides the notifications - */ - public void closeCarNotifications(float velocityY) { - float closedTranslation = mContent.getHeight() * -1; - ValueAnimator animator = - ValueAnimator.ofFloat(mContent.getTranslationY(), closedTranslation); - animator.addUpdateListener( - animation -> mContent.setTranslationY((Float) animation.getAnimatedValue())); - mFlingAnimationUtils.apply( - animator, mContent.getTranslationY(), closedTranslation, velocityY); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mCarNotificationWindow.setVisibility(View.GONE); - } - }); - animator.start(); - mNotificationViewController.disable(); - mIsShowing = false; - mIsTracking = false; - RecyclerView notificationListView = mCarNotificationWindow.findViewById(R.id.notifications); - notificationListView.scrollToPosition(0); - } - - /** - * Sets the notifications to visible - */ - public void openCarNotifications(float velocityY) { - if (mCarStatusBar == null || !mCarStatusBar.getIsUserSetup()) { - return; - } - mCarNotificationWindow.setVisibility(View.VISIBLE); - - ValueAnimator animator = ValueAnimator.ofFloat(mContent.getTranslationY(), 0); - animator.addUpdateListener( - animation -> mContent.setTranslationY((Float) animation.getAnimatedValue())); - mFlingAnimationUtils.apply(animator, mContent.getTranslationY(), 0, velocityY); - animator.start(); - - mNotificationViewController.enable(); - mIsShowing = true; - mIsTracking = false; - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java index afefa1b1fa56..a0f2367c65a4 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java @@ -38,6 +38,7 @@ class CarNavigationBarView extends LinearLayout { private CarStatusBar mCarStatusBar; private Context mContext; private View mLockScreenButtons; + // used to wire in open/close gestures for notifications private OnTouchListener mStatusBarWindowTouchListener; @@ -65,26 +66,45 @@ class CarNavigationBarView extends LinearLayout { mDarkIconManager.setShouldLog(true); Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager); } + // needs to be clickable so that it will receive ACTION_MOVE events + setClickable(true); } + // Used to forward touch events even if the touch was initiated from a child component @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mStatusBarWindowTouchListener == null) { - return false; + if (mStatusBarWindowTouchListener != null) { + // forward touch events to the status bar window so it can add a drag down + // windows if required (Notification shade) + mStatusBarWindowTouchListener.onTouch(this, ev); } - // forward touch events to the status bar window so it can add a drag down - // windows if required (Notification shade) - mStatusBarWindowTouchListener.onTouch(this, ev); - return false; + return super.onInterceptTouchEvent(ev); } + void setStatusBar(CarStatusBar carStatusBar) { mCarStatusBar = carStatusBar; - mStatusBarWindowTouchListener = carStatusBar.getStatusBarWindowTouchListener(); + } + + /** + * Set a touch listener that will be called from onInterceptTouchEvent and onTouchEvent + * + * @param statusBarWindowTouchListener The listener to call from touch and intercept touch + */ + void setStatusBarWindowTouchListener(OnTouchListener statusBarWindowTouchListener) { + mStatusBarWindowTouchListener = statusBarWindowTouchListener; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mStatusBarWindowTouchListener != null) { + mStatusBarWindowTouchListener.onTouch(this, event); + } + return super.onTouchEvent(event); } protected void onNotificationsClick(View v) { - mCarStatusBar.toggleCarNotifications(); + mCarStatusBar.togglePanel(); } /** diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index abe9be87fe7b..9bcc1ab90e47 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -16,17 +16,29 @@ package com.android.systemui.statusbar.car; +import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.car.drivingstate.CarDrivingStateEvent; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.util.Log; +import android.view.GestureDetector; import android.view.Gravity; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.car.notification.CarNotificationListener; +import com.android.car.notification.CarNotificationView; +import com.android.car.notification.CarUxRestrictionManagerWrapper; +import com.android.car.notification.NotificationClickHandlerFactory; +import com.android.car.notification.NotificationViewController; +import com.android.car.notification.PreprocessingManager; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.BatteryMeterView; import com.android.systemui.CarSystemUIFactory; @@ -37,7 +49,6 @@ import com.android.systemui.SystemUIFactory; import com.android.systemui.classifier.FalsingLog; import com.android.systemui.classifier.FalsingManager; import com.android.systemui.fragments.FragmentHostManager; -import com.android.systemui.notifications.NotificationsUI; import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.car.CarQSFragment; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -70,7 +81,6 @@ public class CarStatusBar extends StatusBar implements private BatteryMeterView mBatteryMeterView; private Drawable mNotificationPanelBackground; - private ConnectedDeviceSignalController mConnectedDeviceSignalController; private ViewGroup mNavigationBarWindow; private ViewGroup mLeftNavigationBarWindow; private ViewGroup mRightNavigationBarWindow; @@ -90,6 +100,17 @@ public class CarStatusBar extends StatusBar implements private DrivingStateHelper mDrivingStateHelper; private SwitchToGuestTimer mSwitchToGuestTimer; + // The container for the notifications. + private CarNotificationView mNotificationView; + private RecyclerView mNotificationList; + // The state of if the notification list is currently showing the bottom. + private boolean mNotificationListAtBottom; + // Was the notification list at the bottom when the user first touched the screen + private boolean mNotificationListAtBottomAtTimeOfTouch; + // To be attached to the navigation bars such that they can close the notification panel if + // it's open. + private View.OnTouchListener mNavBarNotificationTouchListener; + @Override public void start() { // get the provisioned state before calling the parent class since it's that flow that @@ -223,7 +244,6 @@ public class CarStatusBar extends StatusBar implements * Switch to the keyguard applicable content contained in the nav bars */ private void updateNavBarForKeyguardContent() { - getComponent(NotificationsUI.class).closeCarNotifications(0); if (mNavigationBarView != null) { mNavigationBarView.showKeyguardButtons(); } @@ -255,17 +275,150 @@ public class CarStatusBar extends StatusBar implements // when a device has connected by bluetooth. mBatteryMeterView.setVisibility(View.GONE); }); - addTemperatureViewToController(mStatusBarWindow); + + connectNotificationsUI(); + } + + /** + * Attach the notification listeners and controllers to the UI as well as build all the + * touch listeners needed for opening and closing the notification panel + */ + private void connectNotificationsUI() { + // Attached to the status bar to detect pull down of the notification shade. + GestureDetector openGestureDetector = new GestureDetector(mContext, + new OpenNotificationGestureListener() { + @Override + protected void openNotification() { + animateExpandNotificationsPanel(); + } + }); + // Attached to the notification ui to detect close request of the notification shade. + GestureDetector closeGestureDetector = new GestureDetector(mContext, + new CloseNotificationGestureListener() { + @Override + protected void close() { + animateCollapsePanels(); + } + }); + // Attached to the NavBars to close the notification shade + GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(mContext, + new NavBarCloseNotificationGestureListener() { + @Override + protected void close() { + animateCollapsePanels(); + } + }); + mNavBarNotificationTouchListener = + (v, event) -> navBarCloseNotificationGestureDetector.onTouchEvent(event); + // The following are the ui elements that the user would call the status bar. // This will set the status bar so it they can make call backs. CarNavigationBarView topBar = mStatusBarWindow.findViewById(R.id.car_top_bar); topBar.setStatusBar(this); - CarNavigationBarView qsTopBar = mStatusBarWindow.findViewById(R.id.qs_car_top_bar); - qsTopBar.setStatusBar(this); - getComponent(NotificationsUI.class).setStatusBar(this); + topBar.setStatusBarWindowTouchListener((v1, event1) -> + openGestureDetector.onTouchEvent(event1)); + + NotificationClickHandlerFactory clickHandlerFactory = new NotificationClickHandlerFactory( + mBarService, + launchResult -> { + if (launchResult == ActivityManager.START_TASK_TO_FRONT + || launchResult == ActivityManager.START_SUCCESS) { + animateCollapsePanels(); + } + }); + CarNotificationListener carNotificationListener = new CarNotificationListener(); + CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper = + new CarUxRestrictionManagerWrapper(); + carNotificationListener.registerAsSystemService(mContext, carUxRestrictionManagerWrapper, + clickHandlerFactory); + + mNotificationView = mStatusBarWindow.findViewById(R.id.notification_view); + View glassPane = mStatusBarWindow.findViewById(R.id.glass_pane); + mNotificationView.setClickHandlerFactory(clickHandlerFactory); + + // The glass pane is used to view touch events before passed to the notification list. + // This allows us to initialize gesture listeners and detect when to close the notifications + glassPane.setOnTouchListener((v, event) -> { + if (event.getActionMasked() == MotionEvent.ACTION_UP) { + mNotificationListAtBottomAtTimeOfTouch = false; + } + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom; + // Pass the down event to gesture detector so that it knows where the touch event + // started. + closeGestureDetector.onTouchEvent(event); + } + return false; + }); + mNotificationList = mNotificationView.findViewById(R.id.notifications); + mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + if (!mNotificationList.canScrollVertically(1)) { + mNotificationListAtBottom = true; + return; + } + mNotificationListAtBottom = false; + mNotificationListAtBottomAtTimeOfTouch = false; + } + }); + mNotificationList.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + boolean handled = false; + if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) { + handled = closeGestureDetector.onTouchEvent(event); + } + // Updating the mNotificationListAtBottomAtTimeOfTouch state has to be done after + // the event has been passed to the closeGestureDetector above, such that the + // closeGestureDetector sees the up event before the state has changed. + if (event.getActionMasked() == MotionEvent.ACTION_UP) { + mNotificationListAtBottomAtTimeOfTouch = false; + } + return handled; + } + }); + + NotificationViewController mNotificationViewController = new NotificationViewController( + mNotificationView, + PreprocessingManager.getInstance(mContext), + carNotificationListener, + carUxRestrictionManagerWrapper); + mNotificationViewController.enable(); } @Override + public void animateExpandNotificationsPanel() { + if (!mCommandQueue.panelsEnabled() || !mUserSetup) { + return; + } + // scroll to top + mNotificationList.scrollToPosition(0); + mStatusBarWindowController.setPanelVisible(true); + mNotificationView.setVisibility(View.VISIBLE); + // let the status bar know that the panel is open + setPanelExpanded(true); + } + + @Override + public void animateCollapsePanels(int flags, boolean force, boolean delayed, + float speedUpFactor) { + super.animateCollapsePanels(flags, force, delayed, speedUpFactor); + if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) { + return; + } + mStatusBarWindowController.setStatusBarFocusable(false); + mStatusBarWindow.cancelExpandHelper(); + mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor); + mStatusBarWindowController.setPanelVisible(false); + mNotificationView.setVisibility(View.INVISIBLE); + // let the status bar know that the panel is cloased + setPanelExpanded(false); + } + + + @Override protected QS createDefaultQSFragment() { return new CarQSFragment(); } @@ -338,8 +491,8 @@ public class CarStatusBar extends StatusBar implements lp.setTitle("CarNavigationBar"); lp.windowAnimations = 0; mWindowManager.addView(mNavigationBarWindow, lp); - mNavigationBarWindow.setOnTouchListener(getStatusBarWindowTouchListener()); } + if (mShowLeft) { int width = mContext.getResources().getDimensionPixelSize( R.dimen.car_left_navigation_bar_width); @@ -389,6 +542,7 @@ public class CarStatusBar extends StatusBar implements } mNavigationBarView.setStatusBar(this); addTemperatureViewToController(mNavigationBarView); + mNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener); } private void buildLeft(int layout) { @@ -400,6 +554,7 @@ public class CarStatusBar extends StatusBar implements } mLeftNavigationBarView.setStatusBar(this); addTemperatureViewToController(mLeftNavigationBarView); + mLeftNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener); } @@ -412,6 +567,7 @@ public class CarStatusBar extends StatusBar implements } mRightNavigationBarView.setStatusBar(this); addTemperatureViewToController(mRightNavigationBarView); + mRightNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener); } @Override @@ -435,8 +591,6 @@ public class CarStatusBar extends StatusBar implements pw.println(mCarBatteryController); pw.print(" mBatteryMeterView="); pw.println(mBatteryMeterView); - pw.print(" mConnectedDeviceSignalController="); - pw.println(mConnectedDeviceSignalController); pw.print(" mNavigationBarView="); pw.println(mNavigationBarView); @@ -456,11 +610,6 @@ public class CarStatusBar extends StatusBar implements } } - @Override - protected View.OnTouchListener getStatusBarWindowTouchListener() { - // Gets the car specific notification touch listener - return getComponent(NotificationsUI.class).getDragDownListener(); - } @Override public void showBatteryView() { @@ -585,15 +734,6 @@ public class CarStatusBar extends StatusBar implements true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */); } - @Override - public void animateExpandNotificationsPanel() { - // Because space is usually constrained in the auto use-case, there should not be a - // pinned notification when the shade has been expanded. Ensure this by removing all heads- - // up notifications. - mHeadsUpManager.releaseAllImmediately(); - super.animateExpandNotificationsPanel(); - } - /** * Ensures that relevant child views are appropriately recreated when the device's density * changes. @@ -620,12 +760,73 @@ public class CarStatusBar extends StatusBar implements return mUserSetup; } - public void toggleCarNotifications() { - getComponent(NotificationsUI.class).toggleShowingCarNotifications(); + + // TODO: add settle down/up logic + private static final int SWIPE_UP_MIN_DISTANCE = 75; + private static final int SWIPE_DOWN_MIN_DISTANCE = 25; + private static final int SWIPE_MAX_OFF_PATH = 75; + private static final int SWIPE_THRESHOLD_VELOCITY = 200; + // Only responsible for open hooks. Since once the panel opens it covers all elements + // there is no need to merge with close. + private abstract class OpenNotificationGestureListener extends + GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onFling(MotionEvent event1, MotionEvent event2, + float velocityX, float velocityY) { + if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH + || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { + // swipe was not vertical or was not fast enough + return false; + } + boolean isDown = velocityY > 0; + float distanceDelta = Math.abs(event1.getY() - event2.getY()); + if (isDown && distanceDelta > SWIPE_DOWN_MIN_DISTANCE) { + openNotification(); + return true; + } + + return false; + } + protected abstract void openNotification(); } - @Override - public void maybeEscalateHeadsUp() { - // Never send full screen intent in car. + // to be installed on the open panel notification panel + private abstract class CloseNotificationGestureListener extends + GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onFling(MotionEvent event1, MotionEvent event2, + float velocityX, float velocityY) { + if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH + || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { + // swipe was not vertical or was not fast enough + return false; + } + boolean isUp = velocityY < 0; + float distanceDelta = Math.abs(event1.getY() - event2.getY()); + if (isUp && distanceDelta > SWIPE_UP_MIN_DISTANCE) { + close(); + return true; + } + return false; + } + protected abstract void close(); + } + + // to be installed on the nav bars + private abstract class NavBarCloseNotificationGestureListener extends + CloseNotificationGestureListener { + @Override + public boolean onSingleTapUp(MotionEvent e) { + close(); + return super.onSingleTapUp(e); + } + + @Override + public void onLongPress(MotionEvent e) { + close(); + super.onLongPress(e); + } } } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java index f896cf1bf10c..0a167d9acf98 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java @@ -38,7 +38,9 @@ public class FullscreenUserSwitcher { public FullscreenUserSwitcher(CarStatusBar statusBar, ViewStub containerStub, Context context) { mStatusBar = statusBar; mParent = containerStub.inflate(); - mParent.setVisibility(View.VISIBLE); + // Hide the user grid by default. It will only be made visible by clicking on a cancel + // button in a bouncer. + hide(); View container = mParent.findViewById(R.id.container); // Initialize user grid. @@ -49,10 +51,6 @@ public class FullscreenUserSwitcher { mUserGridView.buildAdapter(); mUserGridView.setUserSelectionListener(this::onUserSelected); - // Hide the user grid by default. It will only be made visible by clicking on a cancel - // button in a bouncer. - hide(); - mShortAnimDuration = container.getResources() .getInteger(android.R.integer.config_shortAnimTime); } @@ -61,21 +59,21 @@ public class FullscreenUserSwitcher { * Makes user grid visible. */ public void show() { - mUserGridView.setVisibility(View.VISIBLE); + mParent.setVisibility(View.VISIBLE); } /** * Hides the user grid. */ public void hide() { - mUserGridView.setVisibility(View.INVISIBLE); + mParent.setVisibility(View.INVISIBLE); } /** * @return {@code true} if user grid is visible, {@code false} otherwise. */ public boolean isVisible() { - return mUserGridView.getVisibility() == View.VISIBLE; + return mParent.getVisibility() == View.VISIBLE; } /** diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml index 76e2fe7f3956..45a59a32ec17 100644 --- a/packages/ExtServices/AndroidManifest.xml +++ b/packages/ExtServices/AndroidManifest.xml @@ -17,7 +17,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" package="android.ext.services" - android:versionCode="1" + android:versionCode="200000000" android:versionName="1" coreApp="true"> @@ -26,6 +26,7 @@ <uses-permission android:name="android.permission.MONITOR_DEFAULT_SMS_PACKAGE" /> <uses-permission android:name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-sdk android:targetSdkVersion="28" @@ -78,6 +79,13 @@ </intent-filter> </service> + <service android:name=".watchdog.ExplicitHealthCheckServiceImpl" + android:permission="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"> + <intent-filter> + <action android:name="android.service.watchdog.ExplicitHealthCheckService" /> + </intent-filter> + </service> + <activity android:name=".notification.CopyCodeActivity" android:exported="false" android:theme="@android:style/Theme.NoDisplay"/> diff --git a/packages/ExtServices/src/android/ext/services/watchdog/ExplicitHealthCheckServiceImpl.java b/packages/ExtServices/src/android/ext/services/watchdog/ExplicitHealthCheckServiceImpl.java new file mode 100644 index 000000000000..040e2ab26874 --- /dev/null +++ b/packages/ExtServices/src/android/ext/services/watchdog/ExplicitHealthCheckServiceImpl.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.ext.services.watchdog; + +import android.content.ComponentName; +import android.content.Intent; +import android.service.watchdog.ExplicitHealthCheckService; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Routes explicit health check requests to the appropriate {@link ExplicitHealthChecker}. + */ +public final class ExplicitHealthCheckServiceImpl extends ExplicitHealthCheckService { + private static final String TAG = "ExplicitHealthCheckServiceImpl"; + // TODO: Add build dependency on NetworkStack stable AIDL so we can stop hard coding class name + private static final String NETWORK_STACK_CONNECTOR_CLASS = + "android.net.INetworkStackConnector"; + // Modified only #onCreate, using concurrent collection to ensure thread visibility + private final Map<String, ExplicitHealthChecker> mSupportedCheckers = new ConcurrentHashMap<>(); + + @Override + public void onCreate() { + super.onCreate(); + initHealthCheckers(); + } + + @Override + public void onRequestHealthCheck(String packageName) { + ExplicitHealthChecker checker = mSupportedCheckers.get(packageName); + if (checker != null) { + checker.request(); + } else { + Log.w(TAG, "Ignoring request for explicit health check for unsupported package " + + packageName); + } + } + + @Override + public void onCancelHealthCheck(String packageName) { + ExplicitHealthChecker checker = mSupportedCheckers.get(packageName); + if (checker != null) { + checker.cancel(); + } else { + Log.w(TAG, "Ignoring request to cancel explicit health check for unsupported package " + + packageName); + } + } + + @Override + public List<String> onGetSupportedPackages() { + return new ArrayList<>(mSupportedCheckers.keySet()); + } + + @Override + public List<String> onGetRequestedPackages() { + List<String> packages = new ArrayList<>(); + Iterator<ExplicitHealthChecker> it = mSupportedCheckers.values().iterator(); + // Could potentially race, where we read a checker#isPending and it changes before we + // return list. However, if it races and it is in the list, the caller might call #cancel + // which would fail, but that is fine. If it races and it ends up *not* in the list, it was + // already cancelled, so there's no need for the caller to cancel it + while (it.hasNext()) { + ExplicitHealthChecker checker = it.next(); + if (checker.isPending()) { + packages.add(checker.getPackageName()); + } + } + return packages; + } + + private void initHealthCheckers() { + Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS); + ComponentName comp = intent.resolveSystemService(getPackageManager(), 0); + if (comp != null) { + String networkStackPackageName = comp.getPackageName(); + mSupportedCheckers.put(networkStackPackageName, + new NetworkChecker(this, networkStackPackageName)); + } else { + // On Go devices, or any device that does not ship the network stack module. + // The network stack will live in system_server process, so no need to monitor. + Log.i(TAG, "Network stack module not found"); + } + } +} diff --git a/packages/ExtServices/src/android/ext/services/watchdog/ExplicitHealthChecker.java b/packages/ExtServices/src/android/ext/services/watchdog/ExplicitHealthChecker.java new file mode 100644 index 000000000000..650878e43d7c --- /dev/null +++ b/packages/ExtServices/src/android/ext/services/watchdog/ExplicitHealthChecker.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.ext.services.watchdog; + +/** + * A type of explicit health check that can be performed on a device, e.g network health check + */ +interface ExplicitHealthChecker { + /** + * Requests a checker to listen to explicit health checks for {@link #getPackageName}. + * {@link #isPending} will now return {@code true}. + */ + void request(); + + /** + * Cancels a pending explicit health check request for {@link #getPackageName}. + * {@link #isPending} will now return {@code false}. + */ + void cancel(); + + /** + * Returns {@code true} if a request is pending, {@code false} otherwise. + */ + boolean isPending(); + + /** + * Returns the package name this checker can make requests for. + */ + String getPackageName(); +} diff --git a/packages/ExtServices/src/android/ext/services/watchdog/NetworkChecker.java b/packages/ExtServices/src/android/ext/services/watchdog/NetworkChecker.java new file mode 100644 index 000000000000..32375e6a9bbc --- /dev/null +++ b/packages/ExtServices/src/android/ext/services/watchdog/NetworkChecker.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.ext.services.watchdog; + +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.service.watchdog.ExplicitHealthCheckService; + +import com.android.internal.annotations.GuardedBy; + +/** + * Observes the network stack via the ConnectivityManager. + */ +final class NetworkChecker extends ConnectivityManager.NetworkCallback + implements ExplicitHealthChecker { + private static final String TAG = "NetworkChecker"; + + private final Object mLock = new Object(); + private final ExplicitHealthCheckService mService; + private final String mPackageName; + @GuardedBy("mLock") + private boolean mIsPending; + + NetworkChecker(ExplicitHealthCheckService service, String packageName) { + mService = service; + mPackageName = packageName; + } + + @Override + public void request() { + synchronized (mLock) { + if (mIsPending) { + return; + } + mService.getSystemService(ConnectivityManager.class).registerNetworkCallback( + new NetworkRequest.Builder().build(), this); + mIsPending = true; + } + } + + @Override + public void cancel() { + synchronized (mLock) { + if (!mIsPending) { + return; + } + mService.getSystemService(ConnectivityManager.class).unregisterNetworkCallback(this); + mIsPending = false; + } + } + + @Override + public boolean isPending() { + synchronized (mLock) { + return mIsPending; + } + } + + @Override + public String getPackageName() { + return mPackageName; + } + + // TODO(b/120598832): Also monitor NetworkCallback#onAvailable to see if we have any + // available networks that may be unusable. This could be additional signal to our heuristics + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) { + synchronized (mLock) { + if (mIsPending + && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) { + mService.notifyHealthCheckPassed(mPackageName); + cancel(); + } + } + } +} diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp index 52534a8fbae7..0bd5c5f59133 100644 --- a/packages/NetworkStack/Android.bp +++ b/packages/NetworkStack/Android.bp @@ -49,6 +49,9 @@ java_defaults { // Resources already included in NetworkStackBase resource_dirs: [], jarjar_rules: "jarjar-rules-shared.txt", + optimize: { + proguard_flags_files: ["proguard.flags"], + }, // The permission configuration *must* be included to ensure security of the device required: ["NetworkStackPermissionStub"], } diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml index b0a7923d65f4..b4588e01dc79 100644 --- a/packages/NetworkStack/AndroidManifest.xml +++ b/packages/NetworkStack/AndroidManifest.xml @@ -17,9 +17,28 @@ */ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.networkstack" - android:sharedUserId="android.uid.networkstack"> + package="com.android.networkstack" + android:sharedUserId="android.uid.networkstack" + android:versionCode="200000000" + android:versionName="29 system image" +> + <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" /> + + <!-- Permissions must be defined here, and not in the base manifest, as the network stack + running in the system server process does not need any permission, and having privileged + permissions added would cause crashes on startup unless they are also added to the + privileged permissions whitelist for that package. --> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> + <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> + <!-- Send latency broadcast as current user --> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> + <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <!-- Signature permission defined in NetworkStackStub --> <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" /> <application> diff --git a/packages/NetworkStack/AndroidManifestBase.xml b/packages/NetworkStack/AndroidManifestBase.xml index f69e4b2bb795..69a4da46e088 100644 --- a/packages/NetworkStack/AndroidManifestBase.xml +++ b/packages/NetworkStack/AndroidManifestBase.xml @@ -20,15 +20,6 @@ package="com.android.networkstack" android:versionCode="11" android:versionName="Q-initial"> - <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> - <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> - <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> - <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> - <!-- Send latency broadcast as current user --> - <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> - <uses-permission android:name="android.permission.WAKE_LOCK" /> - <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> <application android:label="NetworkStack" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/NetworkStack/proguard.flags b/packages/NetworkStack/proguard.flags new file mode 100644 index 000000000000..c60f6c338d83 --- /dev/null +++ b/packages/NetworkStack/proguard.flags @@ -0,0 +1,9 @@ +-keepclassmembers class android.net.ip.IpClient { + static final int CMD_*; + static final int EVENT_*; +} + +-keepclassmembers class android.net.dhcp.DhcpClient { + static final int CMD_*; + static final int EVENT_*; +} diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java index 3dd90eeff767..d2f32591cbbe 100644 --- a/packages/NetworkStack/src/android/net/apf/ApfFilter.java +++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java @@ -74,6 +74,7 @@ import java.net.SocketException; import java.net.UnknownHostException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; @@ -282,6 +283,7 @@ public class ApfFilter { private static final byte[] ETH_BROADCAST_MAC_ADDRESS = {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }; // TODO: Make these offsets relative to end of link-layer header; don't include ETH_HEADER_LEN. + private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2; private static final int IPV4_FRAGMENT_OFFSET_OFFSET = ETH_HEADER_LEN + 6; // Endianness is not an issue for this constant because the APF interpreter always operates in // network byte order. @@ -881,10 +883,23 @@ public class ApfFilter { protected final TcpKeepaliveAckData mPacket; protected final byte[] mSrcDstAddr; + protected final byte[] mPortSeqAckFingerprint; TcpKeepaliveAck(final TcpKeepaliveAckData packet, final byte[] srcDstAddr) { mPacket = packet; mSrcDstAddr = srcDstAddr; + mPortSeqAckFingerprint = generatePortSeqAckFingerprint(mPacket.srcPort, + mPacket.dstPort, mPacket.seq, mPacket.ack); + } + + static byte[] generatePortSeqAckFingerprint(int srcPort, int dstPort, int seq, int ack) { + final ByteBuffer fp = ByteBuffer.allocate(12); + fp.order(ByteOrder.BIG_ENDIAN); + fp.putShort((short) srcPort); + fp.putShort((short) dstPort); + fp.putInt(seq); + fp.putInt(ack); + return fp.array(); } static byte[] concatArrays(final byte[]... arr) { @@ -919,10 +934,6 @@ public class ApfFilter { private class TcpKeepaliveAckV4 extends TcpKeepaliveAck { private static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12; - private static final int IPV4_TCP_SRC_PORT_OFFSET = 0; - private static final int IPV4_TCP_DST_PORT_OFFSET = 2; - private static final int IPV4_TCP_SEQ_OFFSET = 4; - private static final int IPV4_TCP_ACK_OFFSET = 8; TcpKeepaliveAckV4(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { this(new TcpKeepaliveAckData(sentKeepalivePacket)); @@ -934,12 +945,12 @@ public class ApfFilter { @Override void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked(); - gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET); - gen.addJumpIfR0NotEquals(IPPROTO_TCP, nextFilterLabel); + gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel); - // Pass the packet if it's not zero-sized : + // Skip to the next filter if it's not zero-sized : + // TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0 // Load the IP header size into R1 gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); // Load the TCP header size into R0 (it's indexed by R1) @@ -947,27 +958,18 @@ public class ApfFilter { // Size offset is in the top nibble, but it must be multiplied by 4, and the two // top bits of the low nibble are guaranteed to be zeroes. Right-shift R0 by 2. gen.addRightShift(2); - // R0 += R1 -> R0 contains TCP + IP headers lenght - gen.addAddR1(); - // Add the Ethernet header length to R0. - gen.addLoadImmediate(Register.R1, ETH_HEADER_LEN); + // R0 += R1 -> R0 contains TCP + IP headers length gen.addAddR1(); - // Compare total length of headers to the size of the packet. - gen.addLoadFromMemory(Register.R1, gen.PACKET_SIZE_MEMORY_SLOT); + // Load IPv4 total length + gen.addLoad16(Register.R1, IPV4_TOTAL_LENGTH_OFFSET); gen.addNeg(Register.R0); gen.addAddR1(); gen.addJumpIfR0NotEquals(0, nextFilterLabel); - // Add IPv4 header length gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); - gen.addLoad16Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_SRC_PORT_OFFSET); - gen.addJumpIfR0NotEquals(mPacket.srcPort, nextFilterLabel); - gen.addLoad16Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_DST_PORT_OFFSET); - gen.addJumpIfR0NotEquals(mPacket.dstPort, nextFilterLabel); - gen.addLoad32Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_SEQ_OFFSET); - gen.addJumpIfR0NotEquals(mPacket.seq, nextFilterLabel); - gen.addLoad32Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_ACK_OFFSET); - gen.addJumpIfR0NotEquals(mPacket.ack, nextFilterLabel); + gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN); + gen.addAddR1(); + gen.addJumpIfBytesNotEqual(Register.R0, mPortSeqAckFingerprint, nextFilterLabel); maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK); gen.addJump(mCountAndDropLabel); @@ -1169,9 +1171,10 @@ public class ApfFilter { gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel); } - // If any keepalive filters, - generateKeepaliveFilter(gen); + // If any keepalive filter matches, drop + generateV4KeepaliveFilters(gen); + // Otherwise, this is an IPv4 unicast, pass // If L2 broadcast packet, drop. // TODO: can we invert this condition to fall through to the common pass case below? maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST); @@ -1180,7 +1183,7 @@ public class ApfFilter { maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST); gen.addJump(mCountAndDropLabel); } else { - generateKeepaliveFilter(gen); + generateV4KeepaliveFilters(gen); } // Otherwise, pass @@ -1188,12 +1191,25 @@ public class ApfFilter { gen.addJump(mCountAndPassLabel); } - private void generateKeepaliveFilter(ApfGenerator gen) throws IllegalInstructionException { + private void generateV4KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { + final String skipV4KeepaliveFilter = "skip_v4_keepalive_filter"; + final boolean haveV4KeepaliveAcks = NetworkStackUtils.any(mKeepaliveAcks, + ack -> ack instanceof TcpKeepaliveAckV4); + + // If no keepalive acks + if (!haveV4KeepaliveAcks) return; + + // If not tcp, skip keepalive filters + gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET); + gen.addJumpIfR0NotEquals(IPPROTO_TCP, skipV4KeepaliveFilter); + // Drop IPv4 Keepalive acks for (int i = 0; i < mKeepaliveAcks.size(); ++i) { final TcpKeepaliveAck ack = mKeepaliveAcks.valueAt(i); if (ack instanceof TcpKeepaliveAckV4) ack.generateFilterLocked(gen); } + + gen.defineLabel(skipV4KeepaliveFilter); } /** @@ -1244,11 +1260,14 @@ public class ApfFilter { maybeSetupCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST); gen.addLoad8(Register.R0, IPV6_DEST_ADDR_OFFSET); gen.addJumpIfR0Equals(0xff, mCountAndDropLabel); + // If any keepalive filter matches, drop + generateV6KeepaliveFilters(gen); // Not multicast. Pass. maybeSetupCounter(gen, Counter.PASSED_IPV6_UNICAST_NON_ICMP); gen.addJump(mCountAndPassLabel); gen.defineLabel(skipIPv6MulticastFilterLabel); } else { + generateV6KeepaliveFilters(gen); // If not ICMPv6, pass. maybeSetupCounter(gen, Counter.PASSED_IPV6_NON_ICMP); gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, mCountAndPassLabel); @@ -1272,12 +1291,27 @@ public class ApfFilter { maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA); gen.addJump(mCountAndDropLabel); gen.defineLabel(skipUnsolicitedMulticastNALabel); + } + + private void generateV6KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { + final String skipV6KeepaliveFilter = "skip_v6_keepalive_filter"; + final boolean haveV6KeepaliveAcks = NetworkStackUtils.any(mKeepaliveAcks, + ack -> ack instanceof TcpKeepaliveAckV6); + + // If no keepalive acks + if (!haveV6KeepaliveAcks) return; + + // If not tcp, skip keepalive filters + gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET); + gen.addJumpIfR0NotEquals(IPPROTO_TCP, skipV6KeepaliveFilter); // Drop IPv6 Keepalive acks for (int i = 0; i < mKeepaliveAcks.size(); ++i) { final TcpKeepaliveAck ack = mKeepaliveAcks.valueAt(i); if (ack instanceof TcpKeepaliveAckV6) ack.generateFilterLocked(gen); } + + gen.defineLabel(skipV6KeepaliveFilter); } /** @@ -1294,6 +1328,8 @@ public class ApfFilter { * <li>Pass all non-IPv4 and non-IPv6 packets, * <li>Drop IPv6 ICMPv6 NAs to ff02::1. * <li>Drop IPv6 ICMPv6 RSs. + * <li>Filter IPv4 packets (see generateIPv4FilterLocked()) + * <li>Filter IPv6 packets (see generateIPv6FilterLocked()) * <li>Let execution continue off the end of the program for IPv6 ICMPv6 packets. This allows * insertion of RA filters here, or if there aren't any, just passes the packets. * </ul> @@ -1737,7 +1773,7 @@ public class ApfFilter { } pw.decreaseIndent(); - pw.println("Keepalive filter:"); + pw.println("Keepalive filters:"); pw.increaseIndent(); for (int i = 0; i < mKeepaliveAcks.size(); ++i) { final TcpKeepaliveAck keepaliveAck = mKeepaliveAcks.valueAt(i); diff --git a/packages/NetworkStack/src/android/net/apf/ApfGenerator.java b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java index 809327a0b79d..44ce2db8547c 100644 --- a/packages/NetworkStack/src/android/net/apf/ApfGenerator.java +++ b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java @@ -108,7 +108,7 @@ public class ApfGenerator { private String mLabel; // When mOpcode == Opcodes.JNEBS: private byte[] mCompareBytes; - // Offset in bytes from the begining of this program. Set by {@link ApfGenerator#generate}. + // Offset in bytes from the beginning of this program. Set by {@link ApfGenerator#generate}. int offset; Instruction(Opcodes opcode, Register register) { @@ -431,7 +431,7 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to load the byte at offset {@code offset} - * bytes from the begining of the packet into {@code register}. + * bytes from the beginning of the packet into {@code register}. */ public ApfGenerator addLoad8(Register register, int offset) { Instruction instruction = new Instruction(Opcodes.LDB, register); @@ -442,7 +442,7 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to load 16-bits at offset {@code offset} - * bytes from the begining of the packet into {@code register}. + * bytes from the beginning of the packet into {@code register}. */ public ApfGenerator addLoad16(Register register, int offset) { Instruction instruction = new Instruction(Opcodes.LDH, register); @@ -453,7 +453,7 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to load 32-bits at offset {@code offset} - * bytes from the begining of the packet into {@code register}. + * bytes from the beginning of the packet into {@code register}. */ public ApfGenerator addLoad32(Register register, int offset) { Instruction instruction = new Instruction(Opcodes.LDW, register); @@ -464,7 +464,7 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to load a byte from the packet into - * {@code register}. The offset of the loaded byte from the begining of the packet is + * {@code register}. The offset of the loaded byte from the beginning of the packet is * the sum of {@code offset} and the value in register R1. */ public ApfGenerator addLoad8Indexed(Register register, int offset) { diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java index c6dd0117477b..79d6a554e251 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java @@ -126,6 +126,7 @@ public class DhcpClient extends StateMachine { // DhcpClient uses IpClient's handler. private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE; + // Below constants are picked up by MessageUtils and exempt from ProGuard optimization. /* Commands from controller to start/stop DHCP */ public static final int CMD_START_DHCP = PUBLIC_BASE + 1; public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2; diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java index 346ac68407de..c1f178a7f5f1 100644 --- a/packages/NetworkStack/src/android/net/ip/IpClient.java +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -282,6 +282,7 @@ public class IpClient extends StateMachine { public static final String DUMP_ARG_CONFIRM = "confirm"; + // Below constants are picked up by MessageUtils and exempt from ProGuard optimization. private static final int CMD_TERMINATE_AFTER_STOP = 1; private static final int CMD_STOP = 2; private static final int CMD_START = 3; @@ -1388,8 +1389,8 @@ public class IpClient extends StateMachine { apfConfig.apfCapabilities = mConfiguration.mApfCapabilities; apfConfig.multicastFilter = mMulticastFiltering; // Get the Configuration for ApfFilter from Context - apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames(mContext); - apfConfig.ethTypeBlackList = ApfCapabilities.getApfEthTypeBlackList(mContext); + apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames(); + apfConfig.ethTypeBlackList = ApfCapabilities.getApfEtherTypeBlackList(); mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback); // TODO: investigate the effects of any multicast filtering racing/interfering with the // rest of this IP configuration startup. diff --git a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java index 481dbdadbac0..670563cf391e 100644 --- a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java +++ b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java @@ -17,10 +17,14 @@ package android.net.util; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.SparseArray; import java.io.FileDescriptor; import java.io.IOException; import java.util.List; +import java.util.function.Predicate; + /** * Collection of utilities for the network stack. @@ -65,4 +69,32 @@ public class NetworkStackUtils { } return array; } + + /** + * @return True if there exists at least one element in the sparse array for which + * condition {@code predicate} + */ + public static <T> boolean any(SparseArray<T> array, Predicate<T> predicate) { + for (int i = 0; i < array.size(); ++i) { + if (predicate.test(array.valueAt(i))) { + return true; + } + } + return false; + } + + /** + * Look up the value of a property for a particular namespace from {@link DeviceConfig}. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param defaultValue The value to return if the property does not exist or has no non-null + * value. + * @return the corresponding value, or defaultValue if none exists. + */ + @Nullable + public static String getDeviceConfigProperty(@NonNull String namespace, @NonNull String name, + @Nullable String defaultValue) { + // TODO: Link to DeviceConfig API once it is ready. + return defaultValue; + } } diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index 19e9108d2fc8..63f057caa26e 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -35,7 +35,9 @@ import android.net.INetd; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkStackConnector; +import android.net.LinkProperties; import android.net.Network; +import android.net.NetworkCapabilities; import android.net.PrivateDnsConfigParcel; import android.net.dhcp.DhcpServer; import android.net.dhcp.DhcpServingParams; @@ -307,9 +309,9 @@ public class NetworkStackService extends Service { } @Override - public void notifyNetworkConnected() { + public void notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) { checkNetworkStackCallingPermission(); - mNm.notifyNetworkConnected(); + mNm.notifyNetworkConnected(lp, nc); } @Override @@ -319,15 +321,15 @@ public class NetworkStackService extends Service { } @Override - public void notifyLinkPropertiesChanged() { + public void notifyLinkPropertiesChanged(LinkProperties lp) { checkNetworkStackCallingPermission(); - mNm.notifyLinkPropertiesChanged(); + mNm.notifyLinkPropertiesChanged(lp); } @Override - public void notifyNetworkCapabilitiesChanged() { + public void notifyNetworkCapabilitiesChanged(NetworkCapabilities nc) { checkNetworkStackCallingPermission(); - mNm.notifyNetworkCapabilitiesChanged(); + mNm.notifyNetworkCapabilitiesChanged(nc); } } } diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index f3476ed1566f..c000fc6b721d 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -78,6 +78,7 @@ import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.RingBufferIndices; @@ -221,19 +222,31 @@ public class NetworkMonitor extends StateMachine { * Message to self indicating captive portal detection is completed. * obj = CaptivePortalProbeResult for detection result; */ - public static final int CMD_PROBE_COMPLETE = 16; + private static final int CMD_PROBE_COMPLETE = 16; /** * ConnectivityService notifies NetworkMonitor of DNS query responses event. * arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query. */ - public static final int EVENT_DNS_NOTIFICATION = 17; + private static final int EVENT_DNS_NOTIFICATION = 17; /** * ConnectivityService notifies NetworkMonitor that the user accepts partial connectivity and * NetworkMonitor should ignore the https probe. */ - public static final int EVENT_ACCEPT_PARTIAL_CONNECTIVITY = 18; + private static final int EVENT_ACCEPT_PARTIAL_CONNECTIVITY = 18; + + /** + * ConnectivityService notifies NetworkMonitor of changed LinkProperties. + * obj = new LinkProperties. + */ + private static final int EVENT_LINK_PROPERTIES_CHANGED = 19; + + /** + * ConnectivityService notifies NetworkMonitor of changed NetworkCapabilities. + * obj = new NetworkCapabilities. + */ + private static final int EVENT_NETWORK_CAPABILITIES_CHANGED = 20; // Start mReevaluateDelayMs at this value and double. private static final int INITIAL_REEVALUATE_DELAY_MS = 1000; @@ -379,10 +392,8 @@ public class NetworkMonitor extends StateMachine { mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold(); mDataStallEvaluationType = getDataStallEvalutionType(); - // mLinkProperties and mNetworkCapbilities must never be null or we will NPE. - // Provide empty objects in case we are started and the network disconnects before - // we can ever fetch them. - // TODO: Delete ASAP. + // Provide empty LinkProperties and NetworkCapabilities to make sure they are never null, + // even before notifyNetworkConnected. mLinkProperties = new LinkProperties(); mNetworkCapabilities = new NetworkCapabilities(null); } @@ -434,8 +445,16 @@ public class NetworkMonitor extends StateMachine { /** * Send a notification to NetworkMonitor indicating that the network is now connected. */ - public void notifyNetworkConnected() { - sendMessage(CMD_NETWORK_CONNECTED); + public void notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) { + sendMessage(CMD_NETWORK_CONNECTED, new Pair<>( + new LinkProperties(lp), new NetworkCapabilities(nc))); + } + + private void updateConnectedNetworkAttributes(Message connectedMsg) { + final Pair<LinkProperties, NetworkCapabilities> attrs = + (Pair<LinkProperties, NetworkCapabilities>) connectedMsg.obj; + mLinkProperties = attrs.first; + mNetworkCapabilities = attrs.second; } /** @@ -448,37 +467,15 @@ public class NetworkMonitor extends StateMachine { /** * Send a notification to NetworkMonitor indicating that link properties have changed. */ - public void notifyLinkPropertiesChanged() { - getHandler().post(() -> { - updateLinkProperties(); - }); - } - - private void updateLinkProperties() { - final LinkProperties lp = mCm.getLinkProperties(mNetwork); - // If null, we should soon get a message that the network was disconnected, and will stop. - if (lp != null) { - // TODO: send LinkProperties parceled in notifyLinkPropertiesChanged() and start(). - mLinkProperties = lp; - } + public void notifyLinkPropertiesChanged(final LinkProperties lp) { + sendMessage(EVENT_LINK_PROPERTIES_CHANGED, new LinkProperties(lp)); } /** * Send a notification to NetworkMonitor indicating that network capabilities have changed. */ - public void notifyNetworkCapabilitiesChanged() { - getHandler().post(() -> { - updateNetworkCapabilities(); - }); - } - - private void updateNetworkCapabilities() { - final NetworkCapabilities nc = mCm.getNetworkCapabilities(mNetwork); - // If null, we should soon get a message that the network was disconnected, and will stop. - if (nc != null) { - // TODO: send NetworkCapabilities parceled in notifyNetworkCapsChanged() and start(). - mNetworkCapabilities = nc; - } + public void notifyNetworkCapabilitiesChanged(final NetworkCapabilities nc) { + sendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, new NetworkCapabilities(nc)); } /** @@ -547,16 +544,10 @@ public class NetworkMonitor extends StateMachine { // does not entail any real state (hence no enter() or exit() routines). private class DefaultState extends State { @Override - public void enter() { - // TODO: have those passed parceled in start() and remove this - updateLinkProperties(); - updateNetworkCapabilities(); - } - - @Override public boolean processMessage(Message message) { switch (message.what) { case CMD_NETWORK_CONNECTED: + updateConnectedNetworkAttributes(message); logNetworkEvent(NetworkEvent.NETWORK_CONNECTED); transitionTo(mEvaluatingState); return HANDLED; @@ -660,6 +651,12 @@ public class NetworkMonitor extends StateMachine { case EVENT_ACCEPT_PARTIAL_CONNECTIVITY: mAcceptPartialConnectivity = true; break; + case EVENT_LINK_PROPERTIES_CHANGED: + mLinkProperties = (LinkProperties) message.obj; + break; + case EVENT_NETWORK_CAPABILITIES_CHANGED: + mNetworkCapabilities = (NetworkCapabilities) message.obj; + break; default: break; } @@ -684,6 +681,7 @@ public class NetworkMonitor extends StateMachine { public boolean processMessage(Message message) { switch (message.what) { case CMD_NETWORK_CONNECTED: + updateConnectedNetworkAttributes(message); transitionTo(mValidatedState); break; case CMD_EVALUATE_PRIVATE_DNS: diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java index 4d4ceed9cb52..b4eeefd5ecae 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java @@ -72,6 +72,10 @@ public class IpMemoryStoreDatabase { public static final String COLNAME_ASSIGNEDV4ADDRESS = "assignedV4Address"; public static final String COLTYPE_ASSIGNEDV4ADDRESS = "INTEGER"; + public static final String COLNAME_ASSIGNEDV4ADDRESSEXPIRY = "assignedV4AddressExpiry"; + // The lease expiry timestamp in uint of milliseconds + public static final String COLTYPE_ASSIGNEDV4ADDRESSEXPIRY = "BIGINT"; + // Please note that the group hint is only a *hint*, hence its name. The client can offer // this information to nudge the grouping in the decision it thinks is right, but it can't // decide for the memory store what is the same L3 network. @@ -86,13 +90,14 @@ public class IpMemoryStoreDatabase { public static final String COLTYPE_MTU = "INTEGER DEFAULT -1"; public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " - + TABLENAME + " (" - + COLNAME_L2KEY + " " + COLTYPE_L2KEY + " PRIMARY KEY NOT NULL, " - + COLNAME_EXPIRYDATE + " " + COLTYPE_EXPIRYDATE + ", " - + COLNAME_ASSIGNEDV4ADDRESS + " " + COLTYPE_ASSIGNEDV4ADDRESS + ", " - + COLNAME_GROUPHINT + " " + COLTYPE_GROUPHINT + ", " - + COLNAME_DNSADDRESSES + " " + COLTYPE_DNSADDRESSES + ", " - + COLNAME_MTU + " " + COLTYPE_MTU + ")"; + + TABLENAME + " (" + + COLNAME_L2KEY + " " + COLTYPE_L2KEY + " PRIMARY KEY NOT NULL, " + + COLNAME_EXPIRYDATE + " " + COLTYPE_EXPIRYDATE + ", " + + COLNAME_ASSIGNEDV4ADDRESS + " " + COLTYPE_ASSIGNEDV4ADDRESS + ", " + + COLNAME_ASSIGNEDV4ADDRESSEXPIRY + " " + COLTYPE_ASSIGNEDV4ADDRESSEXPIRY + ", " + + COLNAME_GROUPHINT + " " + COLTYPE_GROUPHINT + ", " + + COLNAME_DNSADDRESSES + " " + COLTYPE_DNSADDRESSES + ", " + + COLNAME_MTU + " " + COLTYPE_MTU + ")"; public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME; } @@ -134,7 +139,7 @@ public class IpMemoryStoreDatabase { /** The SQLite DB helper */ public static class DbHelper extends SQLiteOpenHelper { // Update this whenever changing the schema. - private static final int SCHEMA_VERSION = 2; + private static final int SCHEMA_VERSION = 3; private static final String DATABASE_FILENAME = "IpMemoryStore.db"; public DbHelper(@NonNull final Context context) { @@ -153,10 +158,27 @@ public class IpMemoryStoreDatabase { @Override public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) { - // No upgrade supported yet. - db.execSQL(NetworkAttributesContract.DROP_TABLE); - db.execSQL(PrivateDataContract.DROP_TABLE); - onCreate(db); + try { + if (oldVersion < 2) { + // upgrade from version 1 to version 2 + // since we starts from version 2, do nothing here + } + + if (oldVersion < 3) { + // upgrade from version 2 to version 3 + final String sqlUpgradeAddressExpiry = "alter table" + + " " + NetworkAttributesContract.TABLENAME + " ADD" + + " " + NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY + + " " + NetworkAttributesContract.COLTYPE_ASSIGNEDV4ADDRESSEXPIRY; + db.execSQL(sqlUpgradeAddressExpiry); + } + } catch (SQLiteException e) { + Log.e(TAG, "Could not upgrade to the new version", e); + // create database with new version + db.execSQL(NetworkAttributesContract.DROP_TABLE); + db.execSQL(PrivateDataContract.DROP_TABLE); + onCreate(db); + } } /** Called when the database is downgraded */ @@ -204,6 +226,10 @@ public class IpMemoryStoreDatabase { values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, inet4AddressToIntHTH(attributes.assignedV4Address)); } + if (null != attributes.assignedV4AddressExpiry) { + values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY, + attributes.assignedV4AddressExpiry); + } if (null != attributes.groupHint) { values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint); } @@ -251,6 +277,8 @@ public class IpMemoryStoreDatabase { final NetworkAttributes.Builder builder = new NetworkAttributes.Builder(); final int assignedV4AddressInt = getInt(cursor, NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0); + final long assignedV4AddressExpiry = getLong(cursor, + NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY, 0); final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT); final byte[] dnsAddressesBlob = getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES); @@ -258,6 +286,9 @@ public class IpMemoryStoreDatabase { if (0 != assignedV4AddressInt) { builder.setAssignedV4Address(intToInet4AddressHTH(assignedV4AddressInt)); } + if (0 != assignedV4AddressExpiry) { + builder.setAssignedV4AddressExpiry(assignedV4AddressExpiry); + } builder.setGroupHint(groupHint); if (null != dnsAddressesBlob) { builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob)); diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp index aadf99ec37d0..0535af30681c 100644 --- a/packages/NetworkStack/tests/Android.bp +++ b/packages/NetworkStack/tests/Android.bp @@ -45,6 +45,8 @@ android_test { "libcrypto", "libcutils", "libdexfile", + "ld-android", + "libdl_android", "libhidl-gen-utils", "libhidlbase", "libhidltransport", diff --git a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java index 88a05d506aa4..a0e508f130a5 100644 --- a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java +++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java @@ -1006,7 +1006,7 @@ public class ApfTest { private static final int IPV4_HEADER_LEN = 20; private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0; - private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2; + private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2; private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9; private static final int IPV4_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 12; private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16; diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java index d732c4e81d83..6665aae65d90 100644 --- a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java +++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java @@ -26,6 +26,8 @@ import static android.provider.Settings.Global.DATA_STALL_EVALUATION_TYPE_DNS; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; @@ -60,6 +62,7 @@ import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.ConditionVariable; import android.os.Handler; +import android.os.RemoteException; import android.os.SystemClock; import android.provider.Settings; import android.telephony.CellSignalStrength; @@ -73,6 +76,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; @@ -107,6 +111,7 @@ public class NetworkMonitorTest { private @Spy Network mNetwork = new Network(TEST_NETID); private @Mock DataStallStatsUtils mDataStallStatsUtils; private @Mock WifiInfo mWifiInfo; + private @Captor ArgumentCaptor<String> mNetworkTestedRedirectUrlCaptor; private static final int TEST_NETID = 4242; @@ -122,7 +127,7 @@ public class NetworkMonitorTest { private static final int HANDLER_TIMEOUT_MS = 1000; - private static final LinkProperties TEST_LINKPROPERTIES = new LinkProperties(); + private static final LinkProperties TEST_LINK_PROPERTIES = new LinkProperties(); private static final NetworkCapabilities METERED_CAPABILITIES = new NetworkCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) @@ -182,10 +187,6 @@ public class NetworkMonitorTest { InetAddresses.parseNumericAddress("192.168.0.0") }).when(mNetwork).getAllByName(any()); - // Default values. Individual tests can override these. - when(mCm.getLinkProperties(any())).thenReturn(TEST_LINKPROPERTIES); - when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES); - setMinDataStallEvaluateInterval(500); setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS); setValidDataStallDnsTimeThreshold(500); @@ -195,10 +196,9 @@ public class NetworkMonitorTest { private class WrappedNetworkMonitor extends NetworkMonitor { private long mProbeTime = 0; - WrappedNetworkMonitor(Context context, Network network, IpConnectivityLog logger, - Dependencies deps, DataStallStatsUtils statsUtils) { - super(context, mCallbacks, network, logger, - new SharedLog("test_nm"), deps, statsUtils); + WrappedNetworkMonitor() { + super(mContext, mCallbacks, mNetwork, mLogger, mValidationLogger, mDependencies, + mDataStallStatsUtils); } @Override @@ -216,33 +216,30 @@ public class NetworkMonitorTest { } } - private WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() { - final WrappedNetworkMonitor nm = new WrappedNetworkMonitor( - mContext, mNetwork, mLogger, mDependencies, mDataStallStatsUtils); - when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES); + private WrappedNetworkMonitor makeMonitor() { + final WrappedNetworkMonitor nm = new WrappedNetworkMonitor(); nm.start(); waitForIdle(nm.getHandler()); return nm; } - private WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() { - final WrappedNetworkMonitor nm = new WrappedNetworkMonitor( - mContext, mNetwork, mLogger, mDependencies, mDataStallStatsUtils); - when(mCm.getNetworkCapabilities(any())).thenReturn(NOT_METERED_CAPABILITIES); - nm.start(); - waitForIdle(nm.getHandler()); + private WrappedNetworkMonitor makeMeteredNetworkMonitor() { + final WrappedNetworkMonitor nm = makeMonitor(); + setNetworkCapabilities(nm, METERED_CAPABILITIES); return nm; } - private NetworkMonitor makeMonitor() { - final NetworkMonitor nm = new NetworkMonitor( - mContext, mCallbacks, mNetwork, mLogger, mValidationLogger, - mDependencies, mDataStallStatsUtils); - nm.start(); - waitForIdle(nm.getHandler()); + private WrappedNetworkMonitor makeNotMeteredNetworkMonitor() { + final WrappedNetworkMonitor nm = makeMonitor(); + setNetworkCapabilities(nm, NOT_METERED_CAPABILITIES); return nm; } + private void setNetworkCapabilities(NetworkMonitor nm, NetworkCapabilities nc) { + nm.notifyNetworkCapabilitiesChanged(nc); + waitForIdle(nm.getHandler()); + } + private void waitForIdle(Handler handler) { final ConditionVariable cv = new ConditionVariable(false); handler.post(cv::open); @@ -256,7 +253,7 @@ public class NetworkMonitorTest { setSslException(mHttpsConnection); setPortal302(mHttpConnection); - assertPortal(makeMonitor().isCaptivePortal()); + runPortalNetworkTest(); } @Test @@ -264,17 +261,7 @@ public class NetworkMonitorTest { setStatus(mHttpsConnection, 204); setStatus(mHttpConnection, 500); - assertNotPortal(makeMonitor().isCaptivePortal()); - } - - @Test - public void testIsCaptivePortal_HttpsProbeFailedHttpSuccessNotUsed() throws IOException { - setSslException(mHttpsConnection); - // Even if HTTP returns a 204, do not use the result unless HTTPS succeeded - setStatus(mHttpConnection, 204); - setStatus(mFallbackConnection, 500); - - assertFailed(makeMonitor().isCaptivePortal()); + runNotPortalNetworkTest(); } @Test @@ -283,17 +270,17 @@ public class NetworkMonitorTest { setStatus(mHttpConnection, 500); setPortal302(mFallbackConnection); - assertPortal(makeMonitor().isCaptivePortal()); + runPortalNetworkTest(); } @Test public void testIsCaptivePortal_FallbackProbeIsNotPortal() throws IOException { setSslException(mHttpsConnection); setStatus(mHttpConnection, 500); - setStatus(mFallbackConnection, 204); + setStatus(mFallbackConnection, 500); // Fallback probe did not see portal, HTTPS failed -> inconclusive - assertFailed(makeMonitor().isCaptivePortal()); + runFailedNetworkTest(); } @Test @@ -310,15 +297,15 @@ public class NetworkMonitorTest { // TEST_OTHER_FALLBACK_URL is third when(mRandom.nextInt()).thenReturn(2); - final NetworkMonitor monitor = makeMonitor(); - // First check always uses the first fallback URL: inconclusive - assertFailed(monitor.isCaptivePortal()); + final NetworkMonitor monitor = runNetworkTest(NETWORK_TEST_RESULT_INVALID); + assertNull(mNetworkTestedRedirectUrlCaptor.getValue()); verify(mFallbackConnection, times(1)).getResponseCode(); verify(mOtherFallbackConnection, never()).getResponseCode(); // Second check uses the URL chosen by Random - assertPortal(monitor.isCaptivePortal()); + final CaptivePortalProbeResult result = monitor.isCaptivePortal(); + assertTrue(result.isPortal()); verify(mOtherFallbackConnection, times(1)).getResponseCode(); } @@ -328,7 +315,7 @@ public class NetworkMonitorTest { setStatus(mHttpConnection, 500); setStatus(mFallbackConnection, 404); - assertFailed(makeMonitor().isCaptivePortal()); + runFailedNetworkTest(); verify(mFallbackConnection, times(1)).getResponseCode(); verify(mOtherFallbackConnection, never()).getResponseCode(); } @@ -342,7 +329,7 @@ public class NetworkMonitorTest { setStatus(mHttpConnection, 500); setPortal302(mOtherFallbackConnection); - assertPortal(makeMonitor().isCaptivePortal()); + runPortalNetworkTest(); verify(mOtherFallbackConnection, times(1)).getResponseCode(); verify(mFallbackConnection, never()).getResponseCode(); } @@ -360,12 +347,12 @@ public class NetworkMonitorTest { } @Test - public void testIsCaptivePortal_FallbackSpecIsNotPortal() throws IOException { + public void testIsCaptivePortal_FallbackSpecIsPartial() throws IOException { setupFallbackSpec(); set302(mOtherFallbackConnection, "https://www.google.com/test?q=3"); - // HTTPS failed, fallback spec did not see a portal -> inconclusive - assertFailed(makeMonitor().isCaptivePortal()); + // HTTPS failed, fallback spec went through -> partial connectivity + runPartialConnectivityNetworkTest(); verify(mOtherFallbackConnection, times(1)).getResponseCode(); verify(mFallbackConnection, never()).getResponseCode(); } @@ -375,7 +362,7 @@ public class NetworkMonitorTest { setupFallbackSpec(); set302(mOtherFallbackConnection, "http://login.portal.example.com"); - assertPortal(makeMonitor().isCaptivePortal()); + runPortalNetworkTest(); } @Test @@ -384,20 +371,20 @@ public class NetworkMonitorTest { setSslException(mHttpsConnection); setPortal302(mHttpConnection); - assertNotPortal(makeMonitor().isCaptivePortal()); + runNotPortalNetworkTest(); } @Test public void testIsDataStall_EvaluationDisabled() { setDataStallEvaluationType(0); - WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100); assertFalse(wrappedMonitor.isDataStall()); } @Test public void testIsDataStall_EvaluationDnsOnNotMeteredNetwork() { - WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100); makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD); assertTrue(wrappedMonitor.isDataStall()); @@ -405,7 +392,7 @@ public class NetworkMonitorTest { @Test public void testIsDataStall_EvaluationDnsOnMeteredNetwork() { - WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100); assertFalse(wrappedMonitor.isDataStall()); @@ -416,7 +403,7 @@ public class NetworkMonitorTest { @Test public void testIsDataStall_EvaluationDnsWithDnsTimeoutCount() { - WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); makeDnsTimeoutEvent(wrappedMonitor, 3); assertFalse(wrappedMonitor.isDataStall()); @@ -430,7 +417,7 @@ public class NetworkMonitorTest { // Set the value to larger than the default dns log size. setConsecutiveDnsTimeoutThreshold(51); - wrappedMonitor = makeMeteredWrappedNetworkMonitor(); + wrappedMonitor = makeMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); makeDnsTimeoutEvent(wrappedMonitor, 50); assertFalse(wrappedMonitor.isDataStall()); @@ -442,7 +429,7 @@ public class NetworkMonitorTest { @Test public void testIsDataStall_EvaluationDnsWithDnsTimeThreshold() { // Test dns events happened in valid dns time threshold. - WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100); makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD); assertFalse(wrappedMonitor.isDataStall()); @@ -451,7 +438,7 @@ public class NetworkMonitorTest { // Test dns events happened before valid dns time threshold. setValidDataStallDnsTimeThreshold(0); - wrappedMonitor = makeMeteredWrappedNetworkMonitor(); + wrappedMonitor = makeMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 100); makeDnsTimeoutEvent(wrappedMonitor, DEFAULT_DNS_TIMEOUT_THRESHOLD); assertFalse(wrappedMonitor.isDataStall()); @@ -464,24 +451,13 @@ public class NetworkMonitorTest { setSslException(mHttpsConnection); setStatus(mHttpConnection, 500); setStatus(mFallbackConnection, 404); - when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES); - final NetworkMonitor nm = makeMonitor(); - nm.notifyNetworkConnected(); - - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) - .notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null); + runFailedNetworkTest(); } @Test public void testNoInternetCapabilityValidated() throws Exception { - when(mCm.getNetworkCapabilities(any())).thenReturn(NO_INTERNET_CAPABILITIES); - - final NetworkMonitor nm = makeMonitor(); - nm.notifyNetworkConnected(); - - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) - .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null); + runNetworkTest(NO_INTERNET_CAPABILITIES, NETWORK_TEST_RESULT_VALID); verify(mNetwork, never()).openConnection(any()); } @@ -491,7 +467,7 @@ public class NetworkMonitorTest { setPortal302(mHttpConnection); final NetworkMonitor nm = makeMonitor(); - nm.notifyNetworkConnected(); + nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, METERED_CAPABILITIES); verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) .showProvisioningNotification(any(), any()); @@ -522,7 +498,7 @@ public class NetworkMonitorTest { @Test public void testDataStall_StallSuspectedAndSendMetrics() throws IOException { - WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); makeDnsTimeoutEvent(wrappedMonitor, 5); assertTrue(wrappedMonitor.isDataStall()); @@ -531,7 +507,7 @@ public class NetworkMonitorTest { @Test public void testDataStall_NoStallSuspectedAndSendMetrics() throws IOException { - WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor(); wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000); makeDnsTimeoutEvent(wrappedMonitor, 3); assertFalse(wrappedMonitor.isDataStall()); @@ -540,7 +516,7 @@ public class NetworkMonitorTest { @Test public void testCollectDataStallMetrics() { - WrappedNetworkMonitor wrappedMonitor = makeNotMeteredWrappedNetworkMonitor(); + WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor(); when(mTelephony.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE); when(mTelephony.getNetworkOperator()).thenReturn(TEST_MCCMNC); @@ -578,14 +554,11 @@ public class NetworkMonitorTest { setSslException(mHttpsConnection); setStatus(mHttpConnection, 204); - final NetworkMonitor nm = makeMonitor(); - nm.notifyNetworkConnected(); - verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) - .notifyNetworkTested(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY, null); + final NetworkMonitor nm = runNetworkTest(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY); nm.setAcceptPartialConnectivity(); verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) - .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null); + .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), any()); } @Test @@ -593,12 +566,12 @@ public class NetworkMonitorTest { setStatus(mHttpsConnection, 500); setStatus(mHttpConnection, 204); setStatus(mFallbackConnection, 500); - assertPartialConnectivity(makeMonitor().isCaptivePortal()); + runPartialConnectivityNetworkTest(); setStatus(mHttpsConnection, 500); setStatus(mHttpConnection, 500); setStatus(mFallbackConnection, 204); - assertPartialConnectivity(makeMonitor().isCaptivePortal()); + runPartialConnectivityNetworkTest(); } private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) { @@ -660,26 +633,41 @@ public class NetworkMonitorTest { eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode); } - private void assertPortal(CaptivePortalProbeResult result) { - assertTrue(result.isPortal()); - assertFalse(result.isFailed()); - assertFalse(result.isSuccessful()); + private void runPortalNetworkTest() { + runNetworkTest(NETWORK_TEST_RESULT_INVALID); + assertNotNull(mNetworkTestedRedirectUrlCaptor.getValue()); } - private void assertNotPortal(CaptivePortalProbeResult result) { - assertFalse(result.isPortal()); - assertFalse(result.isFailed()); - assertTrue(result.isSuccessful()); + private void runNotPortalNetworkTest() { + runNetworkTest(NETWORK_TEST_RESULT_VALID); + assertNull(mNetworkTestedRedirectUrlCaptor.getValue()); } - private void assertFailed(CaptivePortalProbeResult result) { - assertFalse(result.isPortal()); - assertTrue(result.isFailed()); - assertFalse(result.isSuccessful()); + private void runFailedNetworkTest() { + runNetworkTest(NETWORK_TEST_RESULT_INVALID); + assertNull(mNetworkTestedRedirectUrlCaptor.getValue()); } - private void assertPartialConnectivity(CaptivePortalProbeResult result) { - assertTrue(result.isPartialConnectivity()); + private void runPartialConnectivityNetworkTest() { + runNetworkTest(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY); + assertNull(mNetworkTestedRedirectUrlCaptor.getValue()); + } + + private NetworkMonitor runNetworkTest(int testResult) { + return runNetworkTest(METERED_CAPABILITIES, testResult); + } + + private NetworkMonitor runNetworkTest(NetworkCapabilities nc, int testResult) { + final NetworkMonitor monitor = makeMonitor(); + monitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc); + try { + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .notifyNetworkTested(eq(testResult), mNetworkTestedRedirectUrlCaptor.capture()); + } catch (RemoteException e) { + fail("Unexpected exception: " + e); + } + + return monitor; } private void setSslException(HttpURLConnection connection) throws IOException { diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java index d0e58b817e9d..071ff2635152 100644 --- a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java +++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java @@ -228,6 +228,7 @@ public class IpMemoryStoreServiceTest { public void testNetworkAttributes() throws UnknownHostException { final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); + na.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000); na.setGroupHint("hint1"); na.setMtu(219); final String l2Key = FAKE_KEYS[0]; @@ -257,6 +258,8 @@ public class IpMemoryStoreServiceTest { + status.resultCode, status.isSuccess()); assertEquals(l2Key, key); assertEquals(attributes.assignedV4Address, attr.assignedV4Address); + assertEquals(attributes.assignedV4AddressExpiry, + attr.assignedV4AddressExpiry); assertEquals(attributes.groupHint, attr.groupHint); assertEquals(attributes.mtu, attr.mtu); assertEquals(attributes2.dnsAddresses, attr.dnsAddresses); @@ -278,7 +281,7 @@ public class IpMemoryStoreServiceTest { // Verify that this test does not miss any new field added later. // If any field is added to NetworkAttributes it must be tested here for storing // and retrieving. - assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields()) + assertEquals(5, Arrays.stream(NetworkAttributes.class.getDeclaredFields()) .filter(f -> !Modifier.isStatic(f.getModifiers())).count()); } diff --git a/packages/OsuLogin/res/values/strings.xml b/packages/OsuLogin/res/values/strings.xml index 06fa8c7b1e8f..14de0f503769 100644 --- a/packages/OsuLogin/res/values/strings.xml +++ b/packages/OsuLogin/res/values/strings.xml @@ -3,4 +3,6 @@ <string name="app_name">OsuLogin</string> <!-- action bar label [CHAR LIMIT=32] --> <string name="action_bar_label">Online Sign Up</string> + <!-- toast message [CHAR LIMIT=32] --> + <string name="sign_up_failed">Sign-up failed</string> </resources> diff --git a/packages/OsuLogin/src/com/android/hotspot2/osu/OsuLoginActivity.java b/packages/OsuLogin/src/com/android/hotspot2/osu/OsuLoginActivity.java index 82e33cc4aff9..416894b23a2a 100644 --- a/packages/OsuLogin/src/com/android/hotspot2/osu/OsuLoginActivity.java +++ b/packages/OsuLogin/src/com/android/hotspot2/osu/OsuLoginActivity.java @@ -38,6 +38,7 @@ import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.ProgressBar; +import android.widget.Toast; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -141,6 +142,7 @@ public class OsuLoginActivity extends Activity { Log.d(TAG, "Lost for the current Network, close the browser"); } mForceDisconnect = false; // It is already disconnected. + showSignUpFailedToast(); if (mNetwork.equals(network)) { finishAndRemoveTask(); } @@ -229,6 +231,11 @@ public class OsuLoginActivity extends Activity { return ""; } + private void showSignUpFailedToast() { + Toast.makeText(getApplicationContext(), R.string.sign_up_failed, + Toast.LENGTH_SHORT).show(); + } + private class OsuWebViewClient extends WebViewClient { boolean mPageError = false; boolean mRedirectResponseReceived = false; diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp new file mode 100644 index 000000000000..9420954748c4 --- /dev/null +++ b/packages/PackageInstaller/Android.bp @@ -0,0 +1,28 @@ +// Copyright (C) 2018 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. + +android_app { + name: "PackageInstaller", + + srcs: ["src/**/*.java"], + + certificate: "platform", + privileged: true, + platform_apis: true, + + static_libs: [ + "xz-java", + "androidx.leanback_leanback", + ], +} diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index 62535b635e44..b0e2700a1f30 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -43,14 +43,12 @@ <action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.INSTALL_PACKAGE" /> <category android:name="android.intent.category.DEFAULT" /> - <data android:scheme="file" /> <data android:scheme="content" /> <data android:mimeType="application/vnd.android.package-archive" /> </intent-filter> <intent-filter android:priority="1"> <action android:name="android.intent.action.INSTALL_PACKAGE" /> <category android:name="android.intent.category.DEFAULT" /> - <data android:scheme="file" /> <data android:scheme="package" /> <data android:scheme="content" /> </intent-filter> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index 441dbac24928..bde1b25b914f 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -26,6 +26,7 @@ import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.Dialog; import android.app.DialogFragment; +import android.app.admin.DevicePolicyManager; import android.content.ActivityNotFoundException; import android.content.ContentResolver; import android.content.Context; @@ -427,7 +428,7 @@ public class PackageInstallerActivity extends AlertActivity { if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) { initiateInstall(); } else { - // Check for unknown sources restriction + // Check for unknown sources restrictions. final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource( UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle()); final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource( @@ -436,16 +437,28 @@ public class PackageInstallerActivity extends AlertActivity { & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource); if (systemRestriction != 0) { showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER); - } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET - || unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) { - startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS)); - finish(); + } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) { + startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES); + } else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) { + startAdminSupportDetailsActivity( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY); } else { handleUnknownSources(); } } } + private void startAdminSupportDetailsActivity(String restriction) { + // If the given restriction is set by an admin, display information about the + // admin enforcing the restriction for the affected user. + final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class); + final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction); + if (showAdminSupportDetailsIntent != null) { + startActivity(showAdminSupportDetailsIntent); + } + finish(); + } + private void handleUnknownSources() { if (mOriginatingPackage == null) { Log.i(TAG, "No source found for package " + mPkgInfo.packageName); diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp new file mode 100644 index 000000000000..7f4442deecd6 --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/Android.bp @@ -0,0 +1,13 @@ +android_library { + name: "SettingsLibAdaptiveIcon", + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + + static_libs: [ + "androidx.annotation_annotation", + "SettingsLibTile" + ], + + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/AdaptiveIcon/AndroidManifest.xml b/packages/SettingsLib/AdaptiveIcon/AndroidManifest.xml new file mode 100644 index 000000000000..256b8f3ea477 --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.widget"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml b/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml new file mode 100644 index 000000000000..76d106a067dd --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <color name="homepage_generic_icon_background">#1A73E8</color> + + <color name="bt_outline_color">#1f000000</color> <!-- icon outline color --> +</resources> diff --git a/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml b/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml new file mode 100644 index 000000000000..7f5b58c48abb --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources> + <!-- Dashboard foreground image inset (from background edge to foreground edge) --> + <dimen name="dashboard_tile_foreground_image_inset">6dp</dimen> + + <!-- Stroke size of adaptive outline --> + <dimen name="adaptive_outline_stroke">1dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java new file mode 100644 index 000000000000..fc93650a6b85 --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIcon.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.widget; + +import static androidx.annotation.VisibleForTesting.NONE; + +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB; +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; + +import com.android.settingslib.drawer.Tile; + +/** + * Adaptive icon that can set background color + */ +public class AdaptiveIcon extends LayerDrawable { + + private static final String TAG = "AdaptiveHomepageIcon"; + + @VisibleForTesting(otherwise = NONE) + int mBackgroundColor = -1; + private AdaptiveConstantState mAdaptiveConstantState; + + public AdaptiveIcon(Context context, Drawable foreground) { + super(new Drawable[]{ + new AdaptiveIconShapeDrawable(context.getResources()), + foreground + }); + final int insetPx = context.getResources() + .getDimensionPixelSize(R.dimen.dashboard_tile_foreground_image_inset); + setLayerInset(1 /* index */, insetPx, insetPx, insetPx, insetPx); + mAdaptiveConstantState = new AdaptiveConstantState(context, foreground); + } + + /** + * According {@code tile} metaData to set background color + */ + public void setBackgroundColor(Context context, Tile tile) { + final Bundle metaData = tile.getMetaData(); + try { + if (metaData != null) { + // Load from bg.argb first + int bgColor = metaData.getInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, + 0 /* default */); + // Not found, load from bg.hint + if (bgColor == 0) { + final int colorRes = metaData.getInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, + 0 /* default */); + if (colorRes != 0) { + bgColor = context.getPackageManager() + .getResourcesForApplication(tile.getPackageName()) + .getColor(colorRes, null /* theme */); + } + } + // If found anything, use it. + if (bgColor != 0) { + setBackgroundColor(bgColor); + return; + } + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to set background color for " + tile.getPackageName()); + } + setBackgroundColor(context.getColor(R.color.homepage_generic_icon_background)); + } + + /** + * Set background color by {@code color} + */ + public void setBackgroundColor(int color) { + mBackgroundColor = color; + getDrawable(0).setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + Log.d(TAG, "Setting background color " + mBackgroundColor); + mAdaptiveConstantState.mColor = color; + } + + @Override + public ConstantState getConstantState() { + return mAdaptiveConstantState; + } + + @VisibleForTesting + static class AdaptiveConstantState extends ConstantState { + Context mContext; + Drawable mDrawable; + int mColor; + + AdaptiveConstantState(Context context, Drawable drawable) { + this.mContext = context; + this.mDrawable = drawable; + } + + @Override + public Drawable newDrawable() { + final AdaptiveIcon + icon = new AdaptiveIcon(mContext, mDrawable); + icon.setBackgroundColor(mColor); + + return icon; + } + + @Override + public int getChangingConfigurations() { + return 0; + } + } +} diff --git a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java new file mode 100644 index 000000000000..4d7610cf97b6 --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.widget; + +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.graphics.Path; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.PathShape; +import android.util.AttributeSet; +import android.util.PathParser; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * Draws a filled {@link ShapeDrawable} using the path from {@link AdaptiveIconDrawable}. + */ +public class AdaptiveIconShapeDrawable extends ShapeDrawable { + public AdaptiveIconShapeDrawable() { + super(); + } + + public AdaptiveIconShapeDrawable(Resources resources) { + super(); + init(resources); + } + + @Override + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + super.inflate(r, parser, attrs, theme); + init(r); + } + + private void init(Resources resources) { + final float pathSize = AdaptiveIconDrawable.MASK_SIZE; + final Path path = new Path(PathParser.createPathFromPathData( + resources.getString(com.android.internal.R.string.config_icon_mask))); + setShape(new PathShape(path, pathSize, pathSize)); + } +} diff --git a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java new file mode 100644 index 000000000000..1c65bc248961 --- /dev/null +++ b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.widget; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.DrawableWrapper; +import android.util.PathParser; + +import androidx.annotation.VisibleForTesting; + +/** + * Adaptive outline drawable with white plain background color and black outline + */ +public class AdaptiveOutlineDrawable extends DrawableWrapper { + @VisibleForTesting + final Paint mOutlinePaint; + private Path mPath; + private final int mInsetPx; + private final Bitmap mBitmap; + + public AdaptiveOutlineDrawable(Resources resources, Bitmap bitmap) { + super(new AdaptiveIconShapeDrawable(resources)); + + getDrawable().setTint(Color.WHITE); + mPath = new Path(PathParser.createPathFromPathData( + resources.getString(com.android.internal.R.string.config_icon_mask))); + mOutlinePaint = new Paint(); + mOutlinePaint.setColor(resources.getColor(R.color.bt_outline_color, null)); + mOutlinePaint.setStyle(Paint.Style.STROKE); + mOutlinePaint.setStrokeWidth(resources.getDimension(R.dimen.adaptive_outline_stroke)); + mOutlinePaint.setAntiAlias(true); + + mInsetPx = resources + .getDimensionPixelSize(R.dimen.dashboard_tile_foreground_image_inset); + mBitmap = bitmap; + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + final Rect bounds = getBounds(); + final float pathSize = AdaptiveIconDrawable.MASK_SIZE; + + final float scaleX = (bounds.right - bounds.left) / pathSize; + final float scaleY = (bounds.bottom - bounds.top) / pathSize; + + final int count = canvas.save(); + canvas.scale(scaleX, scaleY); + // Draw outline + canvas.drawPath(mPath, mOutlinePaint); + canvas.restoreToCount(count); + + // Draw the foreground icon + canvas.drawBitmap(mBitmap, bounds.left + mInsetPx, bounds.top + mInsetPx, null); + } + + @Override + public int getIntrinsicHeight() { + return mBitmap.getHeight() + 2 * mInsetPx; + } + + @Override + public int getIntrinsicWidth() { + return mBitmap.getWidth() + 2 * mInsetPx; + } +} diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 730e9e134e3d..b532621cd617 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -22,6 +22,7 @@ android_library { "SettingsLibEntityHeaderWidgets", "SettingsLibBarChartPreference", "SettingsLibProgressBar", + "SettingsLibAdaptiveIcon", ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/packages/SettingsLib/Tile/Android.bp b/packages/SettingsLib/Tile/Android.bp new file mode 100644 index 000000000000..bf16ef317fd8 --- /dev/null +++ b/packages/SettingsLib/Tile/Android.bp @@ -0,0 +1,11 @@ +android_library { + name: "SettingsLibTile", + + srcs: ["src/**/*.java"], + + static_libs: [ + "androidx.annotation_annotation", + ], + + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/Tile/AndroidManifest.xml b/packages/SettingsLib/Tile/AndroidManifest.xml new file mode 100644 index 000000000000..b13532e2a5fd --- /dev/null +++ b/packages/SettingsLib/Tile/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.drawer"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DashboardCategory.java index a3dda658bec7..7b062b1ac9a9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/DashboardCategory.java @@ -1,11 +1,11 @@ -/** - * Copyright (C) 2015 The Android Open Source Project +/* + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.settingslib.drawer; import static java.lang.String.CASE_INSENSITIVE_ORDER; @@ -26,6 +25,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +/** + * The category for handle {@link Tile} + */ public class DashboardCategory implements Parcelable { /** @@ -67,18 +69,30 @@ public class DashboardCategory implements Parcelable { return result; } + /** + * Add tile + */ public synchronized void addTile(Tile tile) { mTiles.add(tile); } + /** + * Remove tile + */ public synchronized void removeTile(int n) { mTiles.remove(n); } + /** + * Get size of tile + */ public int getTilesCount() { return mTiles.size(); } + /** + * Get tile + */ public Tile getTile(int n) { return mTiles.get(n); } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java index d28b00a7ed39..5108efbdc216 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java @@ -1,11 +1,11 @@ -/** - * Copyright (C) 2015 The Android Open Source Project +/* + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -84,8 +84,8 @@ public class Tile implements Parcelable { mActivityPackage = in.readString(); mActivityName = in.readString(); mIntent = new Intent().setClassName(mActivityPackage, mActivityName); - final int N = in.readInt(); - for (int i = 0; i < N; i++) { + final int number = in.readInt(); + for (int i = 0; i < number; i++) { userHandle.add(UserHandle.CREATOR.createFromParcel(in)); } mCategory = in.readString(); @@ -101,9 +101,9 @@ public class Tile implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeString(mActivityPackage); dest.writeString(mActivityName); - final int N = userHandle.size(); - dest.writeInt(N); - for (int i = 0; i < N; i++) { + final int size = userHandle.size(); + dest.writeInt(size); + for (int i = 0; i < size; i++) { userHandle.get(i).writeToParcel(dest, flags); } dest.writeString(mCategory); @@ -151,6 +151,9 @@ public class Tile implements Parcelable { } } + /** + * Check whether title has order. + */ public boolean hasOrder() { return mMetaData.containsKey(META_DATA_KEY_ORDER) && mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer; @@ -262,6 +265,9 @@ public class Tile implements Parcelable { } } + /** + * Check whether title has key. + */ public boolean hasKey() { return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_KEYHINT); } @@ -361,9 +367,12 @@ public class Tile implements Parcelable { } }; + /** + * Check whether title is only have primary profile + */ public boolean isPrimaryProfileOnly() { - String profile = mMetaData != null ? - mMetaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL; + String profile = mMetaData != null + ? mMetaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL; profile = (profile != null ? profile : PROFILE_ALL); return TextUtils.equals(profile, PROFILE_PRIMARY); } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java index 91892abdfb44..31925ab64ec3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ * 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 + * limitations under the License. */ package com.android.settingslib.drawer; @@ -39,6 +39,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * Utils is a helper class that contains profile key, meta data, settings action + * and static methods for get icon or text from uri. + */ public class TileUtils { private static final boolean DEBUG_TIMING = false; diff --git a/packages/SettingsLib/res/drawable/ic_media_device.xml b/packages/SettingsLib/res/drawable/ic_media_device.xml new file mode 100644 index 000000000000..5a6aeb4a8e23 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_media_device.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:viewportWidth="24" + android:viewportHeight="24" + android:width="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal"> + <path + android:fillColor="#00000000" + android:fillAlpha=".1" + android:pathData="M0 0h24v24H0z" /> + <path + android:fillColor="#00000000" + android:pathData="M0 0h24v24H0z" /> + <path + android:fillColor="#000000" + android:pathData="M21 3H3c-1.1 0-2 0.9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2 -0.9 2-2V5c0-1.1 -0.9-2-2-2zM1 18v3h3c0-1.66-1.34-3-3-3zm0-4v2c2.76 0 5 2.24 5 5h2c0-3.87-3.13-7-7-7zm0-4v2c4.97 0 9 4.03 9 9h2c0-6.08-4.93-11-11-11z" /> +</vector>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml index ed3c11cd3ca3..39c55fd1925c 100644 --- a/packages/SettingsLib/res/values/arrays.xml +++ b/packages/SettingsLib/res/values/arrays.xml @@ -603,4 +603,26 @@ <item>3</item><item>3</item> </array> + <!-- Bluetooth icon foreground colors --> + <integer-array name="bt_icon_fg_colors"> + <item>@color/bt_color_icon_1</item> + <item>@color/bt_color_icon_2</item> + <item>@color/bt_color_icon_3</item> + <item>@color/bt_color_icon_4</item> + <item>@color/bt_color_icon_5</item> + <item>@color/bt_color_icon_6</item> + <item>@color/bt_color_icon_7</item> + </integer-array> + + <!-- Bluetooth icon background colors --> + <integer-array name="bt_icon_bg_colors"> + <item>@color/bt_color_bg_1</item> + <item>@color/bt_color_bg_2</item> + <item>@color/bt_color_bg_3</item> + <item>@color/bt_color_bg_4</item> + <item>@color/bt_color_bg_5</item> + <item>@color/bt_color_bg_6</item> + <item>@color/bt_color_bg_7</item> + </integer-array> + </resources> diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml index 66bbb3a6c890..4b91bbb8d8dc 100644 --- a/packages/SettingsLib/res/values/colors.xml +++ b/packages/SettingsLib/res/values/colors.xml @@ -19,4 +19,20 @@ <color name="usage_graph_dots">@*android:color/tertiary_device_default_settings</color> <color name="list_divider_color">#64000000</color> + + <color name="bt_color_icon_1">#48a50e0e</color> <!-- 72% Material Red 900 --> + <color name="bt_color_icon_2">#480d652d</color> <!-- 72% Material Green 900 --> + <color name="bt_color_icon_3">#48e37400</color> <!-- 72% Material Yellow 900 --> + <color name="bt_color_icon_4">#48b06000</color> <!-- 72% Material Orange 900 --> + <color name="bt_color_icon_5">#489c166b</color> <!-- 72% Material Pink 900 --> + <color name="bt_color_icon_6">#48681da8</color> <!-- 72% Material Purple 900 --> + <color name="bt_color_icon_7">#48007b83</color> <!-- 72% Material Cyan 900 --> + + <color name="bt_color_bg_1">#fad2cf</color> <!-- Material Red 100 --> + <color name="bt_color_bg_2">#ceead6</color> <!-- Material Green 100 --> + <color name="bt_color_bg_3">#feefc3</color> <!-- Material Yellow 100 --> + <color name="bt_color_bg_4">#fedfc8</color> <!-- Material Orange 100 --> + <color name="bt_color_bg_5">#fdcfe8</color> <!-- Material Pink 100 --> + <color name="bt_color_bg_6">#e9d2fd</color> <!-- Material Purple 100 --> + <color name="bt_color_bg_7">#cbf0f8</color> <!-- Material Cyan 100 --> </resources> diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index a9c5061f6d87..2cb9d4b14aa7 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -91,6 +91,7 @@ <!-- How far to inset the rounded edges --> <dimen name="stat_sys_mobile_signal_circle_inset">0.9dp</dimen> - + <!-- Size of nearby icon --> + <dimen name="bt_nearby_icon_size">24dp</dimen> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index bb8c8a6768ed..867efb408258 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -1,18 +1,30 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.Log; import android.util.Pair; import androidx.annotation.DrawableRes; import com.android.settingslib.R; +import com.android.settingslib.widget.AdaptiveIcon; +import com.android.settingslib.widget.AdaptiveOutlineDrawable; +import java.io.IOException; import java.util.List; public class BluetoothUtils { + private static final String TAG = "BluetoothUtils"; + public static final boolean V = false; // verbose logging public static final boolean D = true; // regular logging @@ -112,4 +124,68 @@ public class BluetoothUtils { public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId) { return context.getDrawable(resId); } + + /** + * Get colorful bluetooth icon with description + */ + public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(Context context, + CachedBluetoothDevice cachedDevice) { + final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription( + context, cachedDevice); + final BluetoothDevice bluetoothDevice = cachedDevice.getDevice(); + final boolean untetheredHeadset = bluetoothDevice != null + ? Boolean.parseBoolean(bluetoothDevice.getMetadata( + BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)) + : false; + final int iconSize = context.getResources().getDimensionPixelSize( + R.dimen.bt_nearby_icon_size); + final Resources resources = context.getResources(); + + // Deal with untethered headset + if (untetheredHeadset) { + final String uriString = bluetoothDevice != null + ? bluetoothDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON) + : null; + final Uri iconUri = uriString != null ? Uri.parse(uriString) : null; + if (iconUri != null) { + try { + final Bitmap bitmap = MediaStore.Images.Media.getBitmap( + context.getContentResolver(), iconUri); + if (bitmap != null) { + final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, + iconSize, false); + bitmap.recycle(); + final AdaptiveOutlineDrawable drawable = new AdaptiveOutlineDrawable( + resources, resizedBitmap); + return new Pair<>(drawable, pair.second); + } + } catch (IOException e) { + Log.e(TAG, "Failed to get drawable for: " + iconUri, e); + } + } + } + + return new Pair<>(buildBtRainbowDrawable(context, + pair.first, cachedDevice.getAddress().hashCode()), pair.second); + } + + /** + * Build Bluetooth device icon with rainbow + */ + public static Drawable buildBtRainbowDrawable(Context context, Drawable drawable, + int hashCode) { + final Resources resources = context.getResources(); + + // Deal with normal headset + final int[] iconFgColors = resources.getIntArray(R.array.bt_icon_fg_colors); + final int[] iconBgColors = resources.getIntArray(R.array.bt_icon_bg_colors); + + // get color index based on mac address + final int index = Math.abs(hashCode % iconBgColors.length); + drawable.setColorFilter(iconFgColors[index], PorterDuff.Mode.SRC_ATOP); + final Drawable adaptiveIcon = new AdaptiveIcon(context, drawable); + ((AdaptiveIcon) adaptiveIcon).setBackgroundColor(iconBgColors[index]); + + return adaptiveIcon; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java index b025df44738e..530c73a2448b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java @@ -31,7 +31,9 @@ import android.util.Slog; * Utilities related to battery saver. */ public class BatterySaverUtils { + private static final String TAG = "BatterySaverUtils"; + public static final String EXTRA_CONFIRM_ONLY = "extra_confirm_only"; private BatterySaverUtils() { } @@ -96,7 +98,7 @@ public class BatterySaverUtils { } final ContentResolver cr = context.getContentResolver(); - if (enable && needFirstTimeWarning && maybeShowBatterySaverConfirmation(context)) { + if (enable && needFirstTimeWarning && maybeShowBatterySaverConfirmation(context, false)) { return false; } if (enable && !needFirstTimeWarning) { @@ -116,7 +118,7 @@ public class BatterySaverUtils { && Global.getInt(cr, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0) == 0 && Secure.getInt(cr, Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 0) == 0) { - showAutoBatterySaverSuggestion(context); + showAutoBatterySaverSuggestion(context, false); } } @@ -125,23 +127,36 @@ public class BatterySaverUtils { return false; } - private static boolean maybeShowBatterySaverConfirmation(Context context) { + /** + * Shows the battery saver confirmation warning if it hasn't been acknowledged by the user in + * the past before. When confirmOnly is true, the dialog will have generic info about battery + * saver but will only update that the user has been shown the notification and take no + * further action. if confirmOnly is false it will show a more specific version of the dialog + * that toggles battery saver when acknowledged + * @param context A valid context + * @param confirmOnly Whether to show the actionless generic dialog (true) or the specific one + * that toggles battery saver (false) + * @return True if it showed the notification because it has not been previously acknowledged. + */ + public static boolean maybeShowBatterySaverConfirmation(Context context, boolean confirmOnly) { if (Secure.getInt(context.getContentResolver(), Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 0) != 0) { return false; // Already shown. } - context.sendBroadcast(getSystemUiBroadcast(ACTION_SHOW_START_SAVER_CONFIRMATION)); + context.sendBroadcast( + getSystemUiBroadcast(ACTION_SHOW_START_SAVER_CONFIRMATION, confirmOnly)); return true; } - private static void showAutoBatterySaverSuggestion(Context context) { - context.sendBroadcast(getSystemUiBroadcast(ACTION_SHOW_AUTO_SAVER_SUGGESTION)); + private static void showAutoBatterySaverSuggestion(Context context, boolean confirmOnly) { + context.sendBroadcast(getSystemUiBroadcast(ACTION_SHOW_AUTO_SAVER_SUGGESTION, confirmOnly)); } - private static Intent getSystemUiBroadcast(String action) { + private static Intent getSystemUiBroadcast(String action, boolean confirmOnly) { final Intent i = new Intent(action); i.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); i.setPackage(SYSUI_PACKAGE); + i.putExtra(EXTRA_CONFIRM_ONLY, confirmOnly); return i; } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java index 3092b9960c7e..2711e3175957 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java @@ -18,8 +18,11 @@ package com.android.settingslib.media; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Context; +import android.graphics.drawable.Drawable; import android.util.Log; +import android.util.Pair; +import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; /** @@ -48,9 +51,10 @@ public class BluetoothMediaDevice extends MediaDevice { } @Override - public int getIcon() { - //TODO(b/117129183): This is not final icon for bluetooth device, just for demo. - return com.android.internal.R.drawable.ic_bt_headphones_a2dp; + public Drawable getIcon() { + final Pair<Drawable, String> pair = BluetoothUtils + .getBtRainbowDrawableWithDescription(mContext, mCachedDevice); + return pair.first; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java index 95f3d3d0f769..732e8dba3e44 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java @@ -16,10 +16,14 @@ package com.android.settingslib.media; import android.content.Context; +import android.graphics.drawable.Drawable; import android.widget.Toast; import androidx.mediarouter.media.MediaRouter; +import com.android.settingslib.R; +import com.android.settingslib.bluetooth.BluetoothUtils; + /** * InfoMediaDevice extends MediaDevice to represents wifi device. */ @@ -46,9 +50,10 @@ public class InfoMediaDevice extends MediaDevice { } @Override - public int getIcon() { - //TODO(b/121083246): This is not final icon for cast device, just for demo. - return com.android.internal.R.drawable.ic_settings_print; + public Drawable getIcon() { + //TODO(b/120669861): Return remote device icon uri once api is ready. + return BluetoothUtils.buildBtRainbowDrawable(mContext, + mContext.getDrawable(R.drawable.ic_media_device), getId().hashCode()); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index 9b9e80310c18..53a852069478 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -16,6 +16,7 @@ package com.android.settingslib.media; import android.content.Context; +import android.graphics.drawable.Drawable; import android.text.TextUtils; import androidx.annotation.IntDef; @@ -70,11 +71,11 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { public abstract String getSummary(); /** - * Get resource id of MediaDevice. + * Get icon of MediaDevice. * - * @return resource id of MediaDevice. + * @return drawable of icon. */ - public abstract int getIcon(); + public abstract Drawable getIcon(); /** * Get unique ID that represent MediaDevice diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 8c3fcc077edc..af91c3464194 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -16,10 +16,12 @@ package com.android.settingslib.media; import android.content.Context; +import android.graphics.drawable.Drawable; import android.util.Log; import com.android.settingslib.R; import com.android.settingslib.bluetooth.A2dpProfile; +import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.HearingAidProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; @@ -56,8 +58,9 @@ public class PhoneMediaDevice extends MediaDevice { } @Override - public int getIcon() { - return R.drawable.ic_smartphone; + public Drawable getIcon() { + return BluetoothUtils.buildBtRainbowDrawable(mContext, + mContext.getDrawable(R.drawable.ic_smartphone), getId().hashCode()); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 3acbcd3f6b41..8a88a4c64d0a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -53,6 +53,7 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import androidx.annotation.NonNull; @@ -65,8 +66,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -197,6 +200,9 @@ public class AccessPoint implements Comparable<AccessPoint> { private final Context mContext; + private WifiManager mWifiManager; + private WifiManager.ActionListener mConnectListener; + private String ssid; private String bssid; private int security; @@ -1068,8 +1074,10 @@ public class AccessPoint implements Comparable<AccessPoint> { /** * Starts the OSU Provisioning flow. */ - public void startOsuProvisioning() { - mContext.getSystemService(WifiManager.class).startSubscriptionProvisioning( + public void startOsuProvisioning(@Nullable WifiManager.ActionListener connectListener) { + mConnectListener = connectListener; + + getWifiManager().startSubscriptionProvisioning( mOsuProvider, mContext.getMainExecutor(), new AccessPointProvisioningCallback() @@ -1539,12 +1547,20 @@ public class AccessPoint implements Comparable<AccessPoint> { return string; } + private WifiManager getWifiManager() { + if (mWifiManager == null) { + mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + } + return mWifiManager; + } + /** * Callbacks relaying changes to the AccessPoint representation. * * <p>All methods are invoked on the Main Thread. */ public interface AccessPointListener { + /** * Indicates a change to the externally visible state of the AccessPoint trigger by an * update of ScanResults, saved configuration state, connection state, or score @@ -1561,7 +1577,6 @@ public class AccessPoint implements Comparable<AccessPoint> { * changed */ @MainThread void onAccessPointChanged(AccessPoint accessPoint); - /** * Indicates the "wifi pie signal level" has changed, retrieved via calls to * {@link AccessPoint#getLevel()}. @@ -1643,11 +1658,46 @@ public class AccessPoint implements Comparable<AccessPoint> { mOsuProvisioningComplete = true; mOsuFailure = null; mOsuStatus = null; + ThreadUtils.postOnMainThread(() -> { if (mAccessPointListener != null) { mAccessPointListener.onAccessPointChanged(AccessPoint.this); } }); + + // Connect to the freshly provisioned network. + WifiManager wifiManager = getWifiManager(); + + PasspointConfiguration passpointConfig = wifiManager + .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(mOsuProvider)) + .get(mOsuProvider); + if (passpointConfig == null) { + Log.e(TAG, "Missing PasspointConfiguration for newly provisioned network!"); + if (mConnectListener != null) { + mConnectListener.onFailure(0); + } + return; + } + + String fqdn = passpointConfig.getHomeSp().getFqdn(); + for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pairing : + wifiManager.getAllMatchingWifiConfigs(wifiManager.getScanResults())) { + WifiConfiguration config = pairing.first; + if (TextUtils.equals(config.FQDN, fqdn)) { + List<ScanResult> homeScans = + pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK); + List<ScanResult> roamingScans = + pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK); + + AccessPoint connectionAp = + new AccessPoint(mContext, config, homeScans, roamingScans); + wifiManager.connect(connectionAp.getConfig(), mConnectListener); + return; + } + } + if (mConnectListener != null) { + mConnectListener.onFailure(0); + } } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index fdc0fd33e6d7..5f2bc4e1d490 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -84,7 +84,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro private static final long DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS = 20 * DateUtils.MINUTE_IN_MILLIS; /** Maximum age of scan results to hold onto while actively scanning. **/ - private static final long MAX_SCAN_RESULT_AGE_MILLIS = 15000; + @VisibleForTesting static final long MAX_SCAN_RESULT_AGE_MILLIS = 15000; private static final String TAG = "WifiTracker"; private static final boolean DBG() { @@ -142,6 +142,13 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro */ private boolean mStaleScanResults = true; + /** + * Tracks whether the latest SCAN_RESULTS_AVAILABLE_ACTION contained new scans. If not, then + * we treat the last scan as an aborted scan and increase the eviction timeout window to avoid + * completely flushing the AP list before the next successful scan completes. + */ + private boolean mLastScanSucceeded = true; + // Does not need to be locked as it only updated on the worker thread, with the exception of // during onStart, which occurs before the receiver is registered on the work handler. private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>(); @@ -478,17 +485,22 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } /** - * Remove old scan results from the cache. + * Remove old scan results from the cache. If {@link #mLastScanSucceeded} is false, then + * increase the timeout window to avoid completely flushing the AP list before the next + * successful scan completes. * * <p>Should only ever be invoked from {@link #updateScanResultCache(List)} when * {@link #mStaleScanResults} is false. */ private void evictOldScans() { + long evictionTimeoutMillis = mLastScanSucceeded ? MAX_SCAN_RESULT_AGE_MILLIS + : MAX_SCAN_RESULT_AGE_MILLIS * 2; + long nowMs = SystemClock.elapsedRealtime(); for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) { ScanResult result = iter.next(); // result timestamp is in microseconds - if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MILLIS) { + if (nowMs - result.timestamp / 1000 > evictionTimeoutMillis) { iter.remove(); } } @@ -840,6 +852,8 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro WifiManager.WIFI_STATE_UNKNOWN)); } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) { mStaleScanResults = false; + mLastScanSucceeded = + intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true); fetchScansAndConfigsAndUpdateAccessPoints(); } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java index 9c8e3f4fe543..8e4027164587 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java @@ -41,6 +41,7 @@ import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiEnterpriseConfig; import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; import android.net.wifi.WifiNetworkScoreCache; import android.net.wifi.WifiSsid; import android.net.wifi.hotspot2.OsuProvider; @@ -53,6 +54,7 @@ import android.os.SystemClock; import android.text.SpannableString; import android.text.format.DateUtils; import android.util.ArraySet; +import android.util.Pair; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -72,6 +74,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; @@ -95,9 +98,12 @@ public class AccessPointTest { private Context mContext; private WifiInfo mWifiInfo; + @Mock private Context mMockContext; + @Mock private WifiManager mMockWifiManager; @Mock private RssiCurve mockBadgeCurve; @Mock private WifiNetworkScoreCache mockWifiNetworkScoreCache; @Mock private AccessPoint.AccessPointListener mMockAccessPointListener; + @Mock private WifiManager.ActionListener mMockConnectListener; private static final int NETWORK_ID = 123; private static final int DEFAULT_RSSI = -55; @@ -1360,6 +1366,9 @@ public class AccessPointTest { .isEqualTo(mContext.getString(R.string.tap_to_sign_up)); } + /** + * Verifies that the summary of an OSU entry updates based on provisioning status. + */ @Test public void testOsuAccessPointSummary_showsProvisioningUpdates() { AccessPoint osuAccessPoint = new AccessPoint(mContext, createOsuProvider(), @@ -1411,4 +1420,82 @@ public class AccessPointTest { assertThat(osuAccessPoint.getSummary()) .isEqualTo(mContext.getString(R.string.osu_sign_up_complete)); } + + /** + * Verifies that after provisioning through an OSU provider, we connect to the freshly + * provisioned network. + */ + @Test + public void testOsuAccessPoint_connectsAfterProvisioning() { + // Set up mock for WifiManager.getAllMatchingWifiConfigs + WifiConfiguration config = new WifiConfiguration(); + config.FQDN = "fqdn"; + Map<Integer, List<ScanResult>> scanMapping = new HashMap<>(); + scanMapping.put(WifiManager.PASSPOINT_HOME_NETWORK, mScanResults); + Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> configMapPair = + new Pair<>(config, scanMapping); + List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> matchingWifiConfig = + new ArrayList<>(); + matchingWifiConfig.add(configMapPair); + when(mMockWifiManager.getAllMatchingWifiConfigs(any())).thenReturn(matchingWifiConfig); + + // Set up mock for WifiManager.getMatchingPasspointConfigsForOsuProviders + OsuProvider provider = createOsuProvider(); + PasspointConfiguration passpointConfig = new PasspointConfiguration(); + HomeSp homeSp = new HomeSp(); + homeSp.setFqdn("fqdn"); + homeSp.setFriendlyName("Test Provider"); + passpointConfig.setHomeSp(homeSp); + Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>(); + osuProviderConfigMap.put(provider, passpointConfig); + when(mMockWifiManager + .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(provider))) + .thenReturn(osuProviderConfigMap); + + when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager); + + AccessPoint osuAccessPoint = new AccessPoint(mMockContext, provider, mScanResults); + osuAccessPoint.setListener(mMockAccessPointListener); + + AccessPoint.AccessPointProvisioningCallback provisioningCallback = + osuAccessPoint.new AccessPointProvisioningCallback(); + provisioningCallback.onProvisioningComplete(); + + verify(mMockWifiManager).connect(any(), any()); + } + + /** + * Verifies that after provisioning through an OSU provider, we call the connect listener's + * onFailure() method if we cannot find the network we just provisioned. + */ + @Test + public void testOsuAccessPoint_noMatchingConfigsAfterProvisioning_callsOnFailure() { + // Set up mock for WifiManager.getAllMatchingWifiConfigs + when(mMockWifiManager.getAllMatchingWifiConfigs(any())).thenReturn(new ArrayList<>()); + + // Set up mock for WifiManager.getMatchingPasspointConfigsForOsuProviders + OsuProvider provider = createOsuProvider(); + PasspointConfiguration passpointConfig = new PasspointConfiguration(); + HomeSp homeSp = new HomeSp(); + homeSp.setFqdn("fqdn"); + homeSp.setFriendlyName("Test Provider"); + passpointConfig.setHomeSp(homeSp); + Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>(); + osuProviderConfigMap.put(provider, passpointConfig); + when(mMockWifiManager + .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(provider))) + .thenReturn(osuProviderConfigMap); + + when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager); + + AccessPoint osuAccessPoint = new AccessPoint(mMockContext, provider, mScanResults); + osuAccessPoint.setListener(mMockAccessPointListener); + osuAccessPoint.startOsuProvisioning(mMockConnectListener); + + AccessPoint.AccessPointProvisioningCallback provisioningCallback = + osuAccessPoint.new AccessPointProvisioningCallback(); + provisioningCallback.onProvisioningComplete(); + + verify(mMockConnectListener).onFailure(anyInt()); + } } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java index edf414ddf323..683ec8bb5a6c 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java @@ -270,7 +270,7 @@ public class WifiTrackerTest { SystemClock.elapsedRealtime() * 1000 /* microsecond timestamp */); } - private static ScanResult buildStaleScanResult() { + private static ScanResult buildScanResultWithTimestamp(long timestampMillis) { return new ScanResult( WifiSsid.createFromAsciiEncoded(SSID_3), BSSID_3, @@ -280,7 +280,7 @@ public class WifiTrackerTest { "", // capabilities RSSI_3, 0, // frequency - 0 /* microsecond timestamp */); + timestampMillis * 1000 /* microsecond timestamp */); } private static WifiConfiguration buildPasspointConfiguration(String fqdn, String friendlyName) { @@ -379,6 +379,12 @@ public class WifiTrackerTest { tracker.mReceiver.onReceive(mContext, i); } + private void sendFailedScanResults(WifiTracker tracker) throws InterruptedException { + Intent i = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + i.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, false); + tracker.mReceiver.onReceive(mContext, i); + } + private void sendUpdatedScores() throws InterruptedException { Bundle attr1 = new Bundle(); attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve1); @@ -982,8 +988,8 @@ public class WifiTrackerTest { @Test public void onStart_updateScanResults_evictOldScanResult() { - when(mockWifiManager.getScanResults()).thenReturn( - Arrays.asList(buildScanResult1(), buildScanResult2(), buildStaleScanResult())); + when(mockWifiManager.getScanResults()).thenReturn(Arrays.asList( + buildScanResult1(), buildScanResult2(), buildScanResultWithTimestamp(0))); WifiTracker tracker = createMockedWifiTracker(); tracker.forceUpdate(); @@ -995,6 +1001,33 @@ public class WifiTrackerTest { } /** + * Verifies that a failed scan reported on SCAN_RESULTS_AVAILABLE_ACTION should increase the + * ScanResult eviction timeout to twice the default. + */ + @Test + public void failedScan_increasesEvictionTimeout() throws InterruptedException { + when(mockWifiManager.getScanResults()).thenReturn(Arrays.asList( + buildScanResult1(), buildScanResult2(), buildScanResultWithTimestamp( + SystemClock.elapsedRealtime() - WifiTracker.MAX_SCAN_RESULT_AGE_MILLIS))); + WifiTracker tracker = createMockedWifiTracker(); + + sendFailedScanResults(tracker); + + // Failed scan increases timeout window to include the stale scan + assertThat(tracker.getAccessPoints()).hasSize(3); + assertThat(tracker.getAccessPoints().get(0).getBssid()).isEqualTo(BSSID_1); + assertThat(tracker.getAccessPoints().get(1).getBssid()).isEqualTo(BSSID_2); + assertThat(tracker.getAccessPoints().get(2).getBssid()).isEqualTo(BSSID_3); + + sendScanResults(tracker); + + // Successful scan resets the timeout window to remove the stale scan + assertThat(tracker.getAccessPoints()).hasSize(2); + assertThat(tracker.getAccessPoints().get(0).getBssid()).isEqualTo(BSSID_1); + assertThat(tracker.getAccessPoints().get(1).getBssid()).isEqualTo(BSSID_2); + } + + /** * Verifies that updatePasspointAccessPoints will only return AccessPoints whose * isPasspoint() evaluates as true. */ diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index b713e08eb67e..b228cf7c10c6 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -15,15 +15,20 @@ */ package com.android.settingslib.bluetooth; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; import android.content.Context; import android.graphics.drawable.Drawable; import android.util.Pair; +import com.android.settingslib.widget.AdaptiveIcon; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,6 +43,9 @@ public class BluetoothUtilsTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private BluetoothDevice mBluetoothDevice; + private Context mContext; @Before @@ -66,4 +74,16 @@ public class BluetoothUtilsTest { verify(mContext).getDrawable(com.android.internal.R.drawable.ic_bt_laptop); } + + @Test + public void getBtRainbowDrawableWithDescription_normalHeadset_returnAdaptiveIcon() { + when(mBluetoothDevice.getMetadata( + BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)).thenReturn("false"); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mCachedBluetoothDevice.getAddress()).thenReturn("1f:aa:bb"); + + assertThat(BluetoothUtils.getBtRainbowDrawableWithDescription( + RuntimeEnvironment.application, + mCachedBluetoothDevice).first).isInstanceOf(AdaptiveIcon.class); + } }
\ No newline at end of file diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java new file mode 100644 index 000000000000..ed6b9b0a135e --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.widget; + +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB; +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Icon; +import android.graphics.drawable.ShapeDrawable; +import android.os.Bundle; + +import com.android.settingslib.R; +import com.android.settingslib.drawer.CategoryKey; +import com.android.settingslib.drawer.Tile; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class AdaptiveIconTest { + + private Context mContext; + private ActivityInfo mActivityInfo; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mActivityInfo = new ActivityInfo(); + mActivityInfo.packageName = mContext.getPackageName(); + mActivityInfo.name = "class"; + mActivityInfo.metaData = new Bundle(); + } + + @Test + public void createIcon_shouldSetBackgroundAndInset() { + final AdaptiveIcon icon = + new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + + assertThat(icon.getNumberOfLayers()).isEqualTo(2); + assertThat(icon.getDrawable(0)).isInstanceOf(AdaptiveIconShapeDrawable.class); + } + + @Test + public void setBackgroundColor_shouldUpdateColorFilter() { + final AdaptiveIcon icon = + spy(new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK))); + final ShapeDrawable background = mock(ShapeDrawable.class); + when(icon.getDrawable(0)).thenReturn(background); + + icon.setBackgroundColor(Color.BLUE); + + verify(background).setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_ATOP); + } + + @Test + public void setBackgroundColor_externalTileWithBackgroundColorRawValue_shouldUpdateIcon() { + final Tile tile = spy(new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); + mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, 0xff0000); + doReturn(Icon.createWithResource(mContext, R.drawable.ic_system_update)) + .when(tile).getIcon(mContext); + final AdaptiveIcon icon = + new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + + icon.setBackgroundColor(mContext, tile); + assertThat(icon.mBackgroundColor).isEqualTo(0xff0000); + } + + @Test + public void setBackgroundColor_tileWithoutBackgroundColor_shouldSetDefaultBackgroundColor() { + final Tile tile = spy(new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); + doReturn(Icon.createWithResource(mContext, R.drawable.ic_system_update)) + .when(tile).getIcon(mContext); + final AdaptiveIcon icon = new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + + icon.setBackgroundColor(mContext, tile); + + assertThat(icon.mBackgroundColor) + .isEqualTo(mContext.getColor(R.color.homepage_generic_icon_background)); + } + + @Test + public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() { + final Tile tile = spy(new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); + mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, + R.color.bt_outline_color); + doReturn(Icon.createWithResource(mContext, R.drawable.ic_system_update)) + .when(tile).getIcon(mContext); + + final AdaptiveIcon icon = + new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + icon.setBackgroundColor(mContext, tile); + + assertThat(icon.mBackgroundColor) + .isEqualTo(mContext.getColor(R.color.bt_outline_color)); + } + + @Test + public void getConstantState_returnCorrectState() { + final AdaptiveIcon icon = + new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); + icon.setBackgroundColor(Color.YELLOW); + + final AdaptiveIcon.AdaptiveConstantState state = + (AdaptiveIcon.AdaptiveConstantState) icon.getConstantState(); + + assertThat(state.mColor).isEqualTo(Color.YELLOW); + assertThat(state.mContext).isEqualTo(mContext); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java new file mode 100644 index 000000000000..71d55bc3de2a --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.widget; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.res.Resources; +import android.graphics.Paint; + +import com.android.settingslib.R; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class AdaptiveOutlineDrawableTest { + + @Test + public void constructor_initPaint() { + final Resources resources = RuntimeEnvironment.application.getResources(); + final AdaptiveOutlineDrawable drawable = new AdaptiveOutlineDrawable(resources, null); + + assertThat(drawable.mOutlinePaint.getStyle()).isEqualTo(Paint.Style.STROKE); + assertThat(drawable.mOutlinePaint.getStrokeWidth()).isWithin(0.01f).of( + resources.getDimension(R.dimen.adaptive_outline_stroke)); + } + +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 4b342b37ac20..296f7a144ffe 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -16,6 +16,7 @@ package com.android.providers.settings; +import static android.os.Process.INVALID_UID; import static android.os.Process.ROOT_UID; import static android.os.Process.SHELL_UID; import static android.os.Process.SYSTEM_UID; @@ -2777,7 +2778,7 @@ public class SettingsProvider extends ContentProvider { boolean someSettingChanged = false; Setting setting = settingsState.getSettingLocked(name); if (!SettingsState.isSystemPackage(getContext(), - setting.getPackageName())) { + setting.getPackageName(), INVALID_UID, userId)) { if (prefix != null && !setting.getName().startsWith(prefix)) { continue; } @@ -2797,7 +2798,7 @@ public class SettingsProvider extends ContentProvider { boolean someSettingChanged = false; Setting setting = settingsState.getSettingLocked(name); if (!SettingsState.isSystemPackage(getContext(), - setting.getPackageName())) { + setting.getPackageName(), INVALID_UID, userId)) { if (prefix != null && !setting.getName().startsWith(prefix)) { continue; } @@ -4410,7 +4411,7 @@ public class SettingsProvider extends ContentProvider { } try { final boolean systemSet = SettingsState.isSystemPackage(getContext(), - setting.getPackageName(), callingUid); + setting.getPackageName(), callingUid, userId); if (systemSet) { settings.insertSettingLocked(name, setting.getValue(), setting.getTag(), true, setting.getPackageName()); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 521163f50ca1..c05c4cdf72d7 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -17,6 +17,7 @@ package com.android.providers.settings; import static android.os.Process.FIRST_APPLICATION_UID; +import static android.os.Process.INVALID_UID; import android.annotation.NonNull; import android.content.Context; @@ -1124,11 +1125,16 @@ final class SettingsState { return sb.toString(); } + // Check if a specific package belonging to the caller is part of the system package. public static boolean isSystemPackage(Context context, String packageName) { - return isSystemPackage(context, packageName, Binder.getCallingUid()); + final int callingUid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getUserId(callingUid); + return isSystemPackage(context, packageName, callingUid, callingUserId); } - public static boolean isSystemPackage(Context context, String packageName, int callingUid) { + // Check if a specific package, uid, and user ID are part of the system package. + public static boolean isSystemPackage(Context context, String packageName, int uid, + int userId) { synchronized (sLock) { if (SYSTEM_PACKAGE_NAME.equals(packageName)) { return true; @@ -1140,26 +1146,19 @@ final class SettingsState { return false; } - // Native services running as a special UID get a pass - final int callingAppId = UserHandle.getAppId(callingUid); - if (callingAppId < FIRST_APPLICATION_UID) { - sSystemUids.put(callingAppId, callingAppId); - return true; + if (uid != INVALID_UID) { + // Native services running as a special UID get a pass + final int callingAppId = UserHandle.getAppId(uid); + if (callingAppId < FIRST_APPLICATION_UID) { + sSystemUids.put(callingAppId, callingAppId); + return true; + } } - // While some callers may have permissions to manipulate cross user - // settings or some settings are stored in the parent of a managed - // profile for the purpose of determining whether the other end is a - // system component we need to use the user id of the caller for - // pulling information about the caller from the package manager. - final int callingUserId = UserHandle.getUserId(callingUid); - final long identity = Binder.clearCallingIdentity(); try { - final int uid; try { - uid = context.getPackageManager().getPackageUidAsUser(packageName, 0, - callingUserId); + uid = context.getPackageManager().getPackageUidAsUser(packageName, 0, userId); } catch (PackageManager.NameNotFoundException e) { return false; } @@ -1187,7 +1186,7 @@ final class SettingsState { PackageInfo packageInfo; try { packageInfo = context.getPackageManager().getPackageInfoAsUser( - packageName, PackageManager.GET_SIGNATURES, callingUserId); + packageName, PackageManager.GET_SIGNATURES, userId); if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0 && (packageInfo.applicationInfo.flags diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index d6e61ebbbd6e..2a9456dd723c 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -125,6 +125,8 @@ <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS" /> <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> + <!-- Shell only holds android.permission.NETWORK_SCAN in order to to enable CTS testing --> + <uses-permission android:name="android.permission.NETWORK_SCAN" /> <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" /> <uses-permission android:name="android.permission.REGISTER_CONNECTION_MANAGER" /> <uses-permission android:name="android.permission.REGISTER_SIM_SUBSCRIPTION" /> @@ -166,6 +168,9 @@ <uses-permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED" /> <uses-permission android:name="android.permission.MANAGE_WIFI_WHEN_WIRELESS_CONSENT_REQUIRED" /> <uses-permission android:name="android.permission.WATCH_APPOPS" /> + <!-- Permission needed to invoke DynamicSystem (AOT) --> + <uses-permission android:name="android.permission.INSTALL_DYNAMIC_SYSTEM" /> + <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> <uses-permission android:name="android.permission.SUSPEND_APPS" /> @@ -178,6 +183,8 @@ <!-- Permission needed to run network tests in CTS --> <uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" /> + <!-- Permission needed to test tcp keepalive offload. --> + <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" /> <!-- Permission needed to run keyguard manager tests in CTS --> <uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" /> diff --git a/packages/Shell/tests/Android.mk b/packages/Shell/tests/Android.mk index b93ddde16e4a..44ff3383d566 100644 --- a/packages/Shell/tests/Android.mk +++ b/packages/Shell/tests/Android.mk @@ -9,7 +9,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-test \ + androidx.test.rules \ mockito-target-minus-junit4 \ ub-uiautomator \ junit \ diff --git a/packages/Shell/tests/AndroidManifest.xml b/packages/Shell/tests/AndroidManifest.xml index 6d564c640fcd..e845ef95b28e 100644 --- a/packages/Shell/tests/AndroidManifest.xml +++ b/packages/Shell/tests/AndroidManifest.xml @@ -36,7 +36,7 @@ </activity> </application> - <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.shell" android:label="Tests for Shell" /> diff --git a/packages/Shell/tests/AndroidTest.xml b/packages/Shell/tests/AndroidTest.xml index e592d82bfaf7..e03b68a03d0e 100644 --- a/packages/Shell/tests/AndroidTest.xml +++ b/packages/Shell/tests/AndroidTest.xml @@ -22,7 +22,7 @@ <option name="test-tag" value="ShellTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.shell.tests" /> - <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> <option name="hidden-api-checks" value="false"/> </test> </configuration> diff --git a/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java b/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java index cef74ae39c60..433eca23080e 100644 --- a/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java +++ b/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java @@ -20,7 +20,6 @@ import static com.android.shell.BugreportProgressService.findSendToAccount; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; -import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; @@ -29,12 +28,12 @@ import android.accounts.AccountManager; import android.content.Context; import android.os.UserHandle; import android.os.UserManager; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; import android.test.mock.MockContext; import android.util.Pair; -import org.junit.Before; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java index e69b0a81b97e..3a71632cf1ca 100644 --- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java +++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java @@ -42,34 +42,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import java.io.BufferedOutputStream; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -import libcore.io.Streams; - -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.junit.runner.RunWith; - import android.app.ActivityManager; import android.app.ActivityManager.RunningServiceInfo; import android.app.Instrumentation; @@ -82,9 +54,6 @@ import android.os.Bundle; import android.os.SystemClock; import android.os.SystemProperties; import android.service.notification.StatusBarNotification; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.LargeTest; -import android.support.test.runner.AndroidJUnit4; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject; import android.support.test.uiautomator.UiObjectNotFoundException; @@ -92,8 +61,39 @@ import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + import com.android.shell.ActionSendMultipleConsumerActivity.CustomActionSendMultipleListener; +import libcore.io.Streams; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.junit.runner.RunWith; + +import java.io.BufferedOutputStream; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + /** * Integration tests for {@link BugreportReceiver}. * <p> diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java index 7b7657a3d646..105be46c05d7 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java @@ -28,7 +28,7 @@ import java.util.TimeZone; public interface ClockPlugin extends Plugin { String ACTION = "com.android.systemui.action.PLUGIN_CLOCK"; - int VERSION = 2; + int VERSION = 3; /** * Get the name of the clock face. @@ -48,6 +48,17 @@ public interface ClockPlugin extends Plugin { Bitmap getThumbnail(); /** + * Get preview images of clock face to be shown in the picker app. + * + * Preview image should be realistic and show what the clock face will look like on AOD and lock + * screen. + * + * @param width width of the preview image, should be the same as device width in pixels. + * @param height height of the preview image, should be the same as device height in pixels. + */ + Bitmap getPreview(int width, int height); + + /** * Get clock view. * @return clock view from plugin. */ diff --git a/packages/SystemUI/res/drawable/ic_alarm.xml b/packages/SystemUI/res/drawable/ic_alarm.xml new file mode 100644 index 000000000000..1c1adcd149bf --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_alarm.xml @@ -0,0 +1,25 @@ +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="17dp" + android:width="17dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#FFFFFF" + android:pathData="M13,8h-2v5.41l3.79,3.8 1.42,-1.42 -3.21,-3.2zM12,4c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9 -4.03,-9 -9,-9zM12,20c-3.86,0 -7,-3.14 -7,-7s3.14,-7 7,-7 7,3.14 7,7 -3.14,7 -7,7zM16.056,3.346l1.282,-1.535 4.607,3.85 -1.28,1.54zM2.056,5.654L6.663,1.81l1.28,1.536L3.338,7.19z"/> + +</vector> diff --git a/packages/SystemUI/res/drawable/ic_alarm_dim.xml b/packages/SystemUI/res/drawable/ic_alarm_dim.xml new file mode 100644 index 000000000000..37ab873d504c --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_alarm_dim.xml @@ -0,0 +1,26 @@ +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="17dp" + android:width="17dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#FFFFFF" + android:fillAlpha="0.3" + android:pathData="M13,8h-2v5.41l3.79,3.8 1.42,-1.42 -3.21,-3.2zM12,4c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9 -4.03,-9 -9,-9zM12,20c-3.86,0 -7,-3.14 -7,-7s3.14,-7 7,-7 7,3.14 7,7 -3.14,7 -7,7zM16.056,3.346l1.282,-1.535 4.607,3.85 -1.28,1.54zM2.056,5.654L6.663,1.81l1.28,1.536L3.338,7.19z"/> + +</vector> diff --git a/packages/SystemUI/res/drawable/ic_bluetooth_connected.xml b/packages/SystemUI/res/drawable/ic_bluetooth_connected.xml new file mode 100644 index 000000000000..125082c49bdc --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_bluetooth_connected.xml @@ -0,0 +1,30 @@ +<!-- +Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="17dp" + android:height="17dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="@android:color/white" + android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41L11,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59L13,5.83zM14.88,16.29L13,18.17v-3.76l1.88,1.88z"/> + <path + android:fillColor="@android:color/white" + android:pathData="M5,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/> + <path + android:fillColor="@android:color/white" + android:pathData="M19,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_camera.xml b/packages/SystemUI/res/drawable/ic_camera.xml new file mode 100644 index 000000000000..b330875864d3 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_camera.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="17dp" + android:height="17dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFF" + android:pathData="M20,5h-3.17L15,3H9L7.17,5H4C2.9,5 2,5.9 2,7v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V7C22,5.9 21.1,5 20,5zM20,19H4V7h16V19zM12,9c-2.21,0 -4,1.79 -4,4c0,2.21 1.79,4 4,4s4,-1.79 4,-4C16,10.79 14.21,9 12,9z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_off.xml b/packages/SystemUI/res/drawable/ic_cast.xml index 9e57577d13e1..a2c2eb2806f6 100644 --- a/packages/SystemUI/res/drawable/ic_qs_cast_off.xml +++ b/packages/SystemUI/res/drawable/ic_cast.xml @@ -14,11 +14,11 @@ Copyright (C) 2017 The Android Open Source Project limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="64dp" - android:height="64dp" + android:width="24dp" + android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path - android:pathData="M1,18v2c0,0.55 0.45,1 1,1h2c0,-1.66 -1.34,-3 -3,-3zM0.97,15.06c-0.01,0.51 0.35,0.93 0.85,1.02 2.08,0.36 3.74,2 4.1,4.08 0.09,0.48 0.5,0.84 0.99,0.84 0.61,0 1.09,-0.54 1,-1.14a6.996,6.996 0,0 0,-5.8 -5.78c-0.59,-0.09 -1.12,0.38 -1.14,0.98zM0.97,11.03c-0.01,0.52 0.37,0.96 0.88,1.01 4.26,0.43 7.68,3.82 8.1,8.08 0.05,0.5 0.48,0.88 0.99,0.88 0.59,0 1.06,-0.51 1,-1.1 -0.52,-5.21 -4.66,-9.34 -9.87,-9.85 -0.57,-0.05 -1.08,0.4 -1.1,0.98zM21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2z" - android:fillColor="#FFFFFFFF" /> + android:fillColor="@android:color/white" + android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM1,18v3h3c0,-1.66 -1.34,-3 -3,-3zM1,14v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM1,10v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_vpn.xml b/packages/SystemUI/res/drawable/ic_cast_connected.xml index 6567d123f5d5..995fd498f19e 100644 --- a/packages/SystemUI/res/drawable/ic_qs_vpn.xml +++ b/packages/SystemUI/res/drawable/ic_cast_connected.xml @@ -1,5 +1,5 @@ <!-- -Copyright (C) 2014 The Android Open Source Project +Copyright (C) 2017 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,14 +14,13 @@ Copyright (C) 2014 The Android Open Source Project limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="12.0dp" - android:height="12.0dp" + android:width="17dp" + android:height="17dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> + <path android:fillColor="#FFFFFFFF" - android:pathData="M12.09,9C11.11,7.5 9.43,6.5 7.5,6.5C4.46,6.5 2,8.96 2,12c0,3.04 2.46,5.5 5.5,5.5c1.93,0 3.61,-1 4.59,-2.5H14v3h6v-3h2V9H12.09zM20,13h-2v3h-2v-3h-5.16c-0.43,1.44 -1.76,2.5 -3.34,2.5C5.57,15.5 4,13.93 4,12c0,-1.93 1.57,-3.5 3.5,-3.5c1.58,0 2.9,1.06 3.34,2.5H20V13z"/> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M7.5,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/> + android:pathData="M1,18v3h3C4,19.34 2.66,18 1,18zM1,14v2c2.76,0 5,2.24 5,5h2C8,17.13 4.87,14 1,14zM19,7H5v1.63c3.96,1.28 7.09,4.41 8.37,8.37H19V7zM1,10v2c4.97,0 9,4.03 9,9h2C12,14.92 7.07,10 1,10zM21,3H3C1.9,3 1,3.9 1,5v3h2V5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2V5C23,3.9 22.1,3 21,3"/> + </vector> diff --git a/packages/SystemUI/res/drawable/ic_data_saver.xml b/packages/SystemUI/res/drawable/ic_data_saver.xml index 0f027ee7afb1..cc3f539db8d3 100644 --- a/packages/SystemUI/res/drawable/ic_data_saver.xml +++ b/packages/SystemUI/res/drawable/ic_data_saver.xml @@ -14,15 +14,11 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="18dp" + android:height="18dp" android:viewportWidth="24.0" - android:viewportHeight="24.0" - android:tint="?android:attr/colorControlNormal"> + android:viewportHeight="24.0"> <path - android:pathData="M16,12c0,0.55 -0.45,1 -1,1h-2v2c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1v-2L9,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h2L11,9c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v2h2c0.55,0 1,0.45 1,1zM17.69,16.87a7.437,7.437 0,0 1,-5.93 2.63c-3.82,-0.12 -7.03,-3.25 -7.25,-7.07 -0.21,-3.84 2.48,-7.1 6.07,-7.79 0.24,-0.04 0.42,-0.24 0.42,-0.48L11,2.62c0,-0.3 -0.27,-0.55 -0.57,-0.5A10.02,10.02 0,0 0,2.09 13.4c0.59,4.4 4.16,7.94 8.56,8.52a9.99,9.99 0,0 0,9.14 -3.65,0.5 0.5,0 0,0 -0.15,-0.74l-1.32,-0.76a0.469,0.469 0,0 0,-0.63 0.1z" + android:pathData="M11,8v3L8,11v2h3v3h2v-3h3v-2h-3L13,8zM13,2.05v3.03c3.39,0.49 6,3.39 6,6.92 0,0.9 -0.18,1.75 -0.48,2.54l2.6,1.53c0.56,-1.24 0.88,-2.62 0.88,-4.07 0,-5.18 -3.95,-9.45 -9,-9.95zM12,19c-3.87,0 -7,-3.13 -7,-7 0,-3.53 2.61,-6.43 6,-6.92L11,2.05c-5.06,0.5 -9,4.76 -9,9.95 0,5.52 4.47,10 9.99,10 3.31,0 6.24,-1.61 8.06,-4.09l-2.6,-1.53C16.17,17.98 14.21,19 12,19z" android:fillColor="#FFFFFFFF"/> - <path - android:pathData="M13.41,4.64a0.493,0.493 0,0 1,-0.41 -0.48L13,2.62c0,-0.3 0.27,-0.55 0.57,-0.5C18.35,2.88 22,7.01 22,12c0,1.23 -0.23,2.41 -0.64,3.5 -0.11,0.28 -0.45,0.4 -0.72,0.24l-1.33,-0.77a0.484,0.484 0,0 1,-0.21 -0.59c0.25,-0.75 0.39,-1.55 0.39,-2.38 0.01,-3.65 -2.62,-6.69 -6.08,-7.36z" - android:fillColor="#54FFFFFF" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_data_saver_off.xml b/packages/SystemUI/res/drawable/ic_data_saver_off.xml index 29e6c912b797..d8e9bc46b434 100644 --- a/packages/SystemUI/res/drawable/ic_data_saver_off.xml +++ b/packages/SystemUI/res/drawable/ic_data_saver_off.xml @@ -17,9 +17,8 @@ android:width="24.0dp" android:height="24.0dp" android:viewportWidth="24.0" - android:viewportHeight="24.0" - android:tint="?android:attr/colorControlNormal"> + android:viewportHeight="24.0"> <path android:fillColor="#FFFFFFFF" - android:pathData="M18.32,16.75l1.32,0.76c0.26,0.15 0.34,0.51 0.15,0.74 -2.09,2.6 -5.44,4.14 -9.14,3.65 -4.4,-0.58 -7.96,-4.12 -8.56,-8.52C1.34,7.8 5.21,2.95 10.43,2.12c0.3,-0.05 0.57,0.2 0.57,0.5v1.53c0,0.24 -0.18,0.44 -0.41,0.49 -3.6,0.69 -6.29,3.95 -6.07,7.79 0.21,3.82 3.43,6.95 7.25,7.07 2.37,0.08 4.51,-0.96 5.93,-2.63a0.48,0.48 0,0 1,0.62 -0.12zM19.5,12c0,0.83 -0.14,1.63 -0.39,2.38 -0.08,0.23 0.01,0.47 0.21,0.59l1.33,0.77c0.26,0.15 0.61,0.04 0.72,-0.24 0.4,-1.09 0.63,-2.27 0.63,-3.5 0,-4.99 -3.65,-9.12 -8.43,-9.88 -0.3,-0.04 -0.57,0.2 -0.57,0.5v1.53c0,0.24 0.18,0.44 0.41,0.48 3.46,0.68 6.09,3.72 6.09,7.37z" /> + android:pathData="M13,2.05v3.03c3.39,0.49 6,3.39 6,6.92 0,0.9 -0.18,1.75 -0.48,2.54l2.6,1.53c0.56,-1.24 0.88,-2.62 0.88,-4.07 0,-5.18 -3.95,-9.45 -9,-9.95zM12,19c-3.87,0 -7,-3.13 -7,-7 0,-3.53 2.61,-6.43 6,-6.92V2.05c-5.06,0.5 -9,4.76 -9,9.95 0,5.52 4.47,10 9.99,10 3.31,0 6.24,-1.61 8.06,-4.09l-2.6,-1.53C16.17,17.98 14.21,19 12,19z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_headset.xml b/packages/SystemUI/res/drawable/ic_headset.xml index 27efe80c5ae0..797a80ac6916 100644 --- a/packages/SystemUI/res/drawable/ic_headset.xml +++ b/packages/SystemUI/res/drawable/ic_headset.xml @@ -13,19 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. --> -<inset xmlns:android="http://schemas.android.com/apk/res/android" - android:insetLeft="2.5dp" - android:insetRight="2.5dp"> - <vector - android:width="17.0dp" - android:height="17.0dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <group - android:translateY="-1"> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M19,15v3c0,0.55 -0.45,1 -1,1h-1v-4H19M7,15v4H6c-0.55,0 -1,-0.45 -1,-1v-3H7M12,2c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h3c1.66,0 3,-1.34 3,-3v-7C21,6.03 16.97,2 12,2L12,2z"/> - </group> - </vector> -</inset> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="17.0dp" + android:height="17.0dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M19,15v3c0,0.55 -0.45,1 -1,1h-1v-4h2M7,15v4H6c-0.55,0 -1,-0.45 -1,-1v-3h2m5,-13c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h3c1.66,0 3,-1.34 3,-3v-7c0,-4.97 -4.03,-9 -9,-9z" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_headset_mic.xml b/packages/SystemUI/res/drawable/ic_headset_mic.xml index 1260e0f42949..b3f006d15bb5 100644 --- a/packages/SystemUI/res/drawable/ic_headset_mic.xml +++ b/packages/SystemUI/res/drawable/ic_headset_mic.xml @@ -13,16 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. --> -<inset xmlns:android="http://schemas.android.com/apk/res/android" - android:insetLeft="2.5dp" - android:insetRight="2.5dp"> - <vector - android:width="17.0dp" - android:height="17.0dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-1.71C5,6.45 7.96,3.11 11.79,3C15.76,2.89 19,6.06 19,10v2h-4v8h4v1h-6v2h6c1.1,0 2,-0.9 2,-2V10C21,5.03 16.97,1 12,1zM7,14v4H6c-0.55,0 -1,-0.45 -1,-1v-3H7zM19,18h-2v-4h2V18z"/> - </vector> -</inset> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="17.0dp" + android:height="17.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-1.71C5,6.45 7.96,3.11 11.79,3C15.76,2.89 19,6.06 19,10v2h-4v8h4v1h-6v2h6c1.1,0 2,-0.9 2,-2V10C21,5.03 16.97,1 12,1zM7,14v4H6c-0.55,0 -1,-0.45 -1,-1v-3H7zM19,18h-2v-4h2V18z" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_hotspot.xml b/packages/SystemUI/res/drawable/ic_hotspot.xml index 8450bf6ce60c..b6fa798d8bb6 100644 --- a/packages/SystemUI/res/drawable/ic_hotspot.xml +++ b/packages/SystemUI/res/drawable/ic_hotspot.xml @@ -15,14 +15,12 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="48dp" - android:height="48dp" + android:width="18dp" + android:height="18dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> - <group - android:translateY="-0.32"> - <path - android:pathData="M12,11c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,13a6,6 0,0 0,-6.75 -5.95c-2.62,0.32 -4.78,2.41 -5.18,5.02 -0.32,2.14 0.49,4.11 1.92,5.39 0.48,0.43 1.24,0.33 1.56,-0.23 0.24,-0.42 0.14,-0.94 -0.22,-1.26a3.99,3.99 0,0 1,-1.22 -3.94,3.954 3.954,0 0,1 2.9,-2.91A4.007,4.007 0,0 1,16 13c0,1.18 -0.51,2.23 -1.33,2.96 -0.36,0.33 -0.47,0.85 -0.23,1.27 0.31,0.54 1.04,0.69 1.5,0.28A5.97,5.97 0,0 0,18 13zM10.83,3.07c-4.62,0.52 -8.35,4.33 -8.78,8.96a9.966,9.966 0,0 0,4.02 9.01c0.48,0.35 1.16,0.2 1.46,-0.31 0.25,-0.43 0.14,-0.99 -0.26,-1.29 -2.28,-1.69 -3.65,-4.55 -3.16,-7.7 0.54,-3.5 3.46,-6.29 6.98,-6.68C15.91,4.51 20,8.28 20,13c0,2.65 -1.29,4.98 -3.27,6.44 -0.4,0.3 -0.51,0.85 -0.26,1.29 0.3,0.52 0.98,0.66 1.46,0.31A9.96,9.96 0,0 0,22 13c0,-5.91 -5.13,-10.62 -11.17,-9.93z" - android:fillColor="#FFFFFFFF"/> - </group> + <path + android:fillColor="#FFFFFF" + android:pathData="M12,7c-3.31,0 -6,2.69 -6,6 0,1.66 0.68,3.15 1.76,4.24l1.42,-1.42C8.45,15.1 8,14.11 8,13c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,1.11 -0.45,2.1 -1.18,2.82l1.42,1.42C17.32,16.15 18,14.66 18,13c0,-3.31 -2.69,-6 -6,-6zM12,3C6.48,3 2,7.48 2,13c0,2.76 1.12,5.26 2.93,7.07l1.41,-1.41C4.9,17.21 4,15.21 4,13c0,-4.42 3.58,-8 8,-8s8,3.58 8,8c0,2.21 -0.9,4.2 -2.35,5.65l1.41,1.41C20.88,18.26 22,15.76 22,13c0,-5.52 -4.48,-10 -10,-10zM12,11c-1.1,0 -2,0.9 -2,2 0,0.55 0.23,1.05 0.59,1.41 0.36,0.36 0.86,0.59 1.41,0.59s1.05,-0.23 1.41,-0.59c0.36,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2z" /> + </vector> diff --git a/packages/SystemUI/res/drawable/ic_hotspot_unavailable.xml b/packages/SystemUI/res/drawable/ic_hotspot_unavailable.xml deleted file mode 100644 index 7641998a8cd9..000000000000 --- a/packages/SystemUI/res/drawable/ic_hotspot_unavailable.xml +++ /dev/null @@ -1,61 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2016 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:name="root" - android:height="48dp" - android:width="48dp" - android:viewportHeight="48" - android:viewportWidth="48" > - <group - android:name="ic_hotspot" - android:translateX="23.97354" - android:translateY="24.26306" > - <group - android:name="ic_hotspot_pivot" - android:translateX="-23.21545" - android:translateY="-18.86649" > - <clip-path - android:name="mask" - android:pathData="M 38.8337860107,-40.3974914551 c 0.0,0.0 -38.4077911377,30.8523712158 -38.4077911377,30.8523712158 c 0.0,0.0 43.1884765625,43.515335083 43.1884765625,43.515335083 c 0.0,0.0 -2.4169921875,2.57838439941 -2.4169921875,2.57838439941 c 0.0,0.0 -42.9885101318,-43.0112609863 -42.9885101318,-43.0112609863 c 0.0,0.0 -32.6199798584,25.1699066162 -32.6199798584,25.1699066162 c 0.0,0.0 55.9664764404,69.742401123 55.9664764404,69.742401123 c 0.0,0.0 27.6589050293,-22.6579437256 27.6589050293,-22.6579437256 c 0.0,0.0 -30.8645172119,-34.00390625 -30.8645172119,-34.00390625 c 0.0,0.0 2.70756530762,-1.99278259277 2.70756530762,-1.99278259277 c 0.0,0.0 1.53030395508,-0.876571655273 1.53030395508,-0.876571655274 c 0.0,0.0 2.85780334473,-3.12069702148 2.85780334473,-3.12069702148 c 0.0,0.0 13.0984039307,13.025604248 13.0984039307,13.025604248 c 0.0,0.0 -3.13299560547,2.82977294922 -3.13299560547,2.82977294922 c 0.0,0.0 16.571762085,22.0471801758 16.571762085,22.0471801758 c 0.0,0.0 42.8175811768,-34.3554534912 42.8175811768,-34.3554534912 c 0.0,0.0 -55.9664916992,-69.7423400879 -55.9664916992,-69.7423400879 Z" /> - <group - android:name="cross" > - <path - android:name="cross_1" - android:pathData="M 4.44044494629,2.24310302734 c 0.0,0.0 35.4000396729,35.3999633789 35.4000396729,35.3999633789 " - android:strokeColor="#FFFFFFFF" - android:strokeAlpha="1" - android:strokeWidth="3.5" - android:fillColor="#00000000" /> - </group> - <group - android:name="hotspot" - android:translateX="23.481" - android:translateY="18.71151" > - <group - android:name="circles" - android:translateX="-0.23909" - android:translateY="-0.10807" > - <path - android:name="circle_3" - android:pathData="M 0.0843505859375,-2.93901062012 c -2.30102539062,0.0 -4.16702270508,1.86602783203 -4.16702270508,4.16702270508 c 0.0,2.29898071289 1.86599731445,4.16598510742 4.16702270508,4.16598510742 c 2.29998779297,0.0 4.16598510742,-1.86700439453 4.16598510742,-4.16598510742 c 0.0,-2.30099487305 -1.86599731445,-4.16702270508 -4.16598510742,-4.16702270508 Z M 11.1185302734,5.83390808105 c 0.0,0.0 0.0009765625,0.00100708007812 0.0009765625,0.00100708007812 c 0.14501953125,-0.356994628906 0.27099609375,-0.725006103516 0.382995605469,-1.09799194336 c 0.0570068359375,-0.195007324219 0.101013183594,-0.394989013672 0.149017333984,-0.595001220703 c 0.0690002441406,-0.281005859375 0.126983642578,-0.563995361328 0.175994873047,-0.851989746094 c 0.0270080566406,-0.169006347656 0.0559997558594,-0.337005615234 0.0759887695313,-0.509002685547 c 0.0580139160156,-0.468017578125 0.0970153808594,-0.942993164062 0.0970153808593,-1.4280090332 c 0.0,0.0 0.0,-0.00100708007812 0.0,-0.00100708007812 c 0.0,-5.03900146484 -3.11099243164,-9.3450012207 -7.5119934082,-11.1229858398 c -0.00601196289062,-0.00299072265625 -0.0130004882812,-0.0050048828125 -0.0190124511719,-0.00701904296875 c -0.686004638672,-0.275970458984 -1.39999389648,-0.492980957031 -2.14099121094,-0.638977050781 c -0.072998046875,-0.0150146484375 -0.149017333984,-0.02099609375 -0.222991943359,-0.0339965820313 c -0.302001953125,-0.0540161132812 -0.605010986328,-0.106018066406 -0.916015625,-0.136016845703 c -0.389984130859,-0.0390014648438 -0.786987304688,-0.0599975585938 -1.18899536133,-0.0599975585937 c -0.402008056641,0.0 -0.799011230469,0.02099609375 -1.19000244141,0.0599975585937 c -0.304992675781,0.0299987792969 -0.602996826172,0.0809936523438 -0.901000976563,0.132995605469 c -0.0790100097656,0.0150146484375 -0.160003662109,0.02099609375 -0.238006591797,0.0370178222656 c -0.368988037109,0.0719909667969 -0.730987548828,0.164001464844 -1.08700561523,0.269989013672 c -0.00299072265625,0.00100708007812 -0.0059814453125,0.00201416015625 -0.00900268554687,0.0020141601562 c -0.351989746094,0.10498046875 -0.694000244141,0.226989746094 -1.0309753418,0.361999511719 c -0.0110168457031,0.00399780273438 -0.0220031738281,0.00698852539062 -0.0320129394531,0.0119934082031 c -4.40200805664,1.77798461914 -7.51098632812,6.083984375 -7.5119934082,11.1229858398 c 0.0,0.00799560546875 0.00198364257812,0.0160217285156 0.0019836425781,0.0240173339844 c 0.00100708007812,0.475006103516 0.0380249023438,0.940002441406 0.0950012207032,1.39898681641 c 0.02001953125,0.175994873047 0.0490112304688,0.348999023438 0.0780029296875,0.523010253906 c 0.0469970703125,0.281982421875 0.105010986328,0.557983398438 0.171997070312,0.833984375 c 0.0480041503906,0.204010009766 0.093017578125,0.410003662109 0.152008056641,0.610015869141 c 0.110992431641,0.372009277344 0.238006591797,0.736999511719 0.382019042969,1.09298706055 c 0.0,0.0 0.0009765625,0.0 0.0009765625,0.0 c 1.00701904297,2.48400878906 2.81301879883,4.56100463867 5.11001586914,5.89501953125 c 0.0,0.0 2.01599121094,-3.48300170898 2.01599121094,-3.48300170898 c -2.03900146484,-1.18402099609 -3.5119934082,-3.22500610352 -3.89898681641,-5.63900756836 c 0.0,0.0 0.0009765625,-0.00100708007812 0.0009765625,-0.00100708007812 c -0.0220031738281,-0.130981445312 -0.0369873046875,-0.265991210938 -0.052978515625,-0.399993896484 c -0.0290222167969,-0.274993896484 -0.0570068359375,-0.552001953125 -0.0570068359375,-0.834991455078 c 0.0,0.0 0.0,-0.0190124511719 0.0,-0.0190124511719 c 0.0,-3.98999023438 2.92498779297,-7.28900146484 6.74398803711,-7.89199829102 c 0.0,0.0 0.0180053710938,0.0169982910156 0.0180053710938,0.0169982910156 c 0.404998779297,-0.0639953613281 0.81298828125,-0.125 1.23599243164,-0.125 c 0.0,0.0 0.00201416015625,0.0 0.00201416015624,0.0 c 0.0,0.0 0.00299072265625,0.0 0.00299072265626,0.0 c 0.423004150391,0.0 0.830017089844,0.0610046386719 1.23501586914,0.125 c 0.0,0.0 0.0169982910156,-0.0180053710938 0.0169982910156,-0.0180053710938 c 3.81997680664,0.60400390625 6.74499511719,3.90301513672 6.74499511719,7.89199829102 c 0.0,0.292999267578 -0.0280151367188,0.578002929688 -0.0589904785156,0.861999511719 c -0.0150146484375,0.132019042969 -0.0290222167969,0.264007568359 -0.051025390625,0.393005371094 c -0.385986328125,2.41500854492 -1.85897827148,4.45599365234 -3.89797973633,5.64001464844 c 0.0,0.0 2.01599121094,3.48300170898 2.01599121094,3.48300170898 c 2.29699707031,-1.33401489258 4.10299682617,-3.41101074219 5.11001586914,-5.89602661133 Z M 19.9300231934,2.95698547363 c 0.0059814453125,-0.0659790039062 0.0159912109375,-0.130981445312 0.02099609375,-0.196990966797 c 0.031982421875,-0.462005615234 0.0479736328125,-0.928009033203 0.0489807128906,-1.39700317383 c 0,0.0 0,-0.00997924804688 0,-0.00997924804687 c 0,0.0 0,-0.00100708007812 0,-0.00100708007813 c 0,-7.22500610352 -3.84399414062,-13.5360107422 -9.58599853516,-17.0500183105 c -1.06500244141,-0.652984619141 -2.19299316406,-1.20599365234 -3.37799072266,-1.65197753906 c -0.157989501953,-0.0599975585938 -0.317016601562,-0.118011474609 -0.476989746094,-0.174011230469 c -0.317016601562,-0.110992431641 -0.634002685547,-0.218994140625 -0.9580078125,-0.31298828125 c -0.470001220703,-0.139007568359 -0.944000244141,-0.264007568359 -1.4280090332,-0.368011474609 c -0.186004638672,-0.0390014648438 -0.376983642578,-0.0669860839844 -0.565002441406,-0.101013183594 c -0.414001464844,-0.0759887695312 -0.832000732422,-0.140991210938 -1.25500488281,-0.190979003906 c -0.184997558594,-0.0220031738281 -0.369995117188,-0.0429992675781 -0.556976318359,-0.0599975585937 c -0.592010498047,-0.0530090332031 -1.18801879883,-0.0899963378906 -1.79602050781,-0.0899963378907 c -0.605987548828,0.0 -1.20300292969,0.0369873046875 -1.79598999023,0.0899963378907 c -0.186004638672,0.0169982910156 -0.371002197266,0.0379943847656 -0.555999755859,0.0599975585937 c -0.423004150391,0.0499877929688 -0.842010498047,0.114990234375 -1.25601196289,0.190979003906 c -0.18798828125,0.0350036621094 -0.377990722656,0.06201171875 -0.563995361328,0.101013183594 c -0.483001708984,0.10400390625 -0.959991455078,0.22900390625 -1.42999267578,0.368011474609 c -0.321990966797,0.093994140625 -0.638000488281,0.201995849609 -0.953002929688,0.311981201172 c -0.162994384766,0.0570068359375 -0.324005126953,0.115997314453 -0.484985351562,0.177001953125 c -1.18099975586,0.445007324219 -2.30599975586,0.997009277344 -3.36801147461,1.64700317383 c -0.00201416015625,0.00100708007812 -0.00399780273438,0.00201416015625 -0.0060119628907,0.0029907226562 c -5.74099731445,3.51400756836 -9.58499145508,9.82501220703 -9.58599853516,17.0500183105 c 0,0.0 0,0.00100708007812 0,0.00100708007813 c 0,0.0059814453125 0.00100708007812,0.0130004882812 0.0010070800781,0.0199890136719 c 0.0,0.466003417969 0.0169982910156,0.928009033203 0.0490112304688,1.38598632812 c 0.0050048828125,0.0690002441406 0.0159912109375,0.136016845703 0.02099609375,0.206024169922 c 0.031982421875,0.401000976562 0.0719909667969,0.799987792969 0.127990722656,1.19400024414 c 0.00201416015625,0.0189819335938 0.00701904296875,0.0369873046875 0.010009765625,0.0569763183594 c 0.888000488281,6.17202758789 4.59799194336,11.4250183105 9.7799987793,14.4309997559 c 0.0,0.0 2.00198364258,-3.458984375 2.00198364258,-3.458984375 c -2.58599853516,-1.5 -4.708984375,-3.70401000977 -6.11697387695,-6.34399414063 c 0.0,0.0 0.0169982910156,-0.0180053710938 0.0169982910156,-0.0180053710938 c -0.890014648438,-1.67098999023 -1.50601196289,-3.5110168457 -1.76000976562,-5.46499633789 c -0.00698852539062,-0.0500183105469 -0.010009765625,-0.102020263672 -0.0159912109375,-0.152008056641 c -0.0330200195312,-0.273010253906 -0.0610046386719,-0.545989990234 -0.0800170898437,-0.821990966797 c -0.0220031738281,-0.343017578125 -0.0350036621094,-0.68701171875 -0.0350036621094,-1.03500366211 c 0,-6.53701782227 3.92599487305,-12.1480102539 9.54299926758,-14.6310119629 c 0.157012939453,-0.0700073242188 0.313995361328,-0.135986328125 0.472015380859,-0.199981689453 c 0.373992919922,-0.151000976562 0.751983642578,-0.294006347656 1.13900756836,-0.417022705078 c 0.108978271484,-0.0350036621094 0.221984863281,-0.0619812011719 0.332000732422,-0.0950012207031 c 0.349975585938,-0.102996826172 0.705993652344,-0.194976806641 1.06597900391,-0.273986816406 c 0.114013671875,-0.0249938964844 0.227996826172,-0.052001953125 0.342010498047,-0.0750122070313 c 0.440002441406,-0.0869750976562 0.885986328125,-0.154998779297 1.33700561523,-0.203979492188 c 0.10400390625,-0.0120239257812 0.209991455078,-0.02001953125 0.315002441406,-0.0299987792969 c 0.47998046875,-0.0429992675781 0.963989257812,-0.072998046875 1.45397949219,-0.072998046875 c 0.492004394531,0.0 0.975006103516,0.0299987792969 1.45401000977,0.072998046875 c 0.105987548828,0.00997924804688 0.212005615234,0.0179748535156 0.316986083984,0.0299987792969 c 0.450012207031,0.0489807128906 0.89501953125,0.117004394531 1.33502197266,0.203002929688 c 0.115997314453,0.0239868164062 0.22998046875,0.0509948730469 0.345001220703,0.0769958496094 c 0.358001708984,0.0780029296875 0.710998535156,0.169982910156 1.06097412109,0.272003173828 c 0.111022949219,0.0329895019531 0.226013183594,0.0609741210938 0.336029052734,0.0969848632813 c 0.385986328125,0.123016357422 0.761993408203,0.265014648438 1.13497924805,0.415008544922 c 0.160003662109,0.0650024414062 0.319000244141,0.131988525391 0.477020263672,0.201995849609 c 5.61599731445,2.48400878906 9.53997802734,8.09399414062 9.53997802734,14.6310119629 c 0,0.346984863281 -0.0130004882812,0.690979003906 -0.0350036621094,1.03399658203 c -0.0179748535156,0.274993896484 -0.0469970703125,0.548004150391 -0.0789794921875,0.819000244141 c -0.00601196289062,0.052001953125 -0.010009765625,0.10498046875 -0.0160217285156,0.154998779297 c -0.252990722656,1.95498657227 -0.871002197266,3.79400634766 -1.75997924805,5.46499633789 c 0.0,0.0 0.0169982910156,0.0180053710938 0.0169982910156,0.0180053710938 c -1.40802001953,2.63998413086 -3.53100585938,4.84399414062 -6.11700439453,6.34399414063 c 0.0,0.0 2.00198364258,3.458984375 2.00198364258,3.458984375 c 5.18402099609,-3.00698852539 8.89501953125,-8.26300048828 9.78100585938,-14.4379882813 c 0.00201416015625,-0.0169982910156 0.00601196289062,-0.0320129394531 0.0079956054688,-0.0490112304688 c 0.0570068359375,-0.39697265625 0.0970153808594,-0.798980712891 0.129028320312,-1.20300292969 Z" - android:fillColor="#FFFFFFFF" - android:fillAlpha="1" /> - </group> - </group> - </group> - </group> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_location.xml b/packages/SystemUI/res/drawable/ic_location.xml new file mode 100644 index 000000000000..50ba523fcd3e --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_location.xml @@ -0,0 +1,27 @@ +<!-- + ~ Copyright (C) 2014 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="17dp" + android:height="17dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13C19,5.13 15.87,2 12,2zM7,9c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,2.88 -2.88,7.19 -5,9.88C9.92,16.21 7,11.85 7,9z"/> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M12,9m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml deleted file mode 100644 index dd124b713a72..000000000000 --- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml +++ /dev/null @@ -1,32 +0,0 @@ -<!-- -Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24" - android:tint="?android:attr/colorControlNormal"> - - <path - android:fillColor="#FFFFFFFF" - android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L11,14.41V22h1l5.71-5.71L13.41,12L17.71,7.71z M13,5.83 l1.88,1.88L13,9.59V5.83z M14.88,16.29L13,18.17v-3.76L14.88,16.29z" /> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M 5 10.5 C 5.82842712475 10.5 6.5 11.1715728753 6.5 12 C 6.5 12.8284271247 5.82842712475 13.5 5 13.5 C 4.17157287525 13.5 3.5 12.8284271247 3.5 12 C 3.5 11.1715728753 4.17157287525 10.5 5 10.5 Z" /> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M 19 10.5 C 19.8284271247 10.5 20.5 11.1715728753 20.5 12 C 20.5 12.8284271247 19.8284271247 13.5 19 13.5 C 18.1715728753 13.5 17.5 12.8284271247 17.5 12 C 17.5 11.1715728753 18.1715728753 10.5 19 10.5 Z" /> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml index 220c63ccca6d..1c867064773c 100644 --- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml +++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml @@ -14,13 +14,11 @@ Copyright (C) 2017 The Android Open Source Project limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24" - android:tint="?android:attr/colorControlNormal"> - + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> <path - android:fillColor="#FFFFFFFF" - android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L11,14.41V22h1l5.71-5.71L13.41,12L17.71,7.71z M13,5.83 l1.88,1.88L13,9.59V5.83z M14.88,16.29L13,18.17v-3.76L14.88,16.29z" /> -</vector> + android:fillColor="@android:color/white" + android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41L11,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59L13,5.83zM14.88,16.29L13,18.17v-3.76l1.88,1.88z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_qs_branded_vpn.xml b/packages/SystemUI/res/drawable/ic_qs_branded_vpn.xml deleted file mode 100644 index b20e1582698c..000000000000 --- a/packages/SystemUI/res/drawable/ic_qs_branded_vpn.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 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="12.0dp" - android:height="12.0dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="#4DFFFFFF" - android:pathData="M12.09,9C11.11,7.5 9.43,6.5 7.5,6.5C4.46,6.5 2,8.96 2,12c0,3.04 2.46,5.5 5.5,5.5c1.93,0 3.61,-1 4.59,-2.5H14v3h6v-3h2V9H12.09zM20,13h-2v3h-2v-3h-5.16c-0.43,1.44 -1.76,2.5 -3.34,2.5C5.57,15.5 4,13.93 4,12c0,-1.93 1.57,-3.5 3.5,-3.5c1.58,0 2.9,1.06 3.34,2.5H20V13z"/> - <path - android:fillColor="#4DFFFFFF" - android:pathData="M7.5,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_on.xml b/packages/SystemUI/res/drawable/ic_qs_cast_on.xml deleted file mode 100644 index 3dda87cf7e8b..000000000000 --- a/packages/SystemUI/res/drawable/ic_qs_cast_on.xml +++ /dev/null @@ -1,25 +0,0 @@ -<!-- -Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="64dp" - android:height="64dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - - <path - android:fillColor="#FFFFFFFF" - android:pathData="M1,18v2c0,0.55 0.45,1 1,1h2c0,-1.66 -1.34,-3 -3,-3zM0.97,15.06c-0.01,0.51 0.35,0.93 0.85,1.02 2.08,0.36 3.74,2 4.1,4.08 0.09,0.48 0.5,0.84 0.99,0.84 0.61,0 1.09,-0.54 1,-1.14a6.996,6.996 0,0 0,-5.8 -5.78c-0.59,-0.09 -1.12,0.38 -1.14,0.98zM19,7L5,7v1.63c3.96,1.28 7.09,4.41 8.37,8.37L19,17L19,7zM0.97,11.03c-0.01,0.52 0.37,0.96 0.88,1.01 4.26,0.43 7.68,3.82 8.1,8.08 0.05,0.5 0.48,0.88 0.99,0.88 0.59,0 1.06,-0.51 1,-1.1 -0.52,-5.21 -4.66,-9.34 -9.87,-9.85 -0.57,-0.05 -1.08,0.4 -1.1,0.98zM21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2z" /> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_volume_alarm.xml b/packages/SystemUI/res/drawable/ic_volume_alarm.xml index 996e488e8c81..771b466dc6d1 100644 --- a/packages/SystemUI/res/drawable/ic_volume_alarm.xml +++ b/packages/SystemUI/res/drawable/ic_volume_alarm.xml @@ -14,14 +14,12 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:height="24.0dp" - android:viewportHeight="24.0" - android:viewportWidth="24.0" - android:width="24.0dp" - android:tint="?android:attr/colorControlNormal"> - + android:height="24dp" + android:width="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> <path android:fillColor="#FFFFFF" - android:pathData="M2.7,6.5c-0.4,-0.4 -0.3,-1 0.1,-1.4l3,-2.6c0.4,-0.4 1,-0.3 1.4,0.1C7.6,3 7.5,3.7 7.1,4l-3,2.6C3.6,7 3,6.9 2.7,6.5zM21.3,5.1l-3.1,-2.6c-0.4,-0.4 -0.99,-0.31 -1.4,0.1c-0.4,0.4 -0.3,1 0.1,1.4L20,6.6c0.41,0.37 1,0.3 1.4,-0.1C21.73,6.12 21.7,5.4 21.3,5.1zM21,13c0,5 -4,9 -9,9s-9,-4 -9,-9s4,-9 9,-9S21,8 21,13zM19.1,13c0,-3.9 -3.2,-7.1 -7.1,-7.1S4.9,9.1 4.9,13s3.2,7.1 7.1,7.1S19.1,16.9 19.1,13zM11.75,8C11.34,8 11,8.34 11,8.75V14l4.14,2.48c0.34,0.21 0.77,0.1 0.98,-0.24s0.09,-0.79 -0.25,-0.99l-3.37,-2v-4.5C12.5,8.34 12.16,8 11.75,8z"/> + android:pathData="M13,8h-2v5.41l3.79,3.8 1.42,-1.42 -3.21,-3.2zM12,4c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9 -4.03,-9 -9,-9zM12,20c-3.86,0 -7,-3.14 -7,-7s3.14,-7 7,-7 7,3.14 7,7 -3.14,7 -7,7zM16.056,3.346l1.282,-1.535 4.607,3.85 -1.28,1.54zM2.056,5.654L6.663,1.81l1.28,1.536L3.338,7.19z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_volume_alarm_mute.xml b/packages/SystemUI/res/drawable/ic_volume_alarm_mute.xml index 02fb1e702438..18e273669854 100644 --- a/packages/SystemUI/res/drawable/ic_volume_alarm_mute.xml +++ b/packages/SystemUI/res/drawable/ic_volume_alarm_mute.xml @@ -22,6 +22,6 @@ <path android:fillColor="#FFFFFF" - android:pathData="M21.35,6.49c-0.35,0.42 -0.98,0.47 -1.4,0.12l-3.07,-2.57a1,1 0,1 1,1.29 -1.53l3.07,2.57c0.42,0.35 0.47,0.98 0.11,1.41zM20.72,20.09a0.9,0.9 0,0 1,0 1.27,0.9 0.9,0 0,1 -1.27,0l-1.57,-1.57A8.875,8.875 0,0 1,12 22c-4.98,0 -9,-4.03 -9,-9 0,-2.25 0.83,-4.31 2.2,-5.89l-0.8,-0.8 -0.41,0.35a1,1 0,0 1,-1.35 -0.06,1 1,0 0,1 0.07,-1.47l0.27,-0.23 -0.7,-0.7a0.9,0.9 0,0 1,0 -1.27c0.35,-0.35 0.93,-0.35 1.28,0l17.16,17.16zM16.54,18.45L6.55,8.46A7.041,7.041 0,0 0,4.9 13c0,3.91 3.19,7.1 7.1,7.1 1.73,0 3.31,-0.62 4.54,-1.65zM7.17,3.98A0.997,0.997 0,1 0,5.9 2.44l-0.16,0.13 1.42,1.42 0.01,-0.01zM12,4c-1.41,0 -2.73,0.33 -3.92,0.91l1.45,1.45c0.77,-0.29 1.6,-0.46 2.47,-0.46 3.91,0 7.1,3.18 7.1,7.1 0,0.87 -0.17,1.7 -0.45,2.47l1.44,1.44c0.58,-1.18 0.91,-2.5 0.91,-3.91a9,9 0,0 0,-9 -9z"/> + android:pathData="M16.056,3.346l1.282,-1.535 4.607,3.85 -1.28,1.54zM9.35,6.52C10.17,6.19 11.06,6 12,6c3.86,0 7,3.14 7,7 0,0.94 -0.19,1.83 -0.52,2.65l1.5,1.5C20.63,15.91 21,14.5 21,13c0,-4.97 -4.03,-9 -9,-9 -1.5,0 -2.91,0.37 -4.15,1.02l1.5,1.5zM17.42,17.42L7.58,7.58 6.16,6.16l-0.72,-0.72 -1.42,-1.42 -1.21,-1.21 -1.42,1.41L2.48,5.3l-0.42,0.35 1.28,1.54 0.56,-0.47 0.9,0.9C3.67,9.12 3,10.98 3,13c0,4.97 4.03,9 9,9 2.02,0 3.88,-0.67 5.38,-1.79l2.4,2.4 1.41,-1.41 -2.35,-2.35 -1.42,-1.43zM12,20c-3.86,0 -7,-3.14 -7,-7 0,-1.46 0.46,-2.82 1.23,-3.94l9.71,9.71C14.82,19.54 13.46,20 12,20zM7.94,3.35L6.66,1.81l-1.1,0.92 1.42,1.42z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_volume_ringer_vibrate.xml b/packages/SystemUI/res/drawable/ic_volume_ringer_vibrate.xml index aa13f1e2a9e7..314e06cc93b0 100644 --- a/packages/SystemUI/res/drawable/ic_volume_ringer_vibrate.xml +++ b/packages/SystemUI/res/drawable/ic_volume_ringer_vibrate.xml @@ -14,11 +14,10 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:height="24dp" + android:height="19dp" + android:width="19dp" android:viewportHeight="24.0" - android:viewportWidth="24.0" - android:width="24dp" - android:tint="?android:attr/colorControlNormal" > + android:viewportWidth="24.0"> <path android:fillColor="#FFFFFFFF" diff --git a/packages/SystemUI/res/drawable/stat_sys_airplane_mode.xml b/packages/SystemUI/res/drawable/stat_sys_airplane_mode.xml index 2655bcda3c57..0f8c57183950 100644 --- a/packages/SystemUI/res/drawable/stat_sys_airplane_mode.xml +++ b/packages/SystemUI/res/drawable/stat_sys_airplane_mode.xml @@ -16,16 +16,5 @@ ** limitations under the License. */ --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="17.25dp" - android:height="18dp" - android:viewportWidth="19.65" - android:viewportHeight="20.5"> - <group - android:translateX="1.3" - android:translateY="1.7"> - <path - android:pathData="M16.01,9.87l-6.24,-3.9v-4.7C9.77,0.57 9.21,0 8.5,0S7.23,0.57 7.23,1.28v4.7L0.99,9.88c-0.37,0.23 -0.6,0.64 -0.6,1.08v0.41c0,0.29 0.29,0.5 0.55,0.41l6.27,-1.97v4.7l-1.37,1.02c-0.21,0.16 -0.34,0.41 -0.34,0.68v0.57c0,0.15 0.12,0.23 0.27,0.2 1.67,-0.47 1.12,-0.31 2.73,-0.78 1.03,0.3 1.7,0.49 2.72,0.78 0.15,0.03 0.27,-0.06 0.27,-0.2v-0.57c0,-0.27 -0.13,-0.52 -0.34,-0.68l-1.37,-1.02v-4.7l6.27,1.97c0.28,0.09 0.55,-0.12 0.55,-0.41v-0.41c0.01,-0.45 -0.23,-0.87 -0.59,-1.09z" - android:fillColor="#FFF"/> - </group> -</vector> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@*android:drawable/ic_qs_airplane" /> diff --git a/packages/SystemUI/res/drawable/stat_sys_alarm.xml b/packages/SystemUI/res/drawable/stat_sys_alarm.xml index 0e9319c63eaa..b9bebec5baba 100644 --- a/packages/SystemUI/res/drawable/stat_sys_alarm.xml +++ b/packages/SystemUI/res/drawable/stat_sys_alarm.xml @@ -1,35 +1,21 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -** -** Copyright 2017, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. + * + * Copyright 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ --> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:insetLeft="2.5dp" - android:insetRight="2.5dp"> - <vector - android:width="17dp" - android:height="17dp" - android:viewportWidth="19.3" - android:viewportHeight="19.3"> - <group - android:translateX="1.2" - android:translateY="1.2"> - <path - android:pathData="M0.97,3.97c-0.32,-0.33 -0.24,-0.81 0.08,-1.13L3.47,0.74C3.79,0.42 4.28,0.5 4.6,0.82s0.24,0.89 -0.08,1.13L2.1,4.05c-0.4,0.32 -0.89,0.24 -1.13,-0.08zM15.97,2.84l-2.5,-2.1c-0.32,-0.32 -0.8,-0.25 -1.13,0.08 -0.32,0.32 -0.24,0.81 0.08,1.13l2.5,2.1c0.33,0.3 0.81,0.24 1.13,-0.08 0.27,-0.31 0.25,-0.89 -0.08,-1.13zM15.73,9.21c0,4.03 -3.23,7.26 -7.26,7.26s-7.26,-3.23 -7.26,-7.26 3.23,-7.26 7.26,-7.26 7.26,3.23 7.26,7.26zM14.2,9.21c0,-3.15 -2.58,-5.73 -5.73,-5.73S2.74,6.06 2.74,9.21s2.58,5.73 5.73,5.73 5.73,-2.59 5.73,-5.73zM8.27,5.18c-0.33,0 -0.6,0.27 -0.6,0.6v4.23l3.34,2c0.27,0.17 0.62,0.08 0.79,-0.19s0.07,-0.64 -0.2,-0.8L8.87,9.41L8.87,5.78c0,-0.33 -0.27,-0.6 -0.6,-0.6z" - android:fillColor="#FFF"/> - </group> - </vector> -</inset> + android:insetRight="2.5dp" + android:drawable="@drawable/ic_alarm" /> diff --git a/packages/SystemUI/res/drawable/stat_sys_alarm_dim.xml b/packages/SystemUI/res/drawable/stat_sys_alarm_dim.xml index c8e2ac19a4e2..cf1119d80fa3 100644 --- a/packages/SystemUI/res/drawable/stat_sys_alarm_dim.xml +++ b/packages/SystemUI/res/drawable/stat_sys_alarm_dim.xml @@ -15,18 +15,5 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:insetLeft="2.5dp" - android:insetRight="2.5dp"> - - <vector - android:width="17dp" - android:height="17dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - - <path - android:fillColor="#4dffffff" - android:pathData="M22.0,5.7l-4.6,-3.9l-1.3,1.5l4.6,3.9L22.0,5.7zM7.9,3.4L6.6,1.9L2.0,5.7l1.3,1.5L7.9,3.4zM12.5,8.0L11.0,8.0l0.0,6.0l4.7,2.9l0.8,-1.2l-4.0,-2.4L12.5,8.0zM12.0,4.0c-5.0,0.0 -9.0,4.0 -9.0,9.0c0.0,5.0 4.0,9.0 9.0,9.0s9.0,-4.0 9.0,-9.0C21.0,8.0 17.0,4.0 12.0,4.0zM12.0,20.0c-3.9,0.0 -7.0,-3.1 -7.0,-7.0c0.0,-3.9 3.1,-7.0 7.0,-7.0c3.9,0.0 7.0,3.1 7.0,7.0C19.0,16.9 15.9,20.0 12.0,20.0z"/> - - </vector> - -</inset>
\ No newline at end of file + android:insetRight="2.5dp" + android:drawable="@drawable/ic_alarm_dim" /> diff --git a/packages/SystemUI/res/drawable/stat_sys_branded_vpn.xml b/packages/SystemUI/res/drawable/stat_sys_branded_vpn.xml index bfae8575cd14..5913cdf4a073 100644 --- a/packages/SystemUI/res/drawable/stat_sys_branded_vpn.xml +++ b/packages/SystemUI/res/drawable/stat_sys_branded_vpn.xml @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> +<!-- This icon is statically overlayed - do not remove.--> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="17.0dp" android:height="17.0dp" diff --git a/packages/SystemUI/res/drawable/stat_sys_camera.xml b/packages/SystemUI/res/drawable/stat_sys_camera.xml index eb3e9632dd35..c914262571ea 100644 --- a/packages/SystemUI/res/drawable/stat_sys_camera.xml +++ b/packages/SystemUI/res/drawable/stat_sys_camera.xml @@ -18,14 +18,5 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:insetLeft="3dp" - android:insetRight="3dp"> - <vector - android:width="17dp" - android:height="17dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="#FFF" - android:pathData="M20,5h-3.17L15,3H9L7.17,5H4C2.9,5 2,5.9 2,7v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V7C22,5.9 21.1,5 20,5zM20,19H4V7h16V19zM12,9c-2.21,0 -4,1.79 -4,4c0,2.21 1.79,4 4,4s4,-1.79 4,-4C16,10.79 14.21,9 12,9z"/> - </vector> -</inset> + android:insetRight="3dp" + android:drawable="@drawable/ic_camera" />
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/stat_sys_cast.xml b/packages/SystemUI/res/drawable/stat_sys_cast.xml index 5228085b5dc5..de7ec9d628bb 100644 --- a/packages/SystemUI/res/drawable/stat_sys_cast.xml +++ b/packages/SystemUI/res/drawable/stat_sys_cast.xml @@ -15,14 +15,5 @@ Copyright (C) 2017 The Android Open Source Project --> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:insetLeft="2.5dp" - android:insetRight="2.5dp"> - <vector android:width="17dp" - android:height="17dp" - android:viewportWidth="17.0" - android:viewportHeight="17.0"> - - <path - android:fillColor="#FFFFFFFF" - android:pathData="M0.71,12.75v1.42c0,0.39 0.32,0.71 0.71,0.71h1.42C2.83,13.7 1.88,12.75 0.71,12.75zM0.69,10.67c-0.01,0.36 0.25,0.66 0.6,0.72c1.47,0.26 2.65,1.42 2.9,2.89c0.06,0.34 0.35,0.6 0.7,0.6c0.43,0 0.77,-0.38 0.71,-0.81c-0.34,-2.11 -2,-3.76 -4.11,-4.09C1.08,9.91 0.7,10.24 0.69,10.67zM13.46,4.96H3.54v1.15c2.81,0.91 5.02,3.12 5.93,5.93h3.99V4.96zM0.69,7.81C0.68,8.18 0.95,8.49 1.31,8.53c3.02,0.3 5.44,2.71 5.74,5.72c0.04,0.35 0.34,0.62 0.7,0.62c0.42,0 0.75,-0.36 0.71,-0.78c-0.37,-3.69 -3.3,-6.62 -6.99,-6.98C1.06,7.08 0.7,7.4 0.69,7.81zM14.88,2.12H2.12c-0.78,0 -1.42,0.64 -1.42,1.42v2.12h1.42V3.54h12.75v9.92H9.92v1.42h4.96c0.78,0 1.42,-0.64 1.42,-1.42V3.54C16.29,2.76 15.65,2.12 14.88,2.12z" /> - </vector> -</inset> + android:insetRight="2.5dp" + android:drawable="@drawable/ic_cast_connected" />
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/stat_sys_data_bluetooth.xml b/packages/SystemUI/res/drawable/stat_sys_data_bluetooth.xml deleted file mode 100644 index 4dc0e4baa397..000000000000 --- a/packages/SystemUI/res/drawable/stat_sys_data_bluetooth.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** -** Copyright 2017, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="17dp" - android:height="17dp" - android:viewportWidth="17.0" - android:viewportHeight="17.0"> - <group - android:translateY="0.5" - android:translateX="0.5" > - <path - android:pathData="M8.84,8l2.62,-2.62c0.29,-0.29 0.29,-0.75 0,-1.04L8.33,1.22L8.31,1.2c-0.3,-0.28 -0.76,-0.26 -1.03,0.04c-0.13,0.13 -0.2,0.31 -0.2,0.5v4.51L4.24,3.4c-0.29,-0.29 -0.74,-0.29 -1.03,0s-0.29,0.74 0,1.03L6.78,8l-3.56,3.56c-0.29,0.29 -0.29,0.74 0,1.03s0.74,0.29 1.03,0l2.83,-2.83v4.51c0,0.4 0.33,0.73 0.73,0.73c0.18,0 0.36,-0.07 0.5,-0.2l0.03,-0.03l3.12,-3.12c0.29,-0.29 0.29,-0.75 0,-1.04L8.84,8zM8.47,6.37V3.36l1.5,1.5L8.47,6.37zM8.47,12.63V9.62l1.5,1.5L8.47,12.63z" - android:fillColor="#FFFFFF"/> - </group> -</vector> diff --git a/packages/SystemUI/res/drawable/stat_sys_data_bluetooth_connected.xml b/packages/SystemUI/res/drawable/stat_sys_data_bluetooth_connected.xml index c8a444064fd6..8d9e561df793 100644 --- a/packages/SystemUI/res/drawable/stat_sys_data_bluetooth_connected.xml +++ b/packages/SystemUI/res/drawable/stat_sys_data_bluetooth_connected.xml @@ -1,31 +1,17 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -** -** Copyright 2017, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ +Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="17dp" - android:height="17dp" - android:viewportWidth="18.0" - android:viewportHeight="18.0"> - <group - android:translateY="0.5" - android:translateX="0.5" > - <path - android:pathData="M9.57,8.5l2.79,-2.78c0.3,-0.3 0.3,-0.8 0,-1.1L9.04,1.29L9.02,1.27C8.7,0.98 8.21,1 7.91,1.31C7.78,1.45 7.71,1.64 7.71,1.84v4.79L4.69,3.61c-0.3,-0.3 -0.79,-0.3 -1.09,0s-0.3,0.79 0,1.09L7.39,8.5L3.6,12.29c-0.3,0.3 -0.3,0.79 0,1.09s0.79,0.3 1.09,0l3.01,-3.01v4.8c0,0.42 0.35,0.77 0.77,0.77c0.19,0 0.39,-0.07 0.53,-0.21l0.04,-0.04l3.32,-3.32c0.3,-0.3 0.3,-0.8 0,-1.1L9.57,8.5zM9.19,6.77v-3.2l1.6,1.6L9.19,6.77zM9.19,13.42v-3.2l1.6,1.6L9.19,13.42zM4.03,9.29c-0.44,0.44 -1.15,0.44 -1.58,0C2.02,8.86 2.02,8.16 2.45,7.72l0.01,-0.01C2.89,7.27 3.59,7.27 4.02,7.7l0.01,0.01C4.47,8.15 4.47,8.85 4.03,9.29zM14.44,7.71c0.44,0.44 0.44,1.15 0,1.58c-0.44,0.44 -1.15,0.44 -1.58,0c-0.44,-0.43 -0.44,-1.13 -0.01,-1.57l0.01,-0.01C13.3,7.28 14,7.27 14.43,7.7C14.44,7.7 14.44,7.71 14.44,7.71z" - android:fillColor="#FFFFFF"/> - </group> -</vector> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/ic_bluetooth_connected" />
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/stat_sys_data_saver.xml b/packages/SystemUI/res/drawable/stat_sys_data_saver.xml index fed7caec377a..022350173a35 100644 --- a/packages/SystemUI/res/drawable/stat_sys_data_saver.xml +++ b/packages/SystemUI/res/drawable/stat_sys_data_saver.xml @@ -18,26 +18,5 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:insetLeft="2.5dp" - android:insetRight="2.5dp" > - <vector - android:width="18dp" - android:height="18dp" - android:viewportWidth="19.0" - android:viewportHeight="19.0"> - <group - android:translateX="1.0" - android:translateY="1.0"> - <path - android:pathData="M11.5,8.5c0,0.41 -0.34,0.74 -0.74,0.74L9.24,9.24v1.5c0,0.41 -0.34,0.74 -0.74,0.74s-0.74,-0.34 -0.74,-0.74v-1.5h-1.5c-0.41,0 -0.74,-0.34 -0.74,-0.74s0.34,-0.74 0.74,-0.74h1.5v-1.5c0,-0.41 0.34,-0.74 0.74,-0.74s0.74,0.34 0.74,0.74v1.5h1.5c0.42,-0.01 0.76,0.33 0.76,0.74z" - android:fillColor="#FFF"/> - <path - android:pathData="M13.23,12.05l0.99,0.57c0.19,0.12 0.25,0.38 0.12,0.55A7.452,7.452 0,0 1,7.5 15.9c-3.29,-0.44 -5.96,-3.08 -6.41,-6.38 -0.57,-4.17 2.33,-7.8 6.23,-8.42 0.23,-0.04 0.44,0.15 0.44,0.37v1.15c0,0.18 -0.14,0.33 -0.31,0.37 -2.7,0.52 -4.71,2.96 -4.55,5.83 0.16,2.86 2.57,5.21 5.43,5.29 1.77,0.06 3.38,-0.72 4.44,-1.97 0.11,-0.14 0.31,-0.18 0.46,-0.09z" - android:fillColor="#FFF" /> - <path - android:pathData="M14.11,8.5c0,0.62 -0.11,1.22 -0.29,1.78 -0.06,0.17 0.01,0.35 0.16,0.45l1,0.57c0.19,0.12 0.46,0.03 0.54,-0.18 0.3,-0.82 0.47,-1.7 0.47,-2.62 0,-3.73 -2.73,-6.82 -6.31,-7.39a0.377,0.377 0,0 0,-0.44 0.37v1.15c0,0.18 0.14,0.33 0.31,0.36 2.59,0.51 4.56,2.78 4.56,5.51z" - android:fillAlpha="0.3" - android:fillColor="#FFF" /> - - </group> - </vector> -</inset> + android:insetRight="2.5dp" + android:drawable="@drawable/ic_data_saver" />
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/stat_sys_dnd.xml b/packages/SystemUI/res/drawable/stat_sys_dnd.xml index 68a06d013bf7..aa352b42cf04 100644 --- a/packages/SystemUI/res/drawable/stat_sys_dnd.xml +++ b/packages/SystemUI/res/drawable/stat_sys_dnd.xml @@ -18,17 +18,5 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:insetLeft="2.5dp" - android:insetRight="2.5dp" > - <vector - android:width="17dp" - android:height="17dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10c5.52,0 10,-4.48 10,-10C22,6.48 17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8c0,-4.41 3.59,-8 8,-8c4.41,0 8,3.59 8,8C20,16.41 16.41,20 12,20z"/> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M7,11h10v2h-10z"/> - </vector> -</inset> + android:insetRight="2.5dp" + android:drawable="@*android:drawable/ic_qs_dnd" />
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/stat_sys_zen_important.xml b/packages/SystemUI/res/drawable/stat_sys_headset.xml index 1262617589d5..361c6659c5e4 100644 --- a/packages/SystemUI/res/drawable/stat_sys_zen_important.xml +++ b/packages/SystemUI/res/drawable/stat_sys_headset.xml @@ -1,7 +1,7 @@ <!-- -Copyright (C) 2014 The Android Open Source Project + Copyright (C) 2016 The Android Open Source Project - Licensed under the Apache License, Version 2.0 (the "License"); + 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 @@ -15,14 +15,5 @@ Copyright (C) 2014 The Android Open Source Project --> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:insetLeft="2.5dp" - android:insetRight="2.5dp"> - <vector android:width="18dp" - android:height="18dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - - <path - android:fillColor="#FFFFFFFF" - android:pathData="M12.0,17.273l6.1800003,3.7269993 -1.6350002,-7.0290003 5.455,-4.7269993 -7.191,-0.6170006 -2.809,-6.627 -2.809,6.627 -7.191,0.6170006 5.455,4.7269993 -1.6349998,7.0290003z"/> - </vector> -</inset> + android:insetRight="2.5dp" + android:drawable="@drawable/ic_headset" /> diff --git a/packages/SystemUI/res/drawable/stat_sys_headset_mic.xml b/packages/SystemUI/res/drawable/stat_sys_headset_mic.xml new file mode 100644 index 000000000000..b424caa6e12c --- /dev/null +++ b/packages/SystemUI/res/drawable/stat_sys_headset_mic.xml @@ -0,0 +1,19 @@ +<!-- + Copyright (C) 2016 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. +--> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:insetLeft="2.5dp" + android:insetRight="2.5dp" + android:drawable="@drawable/ic_headset_mic" /> diff --git a/packages/SystemUI/res/drawable/stat_sys_hotspot.xml b/packages/SystemUI/res/drawable/stat_sys_hotspot.xml index 4f52777339c5..361b00ae0598 100644 --- a/packages/SystemUI/res/drawable/stat_sys_hotspot.xml +++ b/packages/SystemUI/res/drawable/stat_sys_hotspot.xml @@ -15,18 +15,5 @@ Copyright (C) 2017 The Android Open Source Project --> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:insetLeft="2.5dp" - android:insetRight="2.5dp"> - <vector - android:width="18.0dp" - android:height="18.0dp" - android:viewportWidth="18.0" - android:viewportHeight="18.0"> - <group - android:translateX="0.5" - android:translateY="0.5" > - <path - android:pathData="M8.5,7.79c-0.78,0 -1.42,0.64 -1.42,1.42c0,0.78 0.64,1.42 1.42,1.42s1.42,-0.64 1.42,-1.42C9.92,8.43 9.28,7.79 8.5,7.79zM12.75,9.21c0,-2.35 -1.9,-4.25 -4.25,-4.25c-0.18,0 -0.35,0.01 -0.53,0.03C6.11,5.22 4.58,6.7 4.3,8.55c-0.23,1.52 0.35,2.91 1.36,3.82C6,12.67 6.54,12.6 6.76,12.2c0.17,-0.3 0.1,-0.67 -0.16,-0.89c-0.78,-0.7 -1.12,-1.77 -0.86,-2.79C5.99,7.5 6.78,6.71 7.8,6.46C9.32,6.08 10.86,7 11.25,8.52c0.06,0.23 0.09,0.46 0.09,0.69c0,0.84 -0.36,1.58 -0.94,2.1c-0.25,0.23 -0.33,0.6 -0.16,0.9c0.22,0.38 0.74,0.49 1.06,0.2C12.22,11.6 12.75,10.43 12.75,9.21zM7.63,1.85C4.19,2.24 1.42,5.07 1.1,8.52c-0.26,2.61 0.88,5.15 2.99,6.7c0.36,0.26 0.86,0.15 1.09,-0.23c0.19,-0.32 0.1,-0.74 -0.19,-0.96c-1.7,-1.26 -2.71,-3.38 -2.35,-5.73c0.4,-2.6 2.57,-4.68 5.19,-4.97c3.59,-0.41 6.63,2.4 6.63,5.91c0,1.97 -0.96,3.7 -2.43,4.79c-0.3,0.22 -0.38,0.63 -0.19,0.96c0.22,0.39 0.73,0.49 1.09,0.23c1.9,-1.4 3.03,-3.62 3.03,-5.98C15.94,4.84 12.12,1.34 7.63,1.85z" - android:fillColor="#FFFFFFFF"/> - </group> - </vector> -</inset> + android:insetRight="2.5dp" + android:drawable="@drawable/ic_hotspot" /> diff --git a/packages/SystemUI/res/drawable/stat_sys_location.xml b/packages/SystemUI/res/drawable/stat_sys_location.xml index bdb0b0c2c29f..7a5aeb9ae558 100644 --- a/packages/SystemUI/res/drawable/stat_sys_location.xml +++ b/packages/SystemUI/res/drawable/stat_sys_location.xml @@ -14,18 +14,6 @@ ~ limitations under the License --> <inset xmlns:android="http://schemas.android.com/apk/res/android" - android:insetLeft="1.5dp" - android:insetRight="1.5dp"> - <vector - android:width="17dp" - android:height="17dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13C19,5.13 15.87,2 12,2zM7,9c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,2.88 -2.88,7.19 -5,9.88C9.92,16.21 7,11.85 7,9z"/> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M12,9m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"/> - </vector> -</inset>
\ No newline at end of file + android:insetLeft="2.5dp" + android:insetRight="2.5dp" + android:drawable="@drawable/ic_location" /> diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_vibrate.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_vibrate.xml index 0b72f75ff97d..21a4c1703d31 100644 --- a/packages/SystemUI/res/drawable/stat_sys_ringer_vibrate.xml +++ b/packages/SystemUI/res/drawable/stat_sys_ringer_vibrate.xml @@ -1,36 +1,19 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -** -** Copyright 2017, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. --> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:insetLeft="2.5dp" - android:insetRight="2.5dp"> - <vector - android:width="19dp" - android:height="19dp" - android:viewportWidth="23.0" - android:viewportHeight="23.0"> - <group - android:translateX="-0.5" - android:translateY="-0.5"> - <path - android:fillColor="#F00" - android:pathData="M1,9h2v6H1V9zM4,17h2V7H4V17zM21,9v6h2V9H21zM18,17h2V7h-2V17zM17,5.5v13c0,0.83 -0.67,1.5 -1.5,1.5h-7C7.67,20 7,19.33 7,18.5v-13C7,4.67 7.67,4 8.5,4h7C16.33,4 17,4.67 17,5.5zM15,6H9v12h6V6z"/> - - </group> - </vector> -</inset> + android:insetRight="2.5dp" + android:drawable="@drawable/ic_volume_ringer_vibrate" />
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/stat_sys_zen_none.xml b/packages/SystemUI/res/drawable/stat_sys_zen_none.xml deleted file mode 100644 index 92914a8dd988..000000000000 --- a/packages/SystemUI/res/drawable/stat_sys_zen_none.xml +++ /dev/null @@ -1,28 +0,0 @@ -<!-- -Copyright (C) 2014 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. ---> -<inset xmlns:android="http://schemas.android.com/apk/res/android" - android:insetLeft="2.5dp" - android:insetRight="2.5dp"> - <vector android:width="17dp" - android:height="17dp" - android:viewportWidth="48.0" - android:viewportHeight="48.0"> - - <path - android:fillColor="#FFFFFFFF" - android:pathData="M24.0,4.0C13.0,4.0 4.0,13.0 4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0c11.0,0.0 20.0,-9.0 20.0,-20.0C44.0,13.0 35.0,4.0 24.0,4.0zM24.0,40.0c-8.8,0.0 -16.0,-7.2 -16.0,-16.0c0.0,-3.7 1.3,-7.1 3.4,-9.8l22.4,22.4C31.1,38.7 27.7,40.0 24.0,40.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8z"/> - </vector> -</inset> diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml index 9acfbafa3c0b..c420117073c5 100644 --- a/packages/SystemUI/res/layout-land/volume_dialog.xml +++ b/packages/SystemUI/res/layout-land/volume_dialog.xml @@ -54,6 +54,8 @@ 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" /> diff --git a/packages/SystemUI/res/layout/biometric_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml index 8f7a45f02c46..c452855cc9e9 100644 --- a/packages/SystemUI/res/layout/biometric_dialog.xml +++ b/packages/SystemUI/res/layout/biometric_dialog.xml @@ -76,10 +76,6 @@ android:layout_marginTop="24dp" android:gravity="@integer/biometric_dialog_text_gravity" android:textSize="20sp" - android:maxLines="1" - android:singleLine="true" - android:ellipsize="marquee" - android:marqueeRepeatLimit="marquee_forever" android:textColor="?android:attr/textColorPrimary"/> <TextView @@ -91,10 +87,6 @@ android:layout_marginEnd="24dp" android:gravity="@integer/biometric_dialog_text_gravity" android:textSize="16sp" - android:maxLines="1" - android:singleLine="true" - android:ellipsize="marquee" - android:marqueeRepeatLimit="marquee_forever" android:textColor="?android:attr/textColorPrimary"/> <TextView @@ -106,7 +98,6 @@ android:gravity="@integer/biometric_dialog_text_gravity" android:paddingTop="8dp" android:textSize="16sp" - android:maxLines="4" android:textColor="?android:attr/textColorPrimary"/> <ImageView diff --git a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml index d49aff9abc01..3786812db827 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml @@ -39,7 +39,7 @@ android:layout_height="wrap_content" android:paddingEnd="12dp" android:paddingBottom="4dp" - android:textColor="@color/ksh_keyword_color" + android:textColor="?android:attr/textColorPrimary" android:textSize="16sp" android:maxLines="5" android:singleLine="false" diff --git a/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml b/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml deleted file mode 100644 index 665fc3fbf3da..000000000000 --- a/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml +++ /dev/null @@ -1,61 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2018 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. ---> - -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:fillViewport ="true" - android:orientation="vertical"> - - <LinearLayout - android:id="@+id/dialog_container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" > - <TextView - android:id="@+id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center" - android:textDirection="locale" - android:textAppearance="@style/TextAppearance.AppOpsDialog.Title" - android:textColor="@*android:color/text_color_primary" - android:layout_marginStart="@dimen/ongoing_appops_dialog_title_margin_sides" - android:layout_marginEnd="@dimen/ongoing_appops_dialog_title_margin_sides" - android:layout_marginBottom="@dimen/ongoing_appops_dialog_title_margin_top_bottom" - android:layout_marginTop="@dimen/ongoing_appops_dialog_title_margin_top_bottom" - /> - - <LinearLayout - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/ongoing_appops_dialog_items_bottom_margin" > - - <LinearLayout - android:id="@+id/items_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:gravity="start" - /> - </LinearLayout> - - </LinearLayout> - -</ScrollView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml b/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml deleted file mode 100644 index c8e0845a9144..000000000000 --- a/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml +++ /dev/null @@ -1,61 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2018 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. ---> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="@dimen/ongoing_appops_dialog_line_height" - android:layout_marginStart="@dimen/ongoing_appops_dialog_text_padding" - android:layout_marginEnd="@dimen/ongoing_appops_dialog_text_padding" - android:fillViewport="true" - android:orientation="horizontal" - android:focusable="true" - android:layout_gravity="center_vertical"> - - <FrameLayout - android:layout_height="@dimen/ongoing_appops_dialog_app_icon_size" - android:layout_width="@dimen/ongoing_appops_dialog_app_icon_size" - android:layout_gravity="start|center_vertical"> - - <ImageView - android:id="@+id/app_icon" - android:layout_height="@dimen/ongoing_appops_dialog_app_icon_size" - android:layout_width="@dimen/ongoing_appops_dialog_app_icon_size" - android:layout_gravity="center" - /> - </FrameLayout> - - <TextView - android:id="@+id/app_name" - android:layout_height="match_parent" - android:layout_width="0dp" - android:layout_weight="1" - android:gravity="start|center_vertical" - android:textDirection="locale" - android:textAppearance="@style/TextAppearance.AppOpsDialog.Item" - android:textColor="@*android:color/text_color_primary" - android:layout_marginStart="@dimen/ongoing_appops_dialog_text_padding" - /> - - <LinearLayout - android:id="@+id/icons" - android:layout_height="match_parent" - android:layout_width="wrap_content" - android:gravity="end" - android:layout_gravity="end|center_vertical" - android:visibility="gone" - /> -</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index d1c80c43b3e9..a90b1eb471ff 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -56,6 +56,8 @@ 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" /> diff --git a/packages/SystemUI/res/layout/volume_dnd_icon.xml b/packages/SystemUI/res/layout/volume_dnd_icon.xml index 037d143fa69e..10c1472ae993 100644 --- a/packages/SystemUI/res/layout/volume_dnd_icon.xml +++ b/packages/SystemUI/res/layout/volume_dnd_icon.xml @@ -24,6 +24,6 @@ android:layout_width="14dp" android:layout_height="14dp" android:layout_gravity="right|top" - android:src="@drawable/ic_dnd" + android:src="@*android:drawable/ic_qs_dnd" android:tint="?android:attr/textColorTertiary"/> </FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index b82c250e2bd2..290d75b4de9c 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -129,7 +129,6 @@ <!-- Keyboard shortcuts colors --> <color name="ksh_application_group_color">#fff44336</color> - <color name="ksh_keyword_color">#d9000000</color> <color name="ksh_key_item_color">@color/material_grey_600</color> <color name="ksh_key_item_background">@color/material_grey_100</color> diff --git a/packages/SystemUI/res/values/config_car.xml b/packages/SystemUI/res/values/config_car.xml deleted file mode 100644 index 2c549bc8ce2d..000000000000 --- a/packages/SystemUI/res/values/config_car.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 2016, 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. -*/ ---> - -<resources> - <!-- These next two work together, if you enable this first one you need to provide an intent - uri that will be launched into the docked window. --> - <bool name="config_enablePersistentDockedActivity">false</bool> - <string name="config_persistentDockedActivityIntentUri" translatable="false"></string> - - <!-- configure which system ui bars should be displayed --> - <bool name="config_enableLeftNavigationBar">false</bool> - <bool name="config_enableRightNavigationBar">false</bool> - <bool name="config_enableBottomNavigationBar">true</bool> - <bool name="config_hideNavWhenKeyguardBouncerShown">true</bool> -</resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 039eca694e24..6eb279affc4f 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -23,8 +23,6 @@ <dimen name="navigation_bar_size">@*android:dimen/navigation_bar_height</dimen> <!-- Minimum swipe distance to catch the swipe gestures to invoke assist or switch tasks. --> <dimen name="navigation_bar_min_swipe_distance">48dp</dimen> - <!-- The default distance from a side of the device to start an edge swipe from --> - <dimen name="navigation_bar_default_edge_width">48dp</dimen> <dimen name="navigation_bar_default_edge_height">500dp</dimen> <!-- thickness (height) of the dead zone at the top of the navigation bar, @@ -341,6 +339,8 @@ <dimen name="volume_dialog_ringer_size">64dp</dimen> + <dimen name="volume_dialog_ringer_icon_padding">20dp</dimen> + <dimen name="volume_dialog_caption_size">64dp</dimen> <dimen name="volume_dialog_tap_target_size">48dp</dimen> @@ -983,10 +983,6 @@ <!-- How much into a DisplayCutout's bounds we can go, on each side --> <dimen name="display_cutout_margin_consumption">0px</dimen> - <!-- How much we expand the touchable region of the status bar below the notch to catch touches - that just start below the notch. --> - <dimen name="display_cutout_touchable_region_size">12dp</dimen> - <!-- Padding below Ongoing App Ops dialog title --> <dimen name="ongoing_appops_dialog_sep">16dp</dimen> <!--Padding around text items in Ongoing App Ops dialog --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3aba6a08662e..0411d015fd63 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -79,6 +79,9 @@ <!-- Battery saver confirmation dialog title [CHAR LIMIT=NONE]--> <string name="battery_saver_confirmation_title">Turn on Battery Saver?</string> + <!-- The more generic version of the battery saver confirmation dialog title [CHAR LIMIT=NONE] --> + <string name="battery_saver_confirmation_title_generic">About Battery Saver</string> + <!-- Battery saver confirmation dialog ok text [CHAR LIMIT=40]--> <string name="battery_saver_confirmation_ok">Turn on</string> @@ -2342,18 +2345,6 @@ <!-- Content description for ongoing privacy chip. Use with multiple apps [CHAR LIMIT=NONE]--> <string name="ongoing_privacy_chip_content_multiple_apps">Applications are using your <xliff:g id="types_list" example="camera, location">%s</xliff:g>.</string> - <!-- Action for accepting the Ongoing privacy dialog [CHAR LIMIT=10]--> - <string name="ongoing_privacy_dialog_ok">Got it</string> - - <!-- Action on Ongoing Privacy Dialog to open privacy hub [CHAR LIMIT=23]--> - <string name="ongoing_privacy_dialog_open_settings">Privacy settings</string> - - <!-- Text for item in Ongoing Privacy Dialog title when only one app is using app ops [CHAR LIMIT=NONE] --> - <string name="ongoing_privacy_dialog_single_app_title">App using your <xliff:g id="types_list" example="camera( and location)">%s</xliff:g></string> - - <!-- Text for item in Ongoing Privacy Dialog title when multiple apps is using app ops [CHAR LIMIT=NONE] --> - <string name="ongoing_privacy_dialog_multiple_apps_title">Apps using your <xliff:g id="types_list" example="camera( and location)">%s</xliff:g></string> - <!-- Separator for types. Include spaces before and after if needed [CHAR LIMIT=10] --> <string name="ongoing_privacy_dialog_separator">,\u0020</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 46f4c8689196..d0c17b7f6738 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -112,4 +112,22 @@ public class QuickStepContract { return context.getResources().getInteger( com.android.internal.R.integer.config_navBarInteractionMode); } + + /** + * @return {@code true} if the navbar can be clicked through + */ + public static boolean isNavBarClickThrough(Context context) { + return context.getResources().getBoolean( + com.android.internal.R.bool.config_navBarTapThrough); + } + + /** + * @return the edge sensitivity width in px + */ + public static int getEdgeSensitivityWidth(Context context) { + return context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.config_backGestureInset); + } + + } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java index c0ec405e7dc1..fb4fe814601f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java @@ -99,13 +99,7 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); } - private void showDefaultMessage() { - if (mRemainingAttempts >= 0) { - mSecurityMessageDisplay.setMessage(getPinPasswordErrorMessage( - mRemainingAttempts, true)); - return; - } - + private void setLockedSimMessage() { boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); int count = TelephonyManager.getDefault().getSimCount(); Resources rez = getResources(); @@ -122,13 +116,20 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { color = info.getIconTint(); } } - if (isEsimLocked) { msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); } mSecurityMessageDisplay.setMessage(msg); mSimImageView.setImageTintList(ColorStateList.valueOf(color)); + } + + private void showDefaultMessage() { + setLockedSimMessage(); + if (mRemainingAttempts >= 0) { + return; + } + // Sending empty PIN here to query the number of remaining PIN attempts new CheckSimPin("", mSubId) { @@ -137,8 +138,7 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { " attemptsRemaining=" + attemptsRemaining); if (attemptsRemaining >= 0) { mRemainingAttempts = attemptsRemaining; - mSecurityMessageDisplay.setMessage( - getPinPasswordErrorMessage(attemptsRemaining, true)); + setLockedSimMessage(); } } }.start(); diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java index 32c1242b695d..147def392594 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java @@ -15,15 +15,19 @@ */ package com.android.keyguard.clock; +import android.app.WallpaperManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Color; import android.graphics.Paint.Style; import android.view.LayoutInflater; import android.view.View; import android.widget.TextClock; +import com.android.internal.colorextraction.ColorExtractor; import com.android.keyguard.R; +import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.ClockPlugin; import java.util.TimeZone; @@ -44,6 +48,16 @@ public class BubbleClockController implements ClockPlugin { private final LayoutInflater mLayoutInflater; /** + * Extracts accent color from wallpaper. + */ + private final SysuiColorExtractor mColorExtractor; + + /** + * Renders preview from clock view. + */ + private final ViewPreviewer mRenderer = new ViewPreviewer(); + + /** * Custom clock shown on AOD screen and behind stack scroller on lock. */ private View mView; @@ -64,11 +78,15 @@ public class BubbleClockController implements ClockPlugin { /** * Create a BubbleClockController instance. * - * @param layoutInflater Inflater used to inflate custom clock views. + * @param res Resources contains title and thumbnail. + * @param inflater Inflater used to inflate custom clock views. + * @param colorExtractor Extracts accent color from wallpaper. */ - public BubbleClockController(Resources res, LayoutInflater inflater) { + public BubbleClockController(Resources res, LayoutInflater inflater, + SysuiColorExtractor colorExtractor) { mResources = res; mLayoutInflater = inflater; + mColorExtractor = colorExtractor; } private void createViews() { @@ -99,6 +117,23 @@ public class BubbleClockController implements ClockPlugin { } @Override + public Bitmap getPreview(int width, int height) { + + // Use the big clock view for the preview + View view = getBigClockView(); + + // Initialize state of plugin before generating preview. + setDarkAmount(1f); + setTextColor(Color.WHITE); + ColorExtractor.GradientColors colors = mColorExtractor.getColors( + WallpaperManager.FLAG_LOCK, true); + setColorPalette(colors.supportsDarkText(), colors.getColorPalette()); + onTimeTick(); + + return mRenderer.createPreview(view, width, height); + } + + @Override public View getView() { if (mLockClockContainer == null) { createViews(); diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java index 8ad5c7b90882..d0fff749972d 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java @@ -16,29 +16,19 @@ package com.android.keyguard.clock; import android.annotation.Nullable; -import android.app.WallpaperManager; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Canvas; -import android.graphics.Color; import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.util.ArrayMap; import android.util.DisplayMetrics; -import android.util.Log; import android.view.LayoutInflater; -import android.view.View; -import android.view.View.MeasureSpec; -import android.view.ViewGroup; import androidx.annotation.VisibleForTesting; -import com.android.internal.colorextraction.ColorExtractor; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dock.DockManager; @@ -51,8 +41,6 @@ import com.android.systemui.util.InjectionInflationController; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.FutureTask; import javax.inject.Inject; import javax.inject.Singleton; @@ -128,7 +116,6 @@ public final class ClockManager { private final List<ClockChangedListener> mListeners = new ArrayList<>(); - private final SysuiColorExtractor mColorExtractor; private final int mWidth; private final int mHeight; @@ -144,17 +131,16 @@ public final class ClockManager { ContentResolver contentResolver, SettingsWrapper settingsWrapper) { mContext = context; mPluginManager = pluginManager; - mColorExtractor = colorExtractor; mContentResolver = contentResolver; mSettingsWrapper = settingsWrapper; Resources res = context.getResources(); LayoutInflater layoutInflater = injectionInflater.injectable(LayoutInflater.from(context)); - addClockPlugin(new DefaultClockController(res, layoutInflater)); - addClockPlugin(new BubbleClockController(res, layoutInflater)); - addClockPlugin(new StretchAnalogClockController(res, layoutInflater)); - addClockPlugin(new TypeClockController(res, layoutInflater)); + addClockPlugin(new DefaultClockController(res, layoutInflater, colorExtractor)); + addClockPlugin(new BubbleClockController(res, layoutInflater, colorExtractor)); + addClockPlugin(new StretchAnalogClockController(res, layoutInflater, colorExtractor)); + addClockPlugin(new TypeClockController(res, layoutInflater, colorExtractor)); // Store the size of the display for generation of clock preview. DisplayMetrics dm = res.getDisplayMetrics(); @@ -217,7 +203,7 @@ public final class ClockManager { .setTitle(plugin.getTitle()) .setId(id) .setThumbnail(() -> plugin.getThumbnail()) - .setPreview(() -> getClockPreview(id)) + .setPreview(() -> plugin.getPreview(mWidth, mHeight)) .build()); } @@ -232,81 +218,6 @@ public final class ClockManager { } } - /** - * Generate a realistic preview of a clock face. - * @param clockId ID of clock to use for preview, should be obtained from {@link getClockInfos}. - * Returns null if clockId is not found. - */ - @Nullable - private Bitmap getClockPreview(String clockId) { - FutureTask<Bitmap> task = new FutureTask<>(new Callable<Bitmap>() { - @Override - public Bitmap call() { - Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Config.ARGB_8888); - ClockPlugin plugin = mClocks.get(clockId); - if (plugin == null) { - return null; - } - - // Use the big clock view for the preview - View clockView = plugin.getBigClockView(); - if (clockView == null) { - return null; - } - - // Initialize state of plugin before generating preview. - plugin.setDarkAmount(1f); - plugin.setTextColor(Color.WHITE); - - ColorExtractor.GradientColors colors = mColorExtractor.getColors( - WallpaperManager.FLAG_LOCK, true); - plugin.setColorPalette(colors.supportsDarkText(), colors.getColorPalette()); - plugin.onTimeTick(); - - // Draw clock view hierarchy to canvas. - Canvas canvas = new Canvas(bitmap); - canvas.drawColor(Color.BLACK); - dispatchVisibilityAggregated(clockView, true); - clockView.measure(MeasureSpec.makeMeasureSpec(mWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(mHeight, MeasureSpec.EXACTLY)); - clockView.layout(0, 0, mWidth, mHeight); - clockView.draw(canvas); - return bitmap; - } - }); - - if (Looper.myLooper() == Looper.getMainLooper()) { - task.run(); - } else { - mMainHandler.post(task); - } - - try { - return task.get(); - } catch (Exception e) { - Log.e(TAG, "Error completing task", e); - return null; - } - } - - private void dispatchVisibilityAggregated(View view, boolean isVisible) { - // Similar to View.dispatchVisibilityAggregated implementation. - final boolean thisVisible = view.getVisibility() == View.VISIBLE; - if (thisVisible || !isVisible) { - view.onVisibilityAggregated(isVisible); - } - - if (view instanceof ViewGroup) { - isVisible = thisVisible && isVisible; - ViewGroup vg = (ViewGroup) view; - int count = vg.getChildCount(); - - for (int i = 0; i < count; i++) { - dispatchVisibilityAggregated(vg.getChildAt(i), isVisible); - } - } - } - private void notifyClockChanged(ClockPlugin plugin) { for (int i = 0; i < mListeners.size(); i++) { // It probably doesn't make sense to supply the same plugin instances to multiple diff --git a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java index 8a6a4cd95991..73414b30432f 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java @@ -15,15 +15,19 @@ */ package com.android.keyguard.clock; +import android.app.WallpaperManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Color; import android.graphics.Paint.Style; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; +import com.android.internal.colorextraction.ColorExtractor; import com.android.keyguard.R; +import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.ClockPlugin; import java.util.TimeZone; @@ -44,6 +48,16 @@ public class DefaultClockController implements ClockPlugin { private final LayoutInflater mLayoutInflater; /** + * Extracts accent color from wallpaper. + */ + private final SysuiColorExtractor mColorExtractor; + + /** + * Renders preview from clock view. + */ + private final ViewPreviewer mRenderer = new ViewPreviewer(); + + /** * Root view of preview. */ private View mView; @@ -61,11 +75,15 @@ public class DefaultClockController implements ClockPlugin { /** * Create a DefaultClockController instance. * + * @param res Resources contains title and thumbnail. * @param inflater Inflater used to inflate custom clock views. + * @param colorExtractor Extracts accent color from wallpaper. */ - public DefaultClockController(Resources res, LayoutInflater inflater) { + public DefaultClockController(Resources res, LayoutInflater inflater, + SysuiColorExtractor colorExtractor) { mResources = res; mLayoutInflater = inflater; + mColorExtractor = colorExtractor; } private void createViews() { @@ -90,6 +108,23 @@ public class DefaultClockController implements ClockPlugin { } @Override + public Bitmap getPreview(int width, int height) { + + // Use the big clock view for the preview + View view = getBigClockView(); + + // Initialize state of plugin before generating preview. + setDarkAmount(1f); + setTextColor(Color.WHITE); + ColorExtractor.GradientColors colors = mColorExtractor.getColors( + WallpaperManager.FLAG_LOCK, true); + setColorPalette(colors.supportsDarkText(), colors.getColorPalette()); + onTimeTick(); + + return mRenderer.createPreview(view, width, height); + } + + @Override public View getView() { return null; } diff --git a/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java index 34b2fd86f648..ea9f0cd3c17d 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java @@ -15,15 +15,19 @@ */ package com.android.keyguard.clock; +import android.app.WallpaperManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Color; import android.graphics.Paint.Style; import android.view.LayoutInflater; import android.view.View; import android.widget.TextClock; +import com.android.internal.colorextraction.ColorExtractor; import com.android.keyguard.R; +import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.ClockPlugin; import java.util.TimeZone; @@ -44,6 +48,16 @@ public class StretchAnalogClockController implements ClockPlugin { private final LayoutInflater mLayoutInflater; /** + * Extracts accent color from wallpaper. + */ + private final SysuiColorExtractor mColorExtractor; + + /** + * Renders preview from clock view. + */ + private final ViewPreviewer mRenderer = new ViewPreviewer(); + + /** * Custom clock shown on AOD screen and behind stack scroller on lock. */ private View mBigClockView; @@ -64,11 +78,15 @@ public class StretchAnalogClockController implements ClockPlugin { /** * Create a BubbleClockController instance. * - * @param layoutInflater Inflater used to inflate custom clock views. + * @param res Resources contains title and thumbnail. + * @param inflater Inflater used to inflate custom clock views. + * @param colorExtractor Extracts accent color from wallpaper. */ - public StretchAnalogClockController(Resources res, LayoutInflater inflater) { + public StretchAnalogClockController(Resources res, LayoutInflater inflater, + SysuiColorExtractor colorExtractor) { mResources = res; mLayoutInflater = inflater; + mColorExtractor = colorExtractor; } private void createViews() { @@ -99,6 +117,23 @@ public class StretchAnalogClockController implements ClockPlugin { } @Override + public Bitmap getPreview(int width, int height) { + + // Use the big clock view for the preview + View view = getBigClockView(); + + // Initialize state of plugin before generating preview. + setDarkAmount(1f); + setTextColor(Color.WHITE); + ColorExtractor.GradientColors colors = mColorExtractor.getColors( + WallpaperManager.FLAG_LOCK, true); + setColorPalette(colors.supportsDarkText(), colors.getColorPalette()); + onTimeTick(); + + return mRenderer.createPreview(view, width, height); + } + + @Override public View getView() { if (mView == null) { createViews(); diff --git a/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java index 387f2656c6f8..67c0989b49c4 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java @@ -15,14 +15,18 @@ */ package com.android.keyguard.clock; +import android.app.WallpaperManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Color; import android.graphics.Paint.Style; import android.view.LayoutInflater; import android.view.View; +import com.android.internal.colorextraction.ColorExtractor; import com.android.keyguard.R; +import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.ClockPlugin; import java.util.TimeZone; @@ -43,6 +47,16 @@ public class TypeClockController implements ClockPlugin { private final LayoutInflater mLayoutInflater; /** + * Extracts accent color from wallpaper. + */ + private final SysuiColorExtractor mColorExtractor; + + /** + * Renders preview from clock view. + */ + private final ViewPreviewer mRenderer = new ViewPreviewer(); + + /** * Custom clock shown on AOD screen and behind stack scroller on lock. */ private View mView; @@ -61,11 +75,15 @@ public class TypeClockController implements ClockPlugin { /** * Create a TypeClockController instance. * + * @param res Resources contains title and thumbnail. * @param inflater Inflater used to inflate custom clock views. + * @param colorExtractor Extracts accent color from wallpaper. */ - TypeClockController(Resources res, LayoutInflater inflater) { + TypeClockController(Resources res, LayoutInflater inflater, + SysuiColorExtractor colorExtractor) { mResources = res; mLayoutInflater = inflater; + mColorExtractor = colorExtractor; } private void createViews() { @@ -96,6 +114,23 @@ public class TypeClockController implements ClockPlugin { } @Override + public Bitmap getPreview(int width, int height) { + + // Use the big clock view for the preview + View view = getBigClockView(); + + // Initialize state of plugin before generating preview. + setDarkAmount(1f); + setTextColor(Color.WHITE); + ColorExtractor.GradientColors colors = mColorExtractor.getColors( + WallpaperManager.FLAG_LOCK, true); + setColorPalette(colors.supportsDarkText(), colors.getColorPalette()); + onTimeTick(); + + return mRenderer.createPreview(view, width, height); + } + + @Override public View getView() { if (mLockClock == null) { createViews(); diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ViewPreviewer.java b/packages/SystemUI/src/com/android/keyguard/clock/ViewPreviewer.java new file mode 100644 index 000000000000..abd0dd28dabc --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/ViewPreviewer.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.keyguard.clock; + +import android.annotation.Nullable; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; + +/** + * Creates a preview image ({@link Bitmap}) of a {@link View} for a custom clock face. + */ +final class ViewPreviewer { + + private static final String TAG = "ViewPreviewer"; + + /** + * Handler used to run {@link View#draw(Canvas)} on the main thread. + */ + private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + + /** + * Generate a realistic preview of a clock face. + * + * @param view view is used to generate preview image. + * @param width width of the preview image, should be the same as device width in pixels. + * @param height height of the preview image, should be the same as device height in pixels. + * @return bitmap of view. + */ + @Nullable + Bitmap createPreview(View view, int width, int height) { + if (view == null) { + return null; + } + FutureTask<Bitmap> task = new FutureTask<>(new Callable<Bitmap>() { + @Override + public Bitmap call() { + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + + // Draw clock view hierarchy to canvas. + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(Color.BLACK); + dispatchVisibilityAggregated(view, true); + view.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); + view.layout(0, 0, width, height); + view.draw(canvas); + + return bitmap; + } + }); + + if (Looper.myLooper() == Looper.getMainLooper()) { + task.run(); + } else { + mMainHandler.post(task); + } + + try { + return task.get(); + } catch (Exception e) { + Log.e(TAG, "Error completing task", e); + return null; + } + } + + private void dispatchVisibilityAggregated(View view, boolean isVisible) { + // Similar to View.dispatchVisibilityAggregated implementation. + final boolean thisVisible = view.getVisibility() == View.VISIBLE; + if (thisVisible || !isVisible) { + view.onVisibilityAggregated(isVisible); + } + + if (view instanceof ViewGroup) { + isVisible = thisVisible && isVisible; + ViewGroup vg = (ViewGroup) view; + int count = vg.getChildCount(); + + for (int i = 0; i < count; i++) { + dispatchVisibilityAggregated(vg.getChildAt(i), isVisible); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index e84c64838fd6..3aa9f73939ac 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -293,6 +293,17 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe } /** + * Request the stack expand if needed, then select the specified Bubble as current. + * + * @param notificationKey the notification key for the bubble to be selected + */ + public void expandStackAndSelectBubble(String notificationKey) { + if (mStackView != null && mBubbleData.getBubble(notificationKey) != null) { + mStackView.setExpandedBubble(notificationKey); + } + } + + /** * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack. */ void dismissStack(@DismissReason int reason) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 559c9f6dd04b..de887ff6ff86 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -381,10 +381,18 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } /** - * Update bubble expanded view header when user toggles dark mode. + * Update header color when user toggles dark mode. */ void updateHeaderColor() { - mHeaderView.setBackgroundColor(mContext.getColor(R.attr.colorAccent)); + TypedArray ta = mContext.obtainStyledAttributes( + new int[] {android.R.attr.colorBackgroundFloating, android.R.attr.colorForeground}); + int bgColor = ta.getColor(0, Color.WHITE /* default */); + int btnColor = ta.getColor(1, Color.BLACK /* default */); + ta.recycle(); + + mHeaderView.setBackgroundColor(bgColor); + mSettingsIcon.setColorFilter(btnColor); + mDeepLinkIcon.setColorFilter(btnColor); } private void updateHeaderView() { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 617090ac89e1..be55829869eb 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -52,6 +52,7 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ViewClippingUtil; +import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleController.DismissReason; import com.android.systemui.bubbles.animation.ExpandedAnimationController; @@ -270,8 +271,8 @@ public class BubbleStackView extends FrameLayout { * Handle config changes. */ public void onConfigChanged() { - if (mExpandedBubble != null) { - mExpandedBubble.expandedView.updateHeaderColor(); + for (Bubble b: mBubbleData.getBubbles()) { + b.expandedView.updateHeaderColor(); } } @@ -316,7 +317,8 @@ public class BubbleStackView extends FrameLayout { } switch (action) { case AccessibilityNodeInfo.ACTION_DISMISS: - stackDismissed(BubbleController.DISMISS_ACCESSIBILITY_ACTION); + Dependency.get(BubbleController.class).dismissStack( + BubbleController.DISMISS_ACCESSIBILITY_ACTION); return true; case AccessibilityNodeInfo.ACTION_COLLAPSE: collapseStack(); @@ -422,7 +424,7 @@ public class BubbleStackView extends FrameLayout { * Sets the entry that should be expanded and expands if needed. */ @VisibleForTesting - public void setExpandedBubble(NotificationEntry entry) { + void setExpandedBubble(NotificationEntry entry) { for (int i = 0; i < mBubbleContainer.getChildCount(); i++) { BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); if (entry.equals(bv.getEntry())) { @@ -436,7 +438,7 @@ public class BubbleStackView extends FrameLayout { * * @param entry the notification to add to the stack of bubbles. */ - public void addBubble(NotificationEntry entry) { + void addBubble(NotificationEntry entry) { Bubble b = new Bubble(entry, mInflater, this /* stackView */, mBlockedListener); mBubbleData.addBubble(b); @@ -451,12 +453,17 @@ public class BubbleStackView extends FrameLayout { /** * Remove a bubble from the stack. */ - public void removeBubble(String key, int reason) { + void removeBubble(String key, int reason) { Bubble b = mBubbleData.removeBubble(key); if (b == null) { return; } - int removedIndex = dismissBubble(b, reason); + setBubbleDismissed(b, reason); + + // Remove it from the views + int removedIndex = mBubbleContainer.indexOfChild(b.iconView); + mBubbleContainer.removeViewAt(removedIndex); + int bubbleCount = mBubbleContainer.getChildCount(); if (bubbleCount == 0) { // If no bubbles remain, collapse the entire stack. @@ -481,9 +488,9 @@ public class BubbleStackView extends FrameLayout { /** * Dismiss the stack of bubbles. */ - public void stackDismissed(int reason) { + void stackDismissed(int reason) { for (Bubble bubble : mBubbleData.getBubbles()) { - dismissBubble(bubble, reason); + setBubbleDismissed(bubble, reason); } mBubbleData.clear(); collapseStack(); @@ -495,8 +502,7 @@ public class BubbleStackView extends FrameLayout { } /** - * Marks the notification entry as dismissed, cleans up Bubble icon and expanded view UI - * elements and calls deleteIntent if necessary. + * Marks the notification entry as dismissed & calls any delete intents for the bubble. * * <p>Note: This does not remove the Bubble from BubbleData. * @@ -504,17 +510,13 @@ public class BubbleStackView extends FrameLayout { * @param reason code for the reason the dismiss was triggered * @see BubbleController.DismissReason */ - private int dismissBubble(Bubble bubble, @DismissReason int reason) { + private void setBubbleDismissed(Bubble bubble, @DismissReason int reason) { if (DEBUG) { Log.d(TAG, "dismissBubble: " + bubble + " reason=" + reason); } bubble.entry.setBubbleDismissed(true); bubble.expandedView.cleanUpExpandedState(); - // Remove it from the views - int removedIndex = mBubbleContainer.indexOfChild(bubble.iconView); - mBubbleContainer.removeViewAt(removedIndex); - if (reason == BubbleController.DISMISS_USER_GESTURE) { Notification.BubbleMetadata bubbleMetadata = bubble.entry.getBubbleMetadata(); PendingIntent deleteIntent = bubbleMetadata != null @@ -529,7 +531,6 @@ public class BubbleStackView extends FrameLayout { } } } - return removedIndex; } /** @@ -856,7 +857,6 @@ public class BubbleStackView extends FrameLayout { private void applyCurrentState() { Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded); - mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE); if (mIsExpanded) { // First update the view so that it calculates a new height (ensuring the y position diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java index 8731b6b9767c..0f659c338ce8 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java @@ -799,4 +799,9 @@ public class PhysicsAnimationLayout extends FrameLayout { } } } + + @Override + protected boolean canReceivePointerEvents() { + return false; + } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java index a4592d554f0e..3c6dc7317357 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java @@ -87,7 +87,8 @@ public class DozeDockHandler implements DozeMachine.Part { } private void requestPulseOutNow(State dozeState) { - if (dozeState == State.DOZE_REQUEST_PULSE || dozeState == State.DOZE_PULSING) { + if (dozeState == State.DOZE_REQUEST_PULSE || dozeState == State.DOZE_PULSING + || dozeState == State.DOZE_PULSING_BRIGHT) { final int pulseReason = mMachine.getPulseReason(); if (pulseReason == DozeLog.PULSE_REASON_DOCKING) { mDozeHost.stopPulsing(); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index 36e28dc0156d..060765495f48 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -71,7 +71,7 @@ public class DozeFactory { new DozeScreenState(wrappedService, handler, params, wakeLock), createDozeScreenBrightness(context, wrappedService, sensorManager, host, params, handler), - new DozeWallpaperState(context, machine), + new DozeWallpaperState(context), new DozeDockHandler(context, machine, host, config, handler, dockManager) }); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java index bd7a421e9762..3c9d4a9704a0 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java @@ -33,7 +33,11 @@ public interface DozeHost { boolean isProvisioned(); boolean isBlockingDoze(); - void extendPulse(); + /** + * Makes a current pulse last for twice as long. + * @param reason why we're extending it. + */ + void extendPulse(int reason); void setAnimateWakeup(boolean animateWakeup); void setAnimateScreenOff(boolean animateScreenOff); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index c243899c6bc9..8bf2256a4f80 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -59,6 +59,8 @@ public class DozeMachine { DOZE_REQUEST_PULSE, /** Pulse is showing. Device is awake and showing UI. */ DOZE_PULSING, + /** Pulse is showing with bright wallpaper. Device is awake and showing UI. */ + DOZE_PULSING_BRIGHT, /** Pulse is done showing. Followed by transition to DOZE or DOZE_AOD. */ DOZE_PULSE_DONE, /** Doze is done. DozeService is finished. */ @@ -84,6 +86,7 @@ public class DozeMachine { switch (this) { case DOZE_REQUEST_PULSE: case DOZE_PULSING: + case DOZE_PULSING_BRIGHT: return true; default: return false; @@ -101,6 +104,7 @@ public class DozeMachine { case DOZE: return Display.STATE_OFF; case DOZE_PULSING: + case DOZE_PULSING_BRIGHT: return Display.STATE_ON; case DOZE_AOD: case DOZE_AOD_PAUSING: @@ -188,7 +192,10 @@ public class DozeMachine { @MainThread public State getState() { Assert.isMainThread(); - Preconditions.checkState(!isExecutingTransition()); + if (isExecutingTransition()) { + throw new IllegalStateException("Cannot get state because there were pending " + + "transitions: " + mQueuedRequests.toString()); + } return mState; } @@ -202,6 +209,7 @@ public class DozeMachine { Assert.isMainThread(); Preconditions.checkState(mState == State.DOZE_REQUEST_PULSE || mState == State.DOZE_PULSING + || mState == State.DOZE_PULSING_BRIGHT || mState == State.DOZE_PULSE_DONE, "must be in pulsing state, but is " + mState); return mPulseReason; } @@ -283,7 +291,8 @@ public class DozeMachine { break; case DOZE_PULSE_DONE: Preconditions.checkState( - mState == State.DOZE_REQUEST_PULSE || mState == State.DOZE_PULSING); + mState == State.DOZE_REQUEST_PULSE || mState == State.DOZE_PULSING + || mState == State.DOZE_PULSING_BRIGHT); break; default: break; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 1dd3101075b0..bd6882c01bbd 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -56,6 +56,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi private boolean mRegistered; private int mDefaultDozeBrightness; private boolean mPaused = false; + private boolean mScreenOff = false; private int mLastSensorValue = -1; /** @@ -118,6 +119,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi break; } if (newState != DozeMachine.State.FINISH) { + setScreenOff(newState == DozeMachine.State.DOZE); setPaused(newState == DozeMachine.State.DOZE_AOD_PAUSED); } } @@ -135,15 +137,15 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi try { if (mRegistered) { mLastSensorValue = (int) event.values[0]; - updateBrightnessAndReady(); + updateBrightnessAndReady(false /* force */); } } finally { Trace.endSection(); } } - private void updateBrightnessAndReady() { - if (mRegistered || mDebugBrightnessBucket != -1) { + private void updateBrightnessAndReady(boolean force) { + if (force || mRegistered || mDebugBrightnessBucket != -1) { int sensorValue = mDebugBrightnessBucket == -1 ? mLastSensorValue : mDebugBrightnessBucket; int brightness = computeBrightness(sensorValue); @@ -153,7 +155,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi } int scrimOpacity = -1; - if (mPaused) { + if (mPaused || mScreenOff) { // If AOD is paused, force the screen black until the // sensor reports a new brightness. This ensures that when the screen comes on // again, it will only show after the brightness sensor has stabilized, @@ -216,13 +218,20 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi private void setPaused(boolean paused) { if (mPaused != paused) { mPaused = paused; - updateBrightnessAndReady(); + updateBrightnessAndReady(false /* force */); + } + } + + private void setScreenOff(boolean screenOff) { + if (mScreenOff != screenOff) { + mScreenOff = screenOff; + updateBrightnessAndReady(true /* force */); } } @Override public void onReceive(Context context, Intent intent) { mDebugBrightnessBucket = intent.getIntExtra(BRIGHTNESS_BUCKET, -1); - updateBrightnessAndReady(); + updateBrightnessAndReady(false /* force */); } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 7189a48fed1f..0fe6611b4274 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -16,6 +16,7 @@ package com.android.systemui.doze; +import android.annotation.Nullable; import android.app.AlarmManager; import android.app.UiModeManager; import android.content.BroadcastReceiver; @@ -147,7 +148,7 @@ public class DozeTriggers implements DozeMachine.Part { boolean wakeEvent = rawValues != null && rawValues.length > 0 && rawValues[0] != 0; if (isWakeDisplay) { - onWakeScreen(wakeEvent, mMachine.getState()); + onWakeScreen(wakeEvent, mMachine.isExecutingTransition() ? null : mMachine.getState()); } else if (isLongPress) { requestPulse(pulseReason, sensorPerformedProxCheck); } else if (isWakeLockScreen) { @@ -168,7 +169,7 @@ public class DozeTriggers implements DozeMachine.Part { } else if (isPickup) { gentleWakeUp(pulseReason); } else { - mDozeHost.extendPulse(); + mDozeHost.extendPulse(pulseReason); } }, sensorPerformedProxCheck || (mDockManager != null && mDockManager.isDocked()), pulseReason); @@ -212,7 +213,8 @@ public class DozeTriggers implements DozeMachine.Part { final boolean pausing = (state == DozeMachine.State.DOZE_AOD_PAUSING); final boolean aod = (state == DozeMachine.State.DOZE_AOD); - if (state == DozeMachine.State.DOZE_PULSING) { + if (state == DozeMachine.State.DOZE_PULSING + || state == DozeMachine.State.DOZE_PULSING_BRIGHT) { boolean ignoreTouch = near; if (DEBUG) Log.i(TAG, "Prox changed, ignore touch = " + ignoreTouch); mDozeHost.onIgnoreTouchWhilePulsing(ignoreTouch); @@ -227,10 +229,14 @@ public class DozeTriggers implements DozeMachine.Part { } } - private void onWakeScreen(boolean wake, DozeMachine.State state) { + /** + * When a wake screen event is received from a sensor + * @param wake {@code true} when it's time to wake up, {@code false} when we should sleep. + * @param state The current state, or null if the state could not be determined due to enqueued + * transitions. + */ + private void onWakeScreen(boolean wake, @Nullable DozeMachine.State state) { DozeLog.traceWakeDisplay(wake); - boolean paused = (state == DozeMachine.State.DOZE_AOD_PAUSED); - boolean pausing = (state == DozeMachine.State.DOZE_AOD_PAUSING); sWakeDisplaySensorState = wake; if (wake) { @@ -244,6 +250,8 @@ public class DozeTriggers implements DozeMachine.Part { } }, false /* alreadyPerformedProxCheck */, DozeLog.REASON_SENSOR_WAKE_UP); } else { + boolean paused = (state == DozeMachine.State.DOZE_AOD_PAUSED); + boolean pausing = (state == DozeMachine.State.DOZE_AOD_PAUSING); if (!pausing && !paused) { mMachine.requestState(DozeMachine.State.DOZE); } @@ -276,6 +284,7 @@ public class DozeTriggers implements DozeMachine.Part { mDozeSensors.setListening(false); break; case DOZE_PULSING: + case DOZE_PULSING_BRIGHT: mDozeSensors.setTouchscreenSensorsListening(false); mDozeSensors.setProxListening(true); break; @@ -306,7 +315,16 @@ public class DozeTriggers implements DozeMachine.Part { private void requestPulse(final int reason, boolean performedProxCheck) { Assert.isMainThread(); - mDozeHost.extendPulse(); + mDozeHost.extendPulse(reason); + + // When already pulsing we're allowed to show the wallpaper directly without + // requesting a new pulse. + if (mMachine.getState() == DozeMachine.State.DOZE_PULSING + && reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) { + mMachine.requestState(DozeMachine.State.DOZE_PULSING_BRIGHT); + return; + } + if (mPulsePending || !mAllowPulseTriggers || !canPulse()) { if (mAllowPulseTriggers) { DozeLog.tracePulseDropped(mContext, mPulsePending, mMachine.getState(), diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index 847182d3ad35..51e96d2eecad 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -94,7 +94,10 @@ public class DozeUi implements DozeMachine.Part { @Override public void onPulseStarted() { try { - mMachine.requestState(DozeMachine.State.DOZE_PULSING); + mMachine.requestState( + reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN + ? DozeMachine.State.DOZE_PULSING_BRIGHT + : DozeMachine.State.DOZE_PULSING); } catch (IllegalStateException e) { // It's possible that the pulse was asynchronously cancelled while // we were waiting for it to start (under stress conditions.) @@ -148,6 +151,7 @@ public class DozeUi implements DozeMachine.Part { switch (state) { case DOZE_REQUEST_PULSE: case DOZE_PULSING: + case DOZE_PULSING_BRIGHT: case DOZE_PULSE_DONE: mHost.setAnimateWakeup(true); break; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java index ca9f24fd236f..1b3cd881b949 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java @@ -38,19 +38,17 @@ public class DozeWallpaperState implements DozeMachine.Part { private final IWallpaperManager mWallpaperManagerService; private final DozeParameters mDozeParameters; - private final DozeMachine mMachine; private boolean mIsAmbientMode; - public DozeWallpaperState(Context context, DozeMachine machine) { - this(machine, IWallpaperManager.Stub.asInterface( + public DozeWallpaperState(Context context) { + this(IWallpaperManager.Stub.asInterface( ServiceManager.getService(Context.WALLPAPER_SERVICE)), DozeParameters.getInstance(context)); } @VisibleForTesting - DozeWallpaperState(DozeMachine machine, IWallpaperManager wallpaperManagerService, + DozeWallpaperState(IWallpaperManager wallpaperManagerService, DozeParameters parameters) { - mMachine = machine; mWallpaperManagerService = wallpaperManagerService; mDozeParameters = parameters; } @@ -65,16 +63,13 @@ public class DozeWallpaperState implements DozeMachine.Part { case DOZE_AOD_PAUSED: case DOZE_REQUEST_PULSE: case DOZE_PULSE_DONE: - isAmbientMode = true; - break; case DOZE_PULSING: - isAmbientMode = - mMachine.getPulseReason() != DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN; + isAmbientMode = true; break; + case DOZE_PULSING_BRIGHT: default: isAmbientMode = false; } - final boolean animated; if (isAmbientMode) { animated = mDozeParameters.shouldControlScreenOff(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index b03b872b877f..6f50baa53e38 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -286,8 +286,9 @@ public class KeyguardSliceProvider extends SliceProvider implements RowBuilder dndBuilder = new RowBuilder(mDndUri) .setContentDescription(getContext().getResources() .getString(R.string.accessibility_quick_settings_dnd)) - .addEndItem(IconCompat.createWithResource(getContext(), R.drawable.stat_sys_dnd), - ListBuilder.ICON_IMAGE); + .addEndItem( + IconCompat.createWithResource(getContext(), R.drawable.stat_sys_dnd), + ListBuilder.ICON_IMAGE); builder.addRow(dndBuilder); } diff --git a/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt b/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt index d7a2d9acf3b5..02ad0f1766bd 100644 --- a/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt +++ b/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt @@ -17,7 +17,8 @@ data class BatteryStateSnapshot( val timeRemainingMillis: Long, val severeThresholdMillis: Long, val lowThresholdMillis: Long, - val isBasedOnUsage: Boolean + val isBasedOnUsage: Boolean, + val isLowWarningEnabled: Boolean ) { /** * Returns whether hybrid warning logic/copy should be used for this snapshot @@ -48,7 +49,8 @@ data class BatteryStateSnapshot( NO_ESTIMATE_AVAILABLE.toLong(), NO_ESTIMATE_AVAILABLE.toLong(), NO_ESTIMATE_AVAILABLE.toLong(), - false + false, + true ) { this.isHybrid = false } diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java index bd130f4b40f3..a87922792616 100644 --- a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java +++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java @@ -23,4 +23,9 @@ public interface EnhancedEstimates { * show a severe warning to the user. */ long getSevereWarningThreshold(); + + /** + * Returns a boolean indicating if the low warning should be shown at all or not. + */ + boolean getLowWarningEnabled(); } diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java index 3f2417638f1a..bfb809ecbf34 100644 --- a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java @@ -21,4 +21,9 @@ public class EnhancedEstimatesImpl implements EnhancedEstimates { public long getSevereWarningThreshold() { return 0; } + + @Override + public boolean getLowWarningEnabled() { + return true; + } } diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index 41bcab53f8e9..10f727bc7189 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -31,6 +31,7 @@ import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.os.UserHandle; +import android.provider.Settings.Secure; import android.text.Annotation; import android.text.Layout; import android.text.SpannableString; @@ -70,6 +71,7 @@ import javax.inject.Singleton; */ @Singleton public class PowerNotificationWarnings implements PowerUI.WarningsUI { + private static final String TAG = PowerUI.TAG + ".Notification"; private static final boolean DEBUG = PowerUI.DEBUG; @@ -119,6 +121,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) .build(); + public static final String EXTRA_CONFIRM_ONLY = "extra_confirm_only"; private final Context mContext; private final NotificationManager mNoMan; @@ -544,10 +547,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { updateNotification(); } - private void showStartSaverConfirmation() { + private void showStartSaverConfirmation(boolean confirmOnly) { if (mSaverConfirmation != null) return; final SystemUIDialog d = new SystemUIDialog(mContext); - d.setTitle(R.string.battery_saver_confirmation_title); d.setMessage(getBatterySaverDescription()); // Sad hack for http://b/78261259 and http://b/78298335. Otherwise "Battery" may be split @@ -558,9 +560,19 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { // We need to set LinkMovementMethod to make the link clickable. d.setMessageMovementMethod(LinkMovementMethod.getInstance()); - d.setNegativeButton(android.R.string.cancel, null); - d.setPositiveButton(R.string.battery_saver_confirmation_ok, + if (confirmOnly) { + d.setTitle(R.string.battery_saver_confirmation_title_generic); + d.setPositiveButton(com.android.internal.R.string.confirm_battery_saver, + (dialog, which) -> Secure.putInt( + mContext.getContentResolver(), + Secure.LOW_POWER_WARNING_ACKNOWLEDGED, + 1)); + } else { + d.setTitle(R.string.battery_saver_confirmation_title); + d.setPositiveButton(R.string.battery_saver_confirmation_ok, (dialog, which) -> setSaverMode(true, false)); + d.setNegativeButton(android.R.string.cancel, null); + } d.setShowForAllUsers(true); d.setOnDismissListener((dialog) -> mSaverConfirmation = null); d.show(); @@ -719,7 +731,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { dismissLowBatteryNotification(); } else if (action.equals(ACTION_SHOW_START_SAVER_CONFIRMATION)) { dismissLowBatteryNotification(); - showStartSaverConfirmation(); + showStartSaverConfirmation(intent.getBooleanExtra(EXTRA_CONFIRM_ONLY, false)); } else if (action.equals(ACTION_DISMISSED_WARNING)) { dismissLowBatteryWarning(); } else if (ACTION_CLICKED_TEMP_WARNING.equals(action)) { diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index 18638606a251..4e41108f6496 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -284,7 +284,8 @@ public class PowerUI extends SystemUI { plugged, bucket, mBatteryStatus, mLowBatteryReminderLevels[1], mLowBatteryReminderLevels[0], estimate.getEstimateMillis(), mEnhancedEstimates.getSevereWarningThreshold(), - mEnhancedEstimates.getLowWarningThreshold(), estimate.isBasedOnUsage()); + mEnhancedEstimates.getLowWarningThreshold(), estimate.isBasedOnUsage(), + mEnhancedEstimates.getLowWarningEnabled()); } else { if (DEBUG) { Slog.d(TAG, "using standard"); @@ -351,7 +352,6 @@ public class PowerUI extends SystemUI { Slog.d(TAG, "Low warning marked as shown this cycle"); mLowWarningShownThisChargeCycle = true; } - } else if (shouldDismissHybridWarning(currentSnapshot)) { if (DEBUG) { Slog.d(TAG, "Dismissing warning"); @@ -375,8 +375,9 @@ public class PowerUI extends SystemUI { return false; } - // Only show the low warning once per charge cycle & no battery saver - final boolean canShowWarning = !mLowWarningShownThisChargeCycle && !snapshot.isPowerSaver() + // Only show the low warning if enabled once per charge cycle & no battery saver + final boolean canShowWarning = snapshot.isLowWarningEnabled() + && !mLowWarningShownThisChargeCycle && !snapshot.isPowerSaver() && (snapshot.getTimeRemainingMillis() < snapshot.getLowThresholdMillis() || snapshot.getBatteryLevel() <= snapshot.getLowLevelThreshold()); @@ -386,6 +387,7 @@ public class PowerUI extends SystemUI { || snapshot.getBatteryLevel() <= snapshot.getSevereLevelThreshold()); final boolean canShow = canShowWarning || canShowSevereWarning; + if (DEBUG) { Slog.d(TAG, "Enhanced trigger is: " + canShow + "\nwith battery snapshot:" + " mLowWarningShownThisChargeCycle: " + mLowWarningShownThisChargeCycle diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt index 59b3c3464a69..d08a3733703b 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt @@ -53,9 +53,4 @@ class PrivacyDialogBuilder(private val context: Context, itemsList: List<Privacy else -> types.map { it.getName(context) }.joinWithAnd().toString() } } - - fun getDialogTitle(): String { - return context.getString(R.string.ongoing_privacy_dialog_multiple_apps_title, - joinTypes()) - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java index 7c937a944113..b682cb09b598 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java @@ -160,9 +160,9 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic int footerIconId = R.drawable.ic_info_outline; if (vpnName != null || vpnNameWorkProfile != null) { if (mSecurityController.isVpnBranded()) { - footerIconId = R.drawable.ic_qs_branded_vpn; + footerIconId = R.drawable.stat_sys_branded_vpn; } else { - footerIconId = R.drawable.ic_qs_vpn; + footerIconId = R.drawable.stat_sys_vpn_ic; } } if (mFooterIconId != footerIconId) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index 387de716844c..19e20a93ce66 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java @@ -43,8 +43,7 @@ import javax.inject.Inject; /** Quick settings tile: Airplane mode **/ public class AirplaneModeTile extends QSTileImpl<BooleanState> { - private final Icon mIcon = - ResourceIcon.get(R.drawable.ic_signal_airplane); + private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_airplane); private final GlobalSetting mSetting; private final ActivityStarter mActivityStarter; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 5b85498574a7..ca040762047c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -147,14 +147,15 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { state.icon = ResourceIcon.get(R.drawable.ic_bluetooth_transient_animation); state.contentDescription = state.secondaryLabel; } else { - state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on); + state.icon = + ResourceIcon.get(com.android.internal.R.drawable.ic_qs_bluetooth); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_bluetooth) + "," + mContext.getString(R.string.accessibility_not_connected); } state.state = Tile.STATE_ACTIVE; } else { - state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on); + state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_bluetooth); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_bluetooth); state.state = Tile.STATE_INACTIVE; @@ -288,7 +289,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { // This method returns Pair<Drawable, String> while first value is the drawable return BluetoothDeviceLayerDrawable.createLayerDrawable( context, - R.drawable.ic_qs_bluetooth_connected, + R.drawable.ic_bluetooth_connected, mBatteryLevel, mIconScale); } @@ -309,7 +310,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { @Override public Drawable getDrawable(Context context) { // This method returns Pair<Drawable, String> - the first value is the drawable. - return context.getDrawable(R.drawable.ic_qs_bluetooth_connected); + return context.getDrawable(R.drawable.ic_bluetooth_connected); } } @@ -383,12 +384,12 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { for (CachedBluetoothDevice device : devices) { if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue; final Item item = new Item(); - item.iconResId = R.drawable.ic_qs_bluetooth_on; + item.iconResId = com.android.internal.R.drawable.ic_qs_bluetooth; item.line1 = device.getName(); item.tag = device; int state = device.getMaxConnectionState(); if (state == BluetoothProfile.STATE_CONNECTED) { - item.iconResId = R.drawable.ic_qs_bluetooth_connected; + item.iconResId = R.drawable.ic_bluetooth_connected; int batteryLevel = device.getBatteryLevel(); if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { item.icon = new BluetoothBatteryTileIcon(batteryLevel,1 /* iconScale */); 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 bdebf79d823b..415870c590a3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -202,8 +202,8 @@ public class CastTile extends QSTileImpl<BooleanState> { if (connecting && !state.value) { state.secondaryLabel = mContext.getString(R.string.quick_settings_connecting); } - state.icon = ResourceIcon.get(state.value ? R.drawable.ic_qs_cast_on - : R.drawable.ic_qs_cast_off); + state.icon = ResourceIcon.get(state.value ? R.drawable.ic_cast_connected + : R.drawable.ic_cast); if (mWifiConnected || state.value) { state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; if (!state.value) { @@ -334,7 +334,7 @@ public class CastTile extends QSTileImpl<BooleanState> { for (CastDevice device : devices) { if (device.state == CastDevice.STATE_CONNECTED) { final Item item = new Item(); - item.iconResId = R.drawable.ic_qs_cast_on; + item.iconResId = R.drawable.ic_cast_connected; item.line1 = getDeviceName(device); item.line2 = mContext.getString(R.string.quick_settings_connected); item.tag = device; @@ -354,7 +354,7 @@ public class CastTile extends QSTileImpl<BooleanState> { final CastDevice device = mVisibleOrder.get(id); if (!devices.contains(device)) continue; final Item item = new Item(); - item.iconResId = R.drawable.ic_qs_cast_off; + item.iconResId = R.drawable.ic_cast; item.line1 = getDeviceName(device); if (device.state == CastDevice.STATE_CONNECTING) { item.line2 = mContext.getString(R.string.quick_settings_connecting); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 7fcd59f7c931..869fa6b18245 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -235,7 +235,7 @@ public class DndTile extends QSTileImpl<BooleanState> { state.label = getTileLabel(); state.secondaryLabel = TextUtils.emptyIfNull(ZenModeConfig.getDescription(mContext, zen != Global.ZEN_MODE_OFF, mController.getConfig(), false)); - state.icon = ResourceIcon.get(R.drawable.ic_dnd); + state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_dnd); checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME); switch (zen) { case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index dfa3fb9dafc9..2755e9880b58 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -35,7 +35,7 @@ import javax.inject.Inject; public class FlashlightTile extends QSTileImpl<BooleanState> implements FlashlightController.FlashlightListener { - private final Icon mIcon = ResourceIcon.get(R.drawable.ic_signal_flashlight); + private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_flashlight); private final FlashlightController mFlashlightController; @Inject diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index a0f4e24d2f93..837ea9fc5f4e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -37,7 +37,7 @@ import javax.inject.Inject; /** Quick settings tile: Location **/ public class LocationTile extends QSTileImpl<BooleanState> { - private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_signal_location); + private final Icon mIcon = ResourceIcon.get(R.drawable.ic_location); private final LocationController mController; private final KeyguardMonitor mKeyguard; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java index 21f3d6e77f7b..7ca1e44c93cd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java @@ -36,7 +36,7 @@ import javax.inject.Inject; /** Quick settings tile: Rotation **/ public class RotationLockTile extends QSTileImpl<BooleanState> { - private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_auto_rotate); + private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate); private final RotationLockController mController; @Inject diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 494e6cde5a1c..ead39c69730e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -439,6 +439,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils .supportsRoundedCornersOnWindows(mContext.getResources()); + // Assumes device always starts with back button until launcher tells it that it does not + mBackButtonAlpha = 1.0f; + // Listen for the package update changes. if (mDeviceProvisionedController.getCurrentUser() == UserHandle.USER_SYSTEM) { updateEnabledState(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java index 7f39e474f6c6..7e6ddcfea762 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java @@ -128,7 +128,8 @@ public final class KeyboardShortcuts { private KeyCharacterMap mBackupKeyCharacterMap; private KeyboardShortcuts(Context context) { - this.mContext = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault_Light); + this.mContext = new ContextThemeWrapper( + context, android.R.style.Theme_DeviceDefault_Settings); this.mPackageManager = AppGlobals.getPackageManager(); loadResources(context); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 112f91ca05c9..a87e50c50f51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -671,6 +671,7 @@ public class KeyguardIndicationController implements StateListener { public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) { super.onBiometricAuthenticated(userId, biometricSourceType); mLastSuccessiveErrorMessage = -1; + mHandler.sendEmptyMessage(MSG_HIDE_TRANSIENT); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java index cb9060bc9574..cf6e64cef365 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java @@ -289,4 +289,9 @@ public class ScrimView extends View implements ConfigurationController.Configura } } } + + @Override + protected boolean canReceivePointerEvents() { + return false; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java index b788f537316b..fd2f72062be7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -78,8 +78,7 @@ public final class NotificationClicker implements View.OnClickListener { row.setJustClicked(true); DejankUtils.postAfterTraversal(() -> row.setJustClicked(false)); - // If it was a bubble we should close it - if (row.getEntry().isBubble()) { + if (!row.getEntry().isBubble()) { mBubbleController.collapseStack(); } @@ -95,7 +94,8 @@ public final class NotificationClicker implements View.OnClickListener { */ public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { Notification notification = sbn.getNotification(); - if (notification.contentIntent != null || notification.fullScreenIntent != null) { + if (notification.contentIntent != null || notification.fullScreenIntent != null + || row.getEntry().isBubble()) { row.setOnClickListener(this); } else { row.setOnClickListener(null); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java index b91cdaf9ae80..d3e5af8c729e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java @@ -220,8 +220,6 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { StatusBarNotification sbn, ExpandableNotificationRow row) { row.setIsLowPriority(entry.ambient); - // bind the click event to the content area - checkNotNull(mNotificationClicker).register(row, sbn); // Extract target SDK version. try { @@ -257,6 +255,9 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { row.setNeedsRedaction( Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry)); row.inflateViews(); + + // bind the click event to the content area + checkNotNull(mNotificationClicker).register(row, sbn); } private void logNotificationExpansion(String key, boolean userAction, boolean expanded) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 9630727c8f3d..d287b92876b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -552,6 +552,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mEntry.mIsSystemNotification = isSystemNotification(mContext, mStatusBarNotification); } + isNonblockable |= mEntry.channel.isImportanceLockedByOEM(); + isNonblockable |= mEntry.channel.isImportanceLockedByCriticalDeviceFunction(); + if (!isNonblockable && mEntry != null && mEntry.mIsSystemNotification != null) { if (mEntry.mIsSystemNotification) { if (mEntry.channel != null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 520df9797cc3..ce206816bf1d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -399,7 +399,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { - if (mAmbientState.isDarkAtAll() && !mAmbientState.isFullyDark()) { + if (mAmbientState.isDarkAtAll() && !mAmbientState.isFullyDark() || !mShowDarkShelf) { outline.setRoundRect(mBackgroundAnimationRect, mCornerRadius); } else { ViewOutlineProvider.BACKGROUND.getOutline(view, outline); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java index 79056175b595..211a40a91101 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java @@ -70,7 +70,7 @@ public class BarTransitions { private final String mTag; private final View mView; - private final BarBackgroundDrawable mBarBackground; + protected final BarBackgroundDrawable mBarBackground; private int mMode; private boolean mAlwaysOpaque = false; @@ -152,7 +152,7 @@ public class BarTransitions { return mode == MODE_LIGHTS_OUT || mode == MODE_LIGHTS_OUT_TRANSPARENT; } - private static class BarBackgroundDrawable extends Drawable { + protected static class BarBackgroundDrawable extends Drawable { private final int mOpaque; private final int mSemiTransparent; private final int mTransparent; @@ -171,6 +171,7 @@ public class BarTransitions { private int mGradientAlphaStart; private int mColorStart; + private Rect mFrame; public BarBackgroundDrawable(Context context, int gradientResourceId) { @@ -190,6 +191,10 @@ public class BarTransitions { mGradient = context.getDrawable(gradientResourceId); } + public void setFrame(Rect frame) { + mFrame = frame; + } + @Override public void setAlpha(int alpha) { // noop @@ -296,7 +301,11 @@ public class BarTransitions { if (mTintFilter != null) { mPaint.setColorFilter(mTintFilter); } - canvas.drawPaint(mPaint); + if (mFrame != null) { + canvas.drawRect(mFrame, mPaint); + } else { + canvas.drawPaint(mPaint); + } } if (mAnimating) { invalidateSelf(); // keep going diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java index 08a10dc925e3..ac58e681dbbc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java @@ -36,7 +36,8 @@ import javax.inject.Singleton; /** */ @Singleton -public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher { +public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, + LightBarTransitionsController.DarkIntensityApplier { private final LightBarTransitionsController mTransitionsController; private final Rect mTintArea = new Rect(); @@ -54,8 +55,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher { mDarkModeIconColorSingleTone = context.getColor(R.color.dark_mode_icon_color_single_tone); mLightModeIconColorSingleTone = context.getColor(R.color.light_mode_icon_color_single_tone); - mTransitionsController = new LightBarTransitionsController(context, - this::setIconTintInternal); + mTransitionsController = new LightBarTransitionsController(context, this); } public LightBarTransitionsController getTransitionsController() { @@ -104,13 +104,19 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher { applyIconTint(); } - private void setIconTintInternal(float darkIntensity) { + @Override + public void applyDarkIntensity(float darkIntensity) { mDarkIntensity = darkIntensity; mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone); applyIconTint(); } + @Override + public int getTintAnimationDuration() { + return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION; + } + private void applyIconTint() { for (int i = 0; i < mReceivers.size(); i++) { mReceivers.valueAt(i).onDarkChanged(mTintArea, mDarkIntensity, mIconTint); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java index 236c72c5cc2c..0731a568ae7d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java @@ -108,16 +108,13 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da } String zen = args.getString("zen"); if (zen != null) { - int iconId = zen.equals("important") ? R.drawable.stat_sys_zen_important - : zen.equals("none") ? R.drawable.stat_sys_zen_none - : 0; + int iconId = zen.equals("dnd") ? R.drawable.stat_sys_dnd : 0; updateSlot("zen", null, iconId); } String bt = args.getString("bluetooth"); if (bt != null) { - int iconId = bt.equals("disconnected") ? R.drawable.stat_sys_data_bluetooth - : bt.equals("connected") ? R.drawable.stat_sys_data_bluetooth_connected - : 0; + int iconId = bt.equals("connected") + ? R.drawable.stat_sys_data_bluetooth_connected : 0; updateSlot("bluetooth", null, iconId); } String location = args.getString("location"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 9844d8e5a67a..b7a154d67c10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -140,7 +140,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, mHeadsUpInset = mStatusBarHeight + resources.getDimensionPixelSize( R.dimen.heads_up_status_bar_padding); mDisplayCutoutTouchableRegionSize = resources.getDimensionPixelSize( - R.dimen.display_cutout_touchable_region_size); + com.android.internal.R.dimen.display_cutout_touchable_region_size); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java index b622688a8ac6..d7097309ce20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java @@ -16,9 +16,6 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.statusbar.phone.NavBarTintController.DEFAULT_COLOR_ADAPT_TRANSITION_TIME; -import static com.android.systemui.statusbar.phone.NavBarTintController.MIN_COLOR_ADAPT_TRANSITION_TIME; - import android.animation.ValueAnimator; import android.content.Context; import android.os.Bundle; @@ -52,7 +49,6 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, private final DarkIntensityApplier mApplier; private final KeyguardMonitor mKeyguardMonitor; private final StatusBarStateController mStatusBarStateController; - private NavBarTintController mColorAdaptionController; private boolean mTransitionDeferring; private long mTransitionDeferringStartTime; @@ -118,7 +114,8 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, } if (mTransitionPending && mTintChangePending) { mTintChangePending = false; - animateIconTint(mPendingDarkIntensity, 0 /* delay */, getTintAnimationDuration()); + animateIconTint(mPendingDarkIntensity, 0 /* delay */, + mApplier.getTintAnimationDuration()); } mTransitionPending = false; } @@ -159,15 +156,8 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()), mTransitionDeferringDuration); } else { - animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, getTintAnimationDuration()); - } - } - - public long getTintAnimationDuration() { - if (NavBarTintController.isEnabled(mContext)) { - return Math.max(DEFAULT_COLOR_ADAPT_TRANSITION_TIME, MIN_COLOR_ADAPT_TRANSITION_TIME); + animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, mApplier.getTintAnimationDuration()); } - return DEFAULT_TINT_ANIMATION_DURATION; } public float getCurrentDarkIntensity() { @@ -243,5 +233,6 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, */ public interface DarkIntensityApplier { void applyDarkIntensity(float darkIntensity); + int getTintAnimationDuration(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index cbb5d5430e8d..94856234503c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -988,11 +988,11 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback if (Intent.ACTION_SCREEN_ON.equals(action)) { // Enabled and screen is on, start it again if enabled if (NavBarTintController.isEnabled(getContext())) { - mNavigationBarView.getColorAdaptionController().start(); + mNavigationBarView.getTintController().start(); } } else { // Screen off disable it - mNavigationBarView.getColorAdaptionController().stop(); + mNavigationBarView.getTintController().stop(); } } if (Intent.ACTION_USER_SWITCHED.equals(action)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index 3984405f8e09..8ff6cc9b3d93 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -16,7 +16,11 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.phone.NavBarTintController.DEFAULT_COLOR_ADAPT_TRANSITION_TIME; +import static com.android.systemui.statusbar.phone.NavBarTintController.MIN_COLOR_ADAPT_TRANSITION_TIME; + import android.content.Context; +import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; @@ -30,7 +34,8 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.Dependency; import com.android.systemui.R; -public final class NavigationBarTransitions extends BarTransitions { +public final class NavigationBarTransitions extends BarTransitions implements + LightBarTransitionsController.DarkIntensityApplier { private final NavigationBarView mView; private final IStatusBarService mBarService; @@ -58,8 +63,7 @@ public final class NavigationBarTransitions extends BarTransitions { mView = view; mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); - mLightTransitionsController = new LightBarTransitionsController(view.getContext(), - this::applyDarkIntensity); + mLightTransitionsController = new LightBarTransitionsController(view.getContext(), this); mAllowAutoDimWallpaperNotVisible = view.getContext().getResources() .getBoolean(R.bool.config_navigation_bar_enable_auto_dim_no_visible_wallpaper); @@ -105,6 +109,10 @@ public final class NavigationBarTransitions extends BarTransitions { applyLightsOut(true, false); } + void setBackgroundFrame(Rect frame) { + mBarBackground.setFrame(frame); + } + @Override protected boolean isLightsOut(int mode) { return super.isLightsOut(mode) || (mAllowAutoDimWallpaperNotVisible && mAutoDim @@ -119,6 +127,7 @@ public final class NavigationBarTransitions extends BarTransitions { protected void onTransition(int oldMode, int newMode, boolean animate) { super.onTransition(oldMode, newMode, animate); applyLightsOut(animate, false /*force*/); + mView.onBarTransition(newMode); } private void applyLightsOut(boolean animate, boolean force) { @@ -164,4 +173,12 @@ public final class NavigationBarTransitions extends BarTransitions { } mView.onDarkIntensityChange(darkIntensity); } + + @Override + public int getTintAnimationDuration() { + if (NavBarTintController.isEnabled(mView.getContext())) { + return Math.max(DEFAULT_COLOR_ADAPT_TRANSITION_TIME, MIN_COLOR_ADAPT_TRANSITION_TIME); + } + return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index bfbe886eed98..f22ecf6792bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -30,6 +30,7 @@ import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE; import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW; import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_VIEWS; import android.animation.LayoutTransition; @@ -172,7 +173,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private RecentsOnboarding mRecentsOnboarding; private NotificationPanelView mPanelView; - private NavBarTintController mColorAdaptionController; + private NavBarTintController mTintController; private boolean mAssistantAvailable; private NavigationPrototypeController mPrototypeController; private NavigationGestureAction[] mDefaultGestureMap; @@ -309,9 +310,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav @Override public void onColorAdaptChanged(boolean enabled) { if (enabled) { - mColorAdaptionController.start(); + mTintController.start(); } else { - mColorAdaptionController.stop(); + mTintController.stop(); } } @@ -442,15 +443,15 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mPrototypeController = new NavigationPrototypeController(context); mPrototypeController.register(); mPrototypeController.setOnPrototypeChangedListener(mPrototypeListener); - mColorAdaptionController = new NavBarTintController(this, getLightTransitionsController()); + mTintController = new NavBarTintController(this, getLightTransitionsController()); IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED); filter.addDataScheme("package"); context.registerReceiver(mOverlaysChangedReceiver, filter); } - public NavBarTintController getColorAdaptionController() { - return mColorAdaptionController; + public NavBarTintController getTintController() { + return mTintController; } public BarTransitions getBarTransitions() { @@ -476,7 +477,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); - mColorAdaptionController.onDraw(); + mTintController.onDraw(); } private void updateNavigationGestures() { @@ -557,6 +558,17 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav return super.onTouchEvent(event); } + void onBarTransition(int newMode) { + if (newMode == MODE_OPAQUE) { + // If the nav bar background is opaque, stop auto tinting since we know the icons are + // showing over a dark background + mTintController.stop(); + getLightTransitionsController().setIconsDark(false /* dark */, true /* animate */); + } else { + mTintController.start(); + } + } + private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) { if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) { switch (event.getActionMasked()) { @@ -978,9 +990,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav // Color adaption is tied with showing home handle, only avaliable if visible if (visible) { - mColorAdaptionController.start(); + mTintController.start(); } else { - mColorAdaptionController.stop(); + mTintController.stop(); } } @@ -1195,7 +1207,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav if (DEBUG) Log.d(TAG, String.format( "onMeasure: (%dx%d) old: (%dx%d)", w, h, getMeasuredWidth(), getMeasuredHeight())); - final boolean newVertical = w > 0 && h > w; + final boolean newVertical = w > 0 && h > w + && !QuickStepContract.isGesturalMode(getContext()); if (newVertical != mIsVertical) { mIsVertical = newVertical; if (DEBUG) { @@ -1205,6 +1218,19 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav reorient(); notifyVerticalChangedListener(newVertical); } + + if (QuickStepContract.isGesturalMode(getContext())) { + // Update the nav bar background to match the height of the visible nav bar + int height = mIsVertical + ? getResources().getDimensionPixelSize( + com.android.internal.R.dimen.navigation_bar_height_landscape) + : getResources().getDimensionPixelSize( + com.android.internal.R.dimen.navigation_bar_height); + int frameHeight = getResources().getDimensionPixelSize( + com.android.internal.R.dimen.navigation_bar_frame_height); + mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h)); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @@ -1231,9 +1257,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { - mColorAdaptionController.start(); + mTintController.start(); } else { - mColorAdaptionController.stop(); + mTintController.stop(); } } @@ -1416,7 +1442,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mGestureHelper.dump(pw); } mRecentsOnboarding.dump(pw); - mColorAdaptionController.dump(pw); + mTintController.dump(pw); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java index 81a425cd5eba..7dc71f590ecd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationHandle.java @@ -57,6 +57,7 @@ public class NavigationHandle extends View implements ButtonInterface { mLightColor = Utils.getColorAttrDefaultColor(lightContext, R.attr.singleToneColor); mDarkColor = Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor); mPaint.setAntiAlias(true); + setFocusable(false); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java index 7ea72c79501d..47a10547688b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.phone; import android.annotation.IntDef; +import android.content.ComponentCallbacks; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Point; @@ -25,6 +27,8 @@ import android.net.Uri; import android.os.Handler; import android.provider.Settings; +import com.android.systemui.shared.system.QuickStepContract; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -33,7 +37,7 @@ import java.lang.annotation.RetentionPolicy; * prototypes to run in the system. The class will handle communication changes from the settings * app and call back to listeners. */ -public class NavigationPrototypeController extends ContentObserver { +public class NavigationPrototypeController extends ContentObserver implements ComponentCallbacks { private static final String HIDE_BACK_BUTTON_SETTING = "quickstepcontroller_hideback"; private static final String HIDE_HOME_BUTTON_SETTING = "quickstepcontroller_hidehome"; private static final String PROTOTYPE_ENABLED = "prototype_enabled"; @@ -85,9 +89,9 @@ public class NavigationPrototypeController extends ContentObserver { registerObserver(HIDE_HOME_BUTTON_SETTING); registerObserver(GESTURE_MATCH_SETTING); registerObserver(NAV_COLOR_ADAPT_ENABLE_SETTING); - registerObserver(EDGE_SENSITIVITY_WIDTH_SETTING); registerObserver(SHOW_HOME_HANDLE_SETTING); registerObserver(ENABLE_ASSISTANT_GESTURE); + mContext.registerComponentCallbacks(this); } /** @@ -95,6 +99,7 @@ public class NavigationPrototypeController extends ContentObserver { */ public void unregister() { mContext.getContentResolver().unregisterContentObserver(this); + mContext.unregisterComponentCallbacks(this); } @Override @@ -115,9 +120,6 @@ public class NavigationPrototypeController extends ContentObserver { } else if (path.endsWith(NAV_COLOR_ADAPT_ENABLE_SETTING)) { mListener.onColorAdaptChanged( NavBarTintController.isEnabled(mContext)); - } else if (path.endsWith(EDGE_SENSITIVITY_WIDTH_SETTING)) { - mListener.onEdgeSensitivityChanged(getEdgeSensitivityWidth(), - getEdgeSensitivityHeight()); } else if (path.endsWith(SHOW_HOME_HANDLE_SETTING)) { mListener.onHomeHandleVisiblilityChanged(showHomeHandle()); } else if (path.endsWith(ENABLE_ASSISTANT_GESTURE)) { @@ -130,8 +132,7 @@ public class NavigationPrototypeController extends ContentObserver { * @return the width for edge swipe */ public int getEdgeSensitivityWidth() { - // TODO: Move into resource - return convertDpToPixel(getGlobalInt(EDGE_SENSITIVITY_WIDTH_SETTING, 48)); + return QuickStepContract.getEdgeSensitivityWidth(mContext); } /** @@ -203,6 +204,18 @@ public class NavigationPrototypeController extends ContentObserver { return (int) (dp * Resources.getSystem().getDisplayMetrics().density); } + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (mListener != null) { + mListener.onEdgeSensitivityChanged(getEdgeSensitivityWidth(), + getEdgeSensitivityHeight()); + } + } + + @Override + public void onLowMemory() { + } + public interface OnPrototypeChangedListener { void onGestureRemap(@GestureAction int[] actions); void onBackButtonVisibilityChanged(boolean visible); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index b7a78738828e..183fdb46a795 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -201,7 +201,7 @@ public class PhoneStatusBarPolicy mIconController.setIconVisibility(mSlotAlarmClock, false); // zen - mIconController.setIcon(mSlotZen, R.drawable.stat_sys_zen_important, null); + mIconController.setIcon(mSlotZen, R.drawable.stat_sys_dnd, null); mIconController.setIconVisibility(mSlotZen, false); // volume @@ -339,11 +339,11 @@ public class PhoneStatusBarPolicy zenDescription = mContext.getString(R.string.quick_settings_dnd_label); } else if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) { zenVisible = true; - zenIconId = R.drawable.stat_sys_zen_none; + zenIconId = R.drawable.stat_sys_dnd; zenDescription = mContext.getString(R.string.interruption_level_none); } else if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { zenVisible = true; - zenIconId = R.drawable.stat_sys_zen_important; + zenIconId = R.drawable.stat_sys_dnd; zenDescription = mContext.getString(R.string.interruption_level_priority); } @@ -388,13 +388,12 @@ public class PhoneStatusBarPolicy } private final void updateBluetooth() { - int iconId = R.drawable.stat_sys_data_bluetooth; + int iconId = R.drawable.stat_sys_data_bluetooth_connected; String contentDescription = mContext.getString(R.string.accessibility_quick_settings_bluetooth_on); boolean bluetoothVisible = false; if (mBluetooth != null) { if (mBluetooth.isBluetoothConnected()) { - iconId = R.drawable.stat_sys_data_bluetooth_connected; contentDescription = mContext.getString(R.string.accessibility_bluetooth_connected); bluetoothVisible = mBluetooth.isBluetoothEnabled(); } @@ -582,8 +581,8 @@ public class PhoneStatusBarPolicy String contentDescription = mContext.getString(hasMic ? R.string.accessibility_status_bar_headset : R.string.accessibility_status_bar_headphones); - mIconController.setIcon(mSlotHeadset, hasMic ? R.drawable.ic_headset_mic - : R.drawable.ic_headset, contentDescription); + mIconController.setIcon(mSlotHeadset, hasMic ? R.drawable.stat_sys_headset_mic + : R.drawable.stat_sys_headset, contentDescription); mIconController.setIconVisibility(mSlotHeadset, true); } else { mIconController.setIconVisibility(mSlotHeadset, false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java index 25cb7d0573da..8053ec7e5838 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java @@ -29,7 +29,6 @@ import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE; import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW; import static com.android.systemui.statusbar.phone.NavigationBarView.WINDOW_TARGET_BOTTOM; -import static com.android.systemui.statusbar.phone.NavigationPrototypeController.EDGE_SENSITIVITY_WIDTH_SETTING; import android.annotation.Nullable; import android.content.Context; @@ -264,10 +263,7 @@ public class QuickStepController implements GestureHelper { mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix); mAllowGestureDetection = true; mNotificationsVisibleOnDown = !mNavigationBarView.isNotificationsFullyCollapsed(); - final int defaultRegionThreshold = mContext.getResources() - .getDimensionPixelOffset(R.dimen.navigation_bar_default_edge_width); - mGestureRegionThreshold = convertDpToPixel(getIntGlobalSetting(mContext, - EDGE_SENSITIVITY_WIDTH_SETTING, defaultRegionThreshold)); + mGestureRegionThreshold = QuickStepContract.getEdgeSensitivityWidth(mContext); break; } case MotionEvent.ACTION_MOVE: { @@ -357,7 +353,7 @@ public class QuickStepController implements GestureHelper { if (mCurrentAction != null) { mCurrentAction.endGesture(); } - } else if (QuickStepContract.isGesturalMode(mContext) + } else if (QuickStepContract.isNavBarClickThrough(mContext) && !mClickThroughPressed) { // Enable click through functionality where no gesture has been detected and // not passed the drag slop so inject a touch event at the same location diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 1949badf0513..0d2fe13b9a09 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -92,7 +92,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo /** * Scrim opacity when the phone is about to wake-up. */ - public static final float AOD2_SCRIM_ALPHA = 0.6f; + public static final float WAKE_SENSOR_SCRIM_ALPHA = 0.6f; /** * A scrim varies its opacity based on a busyness factor, for example * how many notifications are currently visible. @@ -458,6 +458,23 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo mState.AOD.setAodFrontScrimAlpha(alpha); } + /** + * If the lock screen sensor is active. + */ + public void setWakeLockScreenSensorActive(boolean active) { + for (ScrimState state : ScrimState.values()) { + state.setWakeLockScreenSensorActive(active); + } + + if (mState == ScrimState.PULSING) { + float newBehindAlpha = mState.getBehindAlpha(); + if (mCurrentBehindAlpha != newBehindAlpha) { + mCurrentBehindAlpha = newBehindAlpha; + updateScrims(); + } + } + } + protected void scheduleUpdate() { if (mUpdatePending) return; @@ -904,10 +921,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo } } - public void setPulseReason(int pulseReason) { - ScrimState.PULSING.setPulseReason(pulseReason); - } - public interface Callback { default void onStart() { } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 2f161d5cd192..d152ecd8b930 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.phone; import android.graphics.Color; import android.os.Trace; -import com.android.systemui.doze.DozeLog; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; @@ -129,16 +128,15 @@ public enum ScrimState { @Override public void prepare(ScrimState previousState) { mCurrentInFrontAlpha = 0f; - if (mPulseReason == DozeLog.PULSE_REASON_NOTIFICATION - || mPulseReason == DozeLog.PULSE_REASON_DOCKING - || mPulseReason == DozeLog.PULSE_REASON_INTENT) { - mCurrentBehindAlpha = previousState.getBehindAlpha(); - } else { - mCurrentBehindAlpha = ScrimController.AOD2_SCRIM_ALPHA; - } mCurrentBehindTint = Color.BLACK; mBlankScreen = mDisplayRequiresBlanking; } + + @Override + public float getBehindAlpha() { + return mWakeLockScreenSensorActive ? ScrimController.WAKE_SENSOR_SCRIM_ALPHA + : AOD.getBehindAlpha(); + } }, /** @@ -199,7 +197,7 @@ public enum ScrimState { int mIndex; boolean mHasBackdrop; boolean mLaunchingAffordanceWithPreview; - int mPulseReason; + boolean mWakeLockScreenSensorActive; ScrimState(int index) { mIndex = index; @@ -264,10 +262,6 @@ public enum ScrimState { mAodFrontScrimAlpha = aodFrontScrimAlpha; } - public void setPulseReason(int pulseReason) { - mPulseReason = pulseReason; - } - public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) { mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; } @@ -287,4 +281,8 @@ public enum ScrimState { public void setHasBackdrop(boolean hasBackdrop) { mHasBackdrop = hasBackdrop; } + + public void setWakeLockScreenSensorActive(boolean active) { + mWakeLockScreenSensorActive = active; + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 5d52359480e6..7e0623250c31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -25,6 +25,7 @@ import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; +import static com.android.systemui.Dependency.BG_HANDLER; import static com.android.systemui.Dependency.MAIN_HANDLER; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; @@ -434,6 +435,9 @@ public class StatusBar extends SystemUI implements DemoMode, public void onUserSetupChanged() { final boolean userSetup = mDeviceProvisionedController.isUserSetup( mDeviceProvisionedController.getCurrentUser()); + // STOPSHIP(kozynski, b/129405675) Remove log + Log.d(TAG, "mUserSetupObserver - DeviceProvisionedListener called for user " + + mDeviceProvisionedController.getCurrentUser()); if (MULTIUSER_DEBUG) { Log.d(TAG, String.format("User setup changed: userSetup=%s mUserSetup=%s", userSetup, mUserSetup)); @@ -1073,7 +1077,7 @@ public class StatusBar extends SystemUI implements DemoMode, mLockscreenUserManager, shadeController, mKeyguardMonitor, mNotificationInterruptionStateProvider, mMetricsLogger, new LockPatternUtils(mContext), Dependency.get(MAIN_HANDLER), - mActivityIntentHelper); + Dependency.get(BG_HANDLER), mActivityIntentHelper, mBubbleController); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); @@ -1297,13 +1301,16 @@ public class StatusBar extends SystemUI implements DemoMode, * the user intends to use the lock screen user switcher, QS in not needed. */ private void updateQsExpansionEnabled() { - mNotificationPanel.setQsExpansionEnabled(mDeviceProvisionedController.isDeviceProvisioned() + final boolean expandEnabled = mDeviceProvisionedController.isDeviceProvisioned() && (mUserSetup || mUserSwitcherController == null || !mUserSwitcherController.isSimpleUserSwitcher()) && ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0) && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0) && !mDozing - && !ONLY_CORE_APPS); + && !ONLY_CORE_APPS; + mNotificationPanel.setQsExpansionEnabled(expandEnabled); + // STOPSHIP(kozynski, b/129405675) Remove log + Log.d(TAG, "updateQsExpansionEnabled - QS Expand enabled: " + expandEnabled); } public void addQsTile(ComponentName tile) { @@ -3902,7 +3909,6 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) { - mScrimController.setPulseReason(reason); if (reason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS) { mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:LONG_PRESS"); @@ -3910,6 +3916,10 @@ public class StatusBar extends SystemUI implements DemoMode, return; } + if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) { + mScrimController.setWakeLockScreenSensorActive(true); + } + boolean passiveAuthInterrupt = reason == DozeLog.PULSE_REASON_NOTIFICATION; // Set the state to pulsing, so ScrimController will know what to do once we ask it to // execute the transition. The pulse callback will then be invoked when the scrims @@ -3928,6 +3938,7 @@ public class StatusBar extends SystemUI implements DemoMode, mPulsing = false; callback.onPulseFinished(); updateNotificationPanelTouchState(); + mScrimController.setWakeLockScreenSensorActive(false); setPulsing(false); } @@ -4004,7 +4015,10 @@ public class StatusBar extends SystemUI implements DemoMode, } @Override - public void extendPulse() { + public void extendPulse(int reason) { + if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) { + mScrimController.setWakeLockScreenSensorActive(true); + } if (mDozeScrimController.isPulsing() && mAmbientPulseManager.hasNotifications()) { mAmbientPulseManager.extendPulse(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index cbaabf719985..3b32d95262cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -760,9 +760,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public boolean bouncerNeedsScrimming() { - return mOccluded || mBouncer.willDismissWithAction() || mBouncer.needsFullscreenBouncer() + return mOccluded || mBouncer.willDismissWithAction() || mStatusBar.isFullScreenUserSwitcherState() - || (mBouncer.isShowing() && mBouncer.isScrimmed()); + || (mBouncer.isShowing() && mBouncer.isScrimmed()) + || mBouncer.isFullscreenBouncer(); } public void dump(PrintWriter pw) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 215f5c4dfc5b..e4af15cf7dd1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -48,6 +48,7 @@ import com.android.systemui.Dependency; import com.android.systemui.EventLogTags; import com.android.systemui.UiOffloadThread; import com.android.systemui.assist.AssistManager; +import com.android.systemui.bubbles.BubbleController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; @@ -98,7 +99,9 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit private final CommandQueue mCommandQueue; private final IDreamManager mDreamManager; private final Handler mMainThreadHandler; + private final Handler mBackgroundHandler; private final ActivityIntentHelper mActivityIntentHelper; + private final BubbleController mBubbleController; private boolean mIsCollapsingToShowActivityOverLockscreen; @@ -125,7 +128,9 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils, Handler mainThreadHandler, - ActivityIntentHelper activityIntentHelper) { + Handler backgroundHandler, + ActivityIntentHelper activityIntentHelper, + BubbleController bubbleController) { mContext = context; mNotificationPanel = panel; mPresenter = presenter; @@ -147,6 +152,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mAssistManager = assistManager; mGroupManager = groupManager; mLockPatternUtils = lockPatternUtils; + mBackgroundHandler = backgroundHandler; mEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override public void onPendingEntryAdded(NotificationEntry entry) { @@ -156,6 +162,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mStatusBarRemoteInputCallback = remoteInputCallback; mMainThreadHandler = mainThreadHandler; mActivityIntentHelper = activityIntentHelper; + mBubbleController = bubbleController; } /** @@ -178,14 +185,24 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit final PendingIntent intent = notification.contentIntent != null ? notification.contentIntent : notification.fullScreenIntent; + final boolean isBubble = row.getEntry().isBubble(); + + // This code path is now executed for notification without a contentIntent. + // The only valid case is Bubble notifications. Guard against other cases + // entering here. + if (intent == null && !isBubble) { + Log.e(TAG, "onNotificationClicked called for non-clickable notification!"); + return; + } + final String notificationKey = sbn.getKey(); - boolean isActivityIntent = intent.isActivity(); + boolean isActivityIntent = intent != null && intent.isActivity() && !isBubble; final boolean afterKeyguardGone = isActivityIntent && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(), mLockscreenUserManager.getCurrentUserId()); final boolean wasOccluded = mShadeController.isOccluded(); - boolean showOverLockscreen = mKeyguardMonitor.isShowing() + boolean showOverLockscreen = mKeyguardMonitor.isShowing() && intent != null && mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(), mLockscreenUserManager.getCurrentUserId()); ActivityStarter.OnDismissAction postKeyguardAction = @@ -244,9 +261,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mShadeController.addAfterKeyguardGoneRunnable(runnable); mShadeController.collapsePanel(); } else { - new Thread(runnable).start(); + mBackgroundHandler.postAtFrontOfQueue(runnable); } - return !mNotificationPanel.isFullyCollapsed(); } @@ -287,6 +303,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } Intent fillInIntent = null; NotificationEntry entry = row.getEntry(); + final boolean isBubble = entry.isBubble(); CharSequence remoteInputText = null; if (!TextUtils.isEmpty(entry.remoteInputText)) { remoteInputText = entry.remoteInputText; @@ -295,8 +312,12 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit fillInIntent = new Intent().putExtra(Notification.EXTRA_REMOTE_INPUT_DRAFT, remoteInputText.toString()); } - startNotificationIntent(intent, fillInIntent, row, wasOccluded, isActivityIntent); - if (isActivityIntent) { + if (isBubble) { + expandBubbleStackOnMainThread(notificationKey); + } else { + startNotificationIntent(intent, fillInIntent, row, wasOccluded, isActivityIntent); + } + if (isActivityIntent || isBubble) { mAssistManager.hideAssist(); } if (shouldCollapse()) { @@ -316,18 +337,29 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } catch (RemoteException ex) { // system process is dead if we're here. } - if (parentToCancelFinal != null) { - removeNotification(parentToCancelFinal); - } - if (shouldAutoCancel(sbn) - || mRemoteInputManager.isNotificationKeptForRemoteInputHistory( - notificationKey)) { - // Automatically remove all notifications that we may have kept around longer - removeNotification(sbn); + if (!isBubble) { + if (parentToCancelFinal != null) { + removeNotification(parentToCancelFinal); + } + if (shouldAutoCancel(sbn) + || mRemoteInputManager.isNotificationKeptForRemoteInputHistory( + notificationKey)) { + // Automatically remove all notifications that we may have kept around longer + removeNotification(sbn); + } } mIsCollapsingToShowActivityOverLockscreen = false; } + private void expandBubbleStackOnMainThread(String notificationKey) { + if (Looper.getMainLooper().isCurrentThread()) { + mBubbleController.expandStackAndSelectBubble(notificationKey); + } else { + mMainThreadHandler.post( + () -> mBubbleController.expandStackAndSelectBubble(notificationKey)); + } + } + private void startNotificationIntent(PendingIntent intent, Intent fillInIntent, ExpandableNotificationRow row, boolean wasOccluded, boolean isActivityIntent) { RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(row, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java index f5e745f650a9..db2523e40ded 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java @@ -21,9 +21,10 @@ import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.net.Uri; +import android.os.Handler; import android.provider.Settings.Global; import android.provider.Settings.Secure; -import android.os.Handler; +import android.util.Log; import com.android.systemui.settings.CurrentUserTracker; @@ -39,6 +40,7 @@ import javax.inject.Singleton; public class DeviceProvisionedControllerImpl extends CurrentUserTracker implements DeviceProvisionedController { + private static final String TAG = DeviceProvisionedControllerImpl.class.getSimpleName(); private final ArrayList<DeviceProvisionedListener> mListeners = new ArrayList<>(); private final ContentResolver mContentResolver; private final Context mContext; @@ -59,6 +61,8 @@ public class DeviceProvisionedControllerImpl extends CurrentUserTracker implemen mSettingsObserver = new ContentObserver(mainHandler) { @Override public void onChange(boolean selfChange, Uri uri, int userId) { + // STOPSHIP(kozynski, b/129405675) Remove log + Log.d(TAG, "Setting change: " + uri); if (mUserSetupUri.equals(uri)) { notifySetupChanged(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index e5f709a995e5..35a245000628 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -288,7 +288,7 @@ public class VolumeDialogImpl implements VolumeDialog, addRow(AudioManager.STREAM_RING, R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false); addRow(STREAM_ALARM, - R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, true, false); + R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false); addRow(AudioManager.STREAM_VOICE_CALL, com.android.internal.R.drawable.ic_phone, com.android.internal.R.drawable.ic_phone, false, false); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java index 267468ffa4d6..f03c234ac3bb 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java @@ -27,10 +27,13 @@ import android.view.ViewGroup; import android.widget.TextView; import com.android.systemui.SysuiTestCase; +import com.android.systemui.colorextraction.SysuiColorExtractor; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -38,12 +41,15 @@ import org.junit.runner.RunWith; public final class BubbleClockControllerTest extends SysuiTestCase { private BubbleClockController mClockController; + @Mock SysuiColorExtractor mMockColorExtractor; @Before public void setUp() { + MockitoAnnotations.initMocks(this); + Resources res = getContext().getResources(); LayoutInflater layoutInflater = LayoutInflater.from(getContext()); - mClockController = new BubbleClockController(res, layoutInflater); + mClockController = new BubbleClockController(res, layoutInflater, mMockColorExtractor); } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/StretchAnalogClockControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/StretchAnalogClockControllerTest.java index 0659b4fe71cd..26fa62b77d9a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/clock/StretchAnalogClockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/StretchAnalogClockControllerTest.java @@ -27,10 +27,13 @@ import android.view.ViewGroup; import android.widget.TextView; import com.android.systemui.SysuiTestCase; +import com.android.systemui.colorextraction.SysuiColorExtractor; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -38,12 +41,16 @@ import org.junit.runner.RunWith; public final class StretchAnalogClockControllerTest extends SysuiTestCase { private StretchAnalogClockController mClockController; + @Mock SysuiColorExtractor mMockColorExtractor; @Before public void setUp() { + MockitoAnnotations.initMocks(this); + Resources res = getContext().getResources(); LayoutInflater layoutInflater = LayoutInflater.from(getContext()); - mClockController = new StretchAnalogClockController(res, layoutInflater); + mClockController = new StretchAnalogClockController(res, layoutInflater, + mMockColorExtractor); } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ViewPreviewerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/clock/ViewPreviewerTest.kt new file mode 100644 index 000000000000..d9ef7fa34883 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ViewPreviewerTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard.clock + +import android.content.Context +import com.google.common.truth.Truth.assertThat + +import android.graphics.Canvas +import android.graphics.Color +import android.testing.AndroidTestingRunner +import android.view.View +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class ViewPreviewerTest : SysuiTestCase() { + + private lateinit var previewer: ViewPreviewer + private lateinit var view: View + + @Before + fun setUp() { + previewer = ViewPreviewer() + view = TestView(context) + } + + @Test + fun testCreatePreview() { + val width = 100 + val height = 100 + // WHEN a preview image is created + val bitmap = previewer.createPreview(view, width, height) + // THEN the bitmap has the expected width and height + assertThat(bitmap.height).isEqualTo(height) + assertThat(bitmap.width).isEqualTo(width) + assertThat(bitmap.getPixel(0, 0)).isEqualTo(Color.RED) + } + + class TestView(context: Context) : View(context) { + override fun onDraw(canvas: Canvas?) { + super.onDraw(canvas) + canvas?.drawColor(Color.RED) + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 14bc71b6a142..9fa85d307d2a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -438,6 +438,22 @@ public class BubbleControllerTest extends SysuiTestCase { } @Test + public void testExpandStackAndSelectBubble_removedFirst() { + final String key = mRow.getEntry().key; + + mEntryListener.onPendingEntryAdded(mRow.getEntry()); + mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */); + + assertTrue(mRow.getEntry().isBubble()); + + // Simulate notification cancellation. + mEntryListener.onEntryRemoved(mRow.getEntry(), null /* notificationVisibility (unused) */, + false /* removedbyUser */); + + mBubbleController.expandStackAndSelectBubble(key); + } + + @Test public void testMarkNewNotificationAsBubble() { mEntryListener.onPendingEntryAdded(mRow.getEntry()); assertTrue(mRow.getEntry().isBubble()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java index dc4287287b03..abfa755671db 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java @@ -86,7 +86,7 @@ class DozeHostFake implements DozeHost { } @Override - public void extendPulse() { + public void extendPulse(int reason) { pulseExtended = true; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java index cde3398f91a6..392c677b9827 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java @@ -237,6 +237,18 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { } @Test + public void screenOff_softBlanks() throws Exception { + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mScreen.transitionTo(INITIALIZED, DOZE_AOD); + mScreen.transitionTo(DOZE_AOD, DOZE); + assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */); + + mScreen.transitionTo(DOZE, DOZE_AOD); + mSensor.sendSensorEvent(2); + assertEquals(0f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */); + } + + @Test public void pausingAod_unblanksAfterSensor() throws Exception { mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java index beba905279aa..87ae85f6d302 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java @@ -45,12 +45,11 @@ public class DozeWallpaperStateTest extends SysuiTestCase { private DozeWallpaperState mDozeWallpaperState; @Mock IWallpaperManager mIWallpaperManager; @Mock DozeParameters mDozeParameters; - @Mock DozeMachine mMachine; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mDozeWallpaperState = new DozeWallpaperState(mMachine, mIWallpaperManager, mDozeParameters); + mDozeWallpaperState = new DozeWallpaperState(mIWallpaperManager, mDozeParameters); } @Test @@ -110,28 +109,18 @@ public class DozeWallpaperStateTest extends SysuiTestCase { } @Test - public void testTransitionTo_notificationPulseIsAmbientMode() throws RemoteException { - when(mMachine.getPulseReason()).thenReturn(DozeLog.PULSE_REASON_NOTIFICATION); - mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_REQUEST_PULSE, - DozeMachine.State.DOZE_PULSING); - verify(mIWallpaperManager).setInAmbientMode(eq(true), eq(0L)); - } - - @Test public void testTransitionTo_wakeFromPulseIsNotAmbientMode() throws RemoteException { - when(mMachine.getPulseReason()).thenReturn(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN); mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_AOD, DozeMachine.State.DOZE_REQUEST_PULSE); reset(mIWallpaperManager); mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_REQUEST_PULSE, - DozeMachine.State.DOZE_PULSING); + DozeMachine.State.DOZE_PULSING_BRIGHT); verify(mIWallpaperManager).setInAmbientMode(eq(false), anyLong()); } @Test public void testTransitionTo_animatesWhenWakingUpFromPulse() throws RemoteException { - when(mMachine.getPulseReason()).thenReturn(DozeLog.PULSE_REASON_NOTIFICATION); mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_REQUEST_PULSE, DozeMachine.State.DOZE_PULSING); reset(mIWallpaperManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java index f51e4731a390..5928a07487d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -265,6 +265,12 @@ public class PowerUITest extends SysuiTestCase { state.mIsPowerSaver = true; shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); assertThat(shouldShow).isFalse(); + + state.mIsPowerSaver = false; + // if disabled we should not show the low warning. + state.mIsLowLevelWarningEnabled = false; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isFalse(); } @Test @@ -365,7 +371,7 @@ public class PowerUITest extends SysuiTestCase { assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_HYBRID_THRESHOLD); BatteryStateSnapshot snapshot = new BatteryStateSnapshot( BATTERY_LEVEL_10, false, false, 0, BatteryManager.BATTERY_HEALTH_GOOD, - 0, 0, -1, 0, 0, false); + 0, 0, -1, 0, 0, false, true); mPowerUI.mLastBatteryStateSnapshot = snapshot; // query again since the estimate was -1 @@ -375,7 +381,7 @@ public class PowerUITest extends SysuiTestCase { assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_SEVERE_HYBRID_THRESHOLD); snapshot = new BatteryStateSnapshot( BATTERY_LEVEL_10, false, false, 0, BatteryManager.BATTERY_HEALTH_GOOD, 0, - 0, BELOW_SEVERE_HYBRID_THRESHOLD, 0, 0, false); + 0, BELOW_SEVERE_HYBRID_THRESHOLD, 0, 0, false, true); mPowerUI.mLastBatteryStateSnapshot = snapshot; // Battery level hasn't changed, so we don't query again @@ -536,13 +542,14 @@ public class PowerUITest extends SysuiTestCase { public long mTimeRemainingMillis = Duration.ofHours(24).toMillis(); public boolean mIsBasedOnUsage = true; public boolean mIsHybrid = true; + public boolean mIsLowLevelWarningEnabled = true; public BatteryStateSnapshot get() { if (mIsHybrid) { return new BatteryStateSnapshot(mBatteryLevel, mIsPowerSaver, mPlugged, mBucket, mBatteryStatus, mSevereLevelThreshold, mLowLevelThreshold, mTimeRemainingMillis, mSevereThresholdMillis, mLowThresholdMillis, - mIsBasedOnUsage); + mIsBasedOnUsage, mIsLowLevelWarningEnabled); } else { return new BatteryStateSnapshot(mBatteryLevel, mIsPowerSaver, mPlugged, mBucket, mBatteryStatus, mSevereLevelThreshold, mLowLevelThreshold); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java index fd31013db429..47933ba9fdaa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java @@ -194,7 +194,7 @@ public class QSSecurityFooterTest extends SysuiTestCase { VPN_PACKAGE), mFooterText.getText()); assertEquals(View.VISIBLE, mFooterIcon.getVisibility()); - assertEquals(R.drawable.ic_qs_vpn, mFooterIcon.getLastImageResource()); + assertEquals(R.drawable.stat_sys_vpn_ic, mFooterIcon.getLastImageResource()); // Same situation, but with organization name set when(mSecurityController.getDeviceOwnerOrganizationName()) @@ -220,7 +220,7 @@ public class QSSecurityFooterTest extends SysuiTestCase { assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_vpns), mFooterText.getText()); assertEquals(View.VISIBLE, mFooterIcon.getVisibility()); - assertEquals(R.drawable.ic_qs_vpn, mFooterIcon.getLastImageResource()); + assertEquals(R.drawable.stat_sys_vpn_ic, mFooterIcon.getLastImageResource()); // Same situation, but with organization name set when(mSecurityController.getDeviceOwnerOrganizationName()) @@ -243,7 +243,7 @@ public class QSSecurityFooterTest extends SysuiTestCase { TestableLooper.get(this).processAllMessages(); assertEquals(View.VISIBLE, mFooterIcon.getVisibility()); - assertEquals(R.drawable.ic_qs_vpn, mFooterIcon.getLastImageResource()); + assertEquals(R.drawable.stat_sys_vpn_ic, mFooterIcon.getLastImageResource()); assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring), mFooterText.getText()); } @@ -294,7 +294,7 @@ public class QSSecurityFooterTest extends SysuiTestCase { mFooter.refreshState(); TestableLooper.get(this).processAllMessages(); - assertEquals(R.drawable.ic_qs_vpn, mFooterIcon.getLastImageResource()); + assertEquals(R.drawable.stat_sys_vpn_ic, mFooterIcon.getLastImageResource()); assertEquals(mContext.getString(R.string.quick_settings_disclosure_vpns), mFooterText.getText()); } @@ -306,7 +306,7 @@ public class QSSecurityFooterTest extends SysuiTestCase { mFooter.refreshState(); TestableLooper.get(this).processAllMessages(); - assertEquals(R.drawable.ic_qs_vpn, mFooterIcon.getLastImageResource()); + assertEquals(R.drawable.stat_sys_vpn_ic, mFooterIcon.getLastImageResource()); assertEquals(mContext.getString( R.string.quick_settings_disclosure_managed_profile_named_vpn, VPN_PACKAGE_2), @@ -320,7 +320,7 @@ public class QSSecurityFooterTest extends SysuiTestCase { mFooter.refreshState(); TestableLooper.get(this).processAllMessages(); - assertEquals(R.drawable.ic_qs_vpn, mFooterIcon.getLastImageResource()); + assertEquals(R.drawable.stat_sys_vpn_ic, mFooterIcon.getLastImageResource()); assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_vpn, VPN_PACKAGE), mFooterText.getText()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index 8c5f6f24ee79..7e089a653b9e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -151,6 +151,16 @@ public class NotificationTestHelper { /** * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. + */ + public ExpandableNotificationRow createBubble() + throws Exception { + Notification n = createNotification(false /* isGroupSummary */, + null /* groupKey */, makeBubbleMetadata(null)); + return generateRow(n, PKG, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH); + } + + /** + * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. * * @param deleteIntent the intent to assign to {@link BubbleMetadata#deleteIntent} */ @@ -212,7 +222,7 @@ public class NotificationTestHelper { * * @return a notification with no special properties */ - private Notification createNotification() { + public Notification createNotification() { return createNotification(false /* isGroupSummary */, null /* groupKey */); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 0aa103fa27da..8077e3fbaa0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -37,7 +37,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.AppOpsManager; +import android.app.Notification; import android.app.NotificationChannel; +import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -355,4 +357,30 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { mGroupRow.setUserExpanded(true); Assert.assertTrue(mGroupRow.isExpanded()); } + + @Test + public void testGetIsNonblockable() throws Exception { + ExpandableNotificationRow row = + mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification()); + + assertFalse(row.getIsNonblockable()); + } + + @Test + public void testGetIsNonblockable_oemLocked() throws Exception { + ExpandableNotificationRow row = + mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification()); + row.getEntry().channel.setImportanceLockedByOEM(true); + + assertTrue(row.getIsNonblockable()); + } + + @Test + public void testGetIsNonblockable_criticalDeviceFunction() throws Exception { + ExpandableNotificationRow row = + mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification()); + row.getEntry().channel.setImportanceLockedByCriticalDeviceFunction(true); + + assertTrue(row.getIsNonblockable()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 539851f45153..191c983ad156 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -46,7 +46,6 @@ import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.util.function.TriConsumer; import com.android.systemui.SysuiTestCase; -import com.android.systemui.doze.DozeLog; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.util.wakelock.WakeLock; import com.android.systemui.utils.os.FakeHandler; @@ -140,7 +139,6 @@ public class ScrimControllerTest extends SysuiTestCase { assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT); // Pulsing notification should conserve AOD wallpaper. - mScrimController.setPulseReason(DozeLog.PULSE_REASON_NOTIFICATION); mScrimController.transitionTo(ScrimState.PULSING); mScrimController.finishAnimationsImmediately(); assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT); @@ -225,14 +223,17 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.finishAnimationsImmediately(); assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE); - mScrimController.setPulseReason(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN); mScrimController.transitionTo(ScrimState.PULSING); mScrimController.finishAnimationsImmediately(); // Front scrim should be transparent // Back scrim should be semi-transparent so the user can see the wallpaper // Pulse callback should have been invoked - assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT); + assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE); assertScrimTint(mScrimBehind, true /* tinted */); + + mScrimController.setWakeLockScreenSensorActive(true); + mScrimController.finishAnimationsImmediately(); + assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT); } @Test @@ -486,7 +487,6 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testHoldsPulsingWallpaperAnimationLock() { // Pre-conditions - mScrimController.setPulseReason(DozeLog.PULSE_REASON_NOTIFICATION); mScrimController.transitionTo(ScrimState.PULSING); mScrimController.finishAnimationsImmediately(); reset(mWakeLock); @@ -508,30 +508,6 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test - public void testWillHidePulsingWallpaper_whenNotification() { - mScrimController.setWallpaperSupportsAmbientMode(false); - mScrimController.transitionTo(ScrimState.AOD); - mScrimController.finishAnimationsImmediately(); - mScrimController.setPulseReason(DozeLog.PULSE_REASON_NOTIFICATION); - mScrimController.transitionTo(ScrimState.PULSING); - mScrimController.finishAnimationsImmediately(); - assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE); - assertScrimTint(mScrimBehind, true); - } - - @Test - public void testWillHidePulsingWallpaper_whenDocking() { - mScrimController.setWallpaperSupportsAmbientMode(false); - mScrimController.transitionTo(ScrimState.AOD); - mScrimController.finishAnimationsImmediately(); - mScrimController.setPulseReason(DozeLog.PULSE_REASON_DOCKING); - mScrimController.transitionTo(ScrimState.PULSING); - mScrimController.finishAnimationsImmediately(); - assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE); - assertScrimTint(mScrimBehind, true); - } - - @Test public void testConservesExpansionOpacityAfterTransition() { mScrimController.transitionTo(ScrimState.UNLOCKED); mScrimController.setPanelExpansion(0.5f); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 20af1ac5a42f..41e82cbeac36 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -24,7 +24,10 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.app.KeyguardManager; @@ -49,6 +52,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.systemui.ActivityIntentHelper; import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.AssistManager; +import com.android.systemui.bubbles.BubbleController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; @@ -101,22 +105,23 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { private KeyguardMonitor mKeyguardMonitor; @Mock private Handler mHandler; + @Mock + private BubbleController mBubbleController; @Mock private ActivityIntentHelper mActivityIntentHelper; @Mock private PendingIntent mContentIntent; @Mock - private NotificationData mNotificationData; - @Mock - private NotificationEntry mNotificationEntry; + private Intent mContentIntentInner; @Mock - private NotificationEntry mBubbleEntry; + private NotificationData mNotificationData; private NotificationActivityStarter mNotificationActivityStarter; private NotificationTestHelper mNotificationTestHelper; - ExpandableNotificationRow mNotificationRow; + private ExpandableNotificationRow mNotificationRow; + private ExpandableNotificationRow mBubbleNotificationRow; private final Answer<Void> mCallOnDismiss = answerVoid( (ActivityStarter.OnDismissAction dismissAction, Runnable cancel, @@ -129,14 +134,32 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController); when(mEntryManager.getNotificationData()).thenReturn(mNotificationData); - mActiveNotifications = new ArrayList<>(); - mActiveNotifications.add(mNotificationEntry); - mActiveNotifications.add(mBubbleEntry); - when(mNotificationData.getActiveNotifications()).thenReturn(mActiveNotifications); - when(mNotificationEntry.getRow()).thenReturn(mNotificationRow); + when(mContentIntent.isActivity()).thenReturn(true); + when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1)); + when(mContentIntent.getIntent()).thenReturn(mContentIntentInner); mNotificationTestHelper = new NotificationTestHelper(mContext); + + // Create standard notification with contentIntent mNotificationRow = mNotificationTestHelper.createRow(); + StatusBarNotification sbn = mNotificationRow.getStatusBarNotification(); + sbn.getNotification().contentIntent = mContentIntent; + sbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL; + + // Create bubble notification row with contentIntent + mBubbleNotificationRow = mNotificationTestHelper.createBubble(); + StatusBarNotification bubbleSbn = mBubbleNotificationRow.getStatusBarNotification(); + bubbleSbn.getNotification().contentIntent = mContentIntent; + bubbleSbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL; + // Do what BubbleController's NotificationEntryListener#onPendingEntryAdded does: + mBubbleNotificationRow.getEntry().setIsBubble(true); + mBubbleNotificationRow.getEntry().setShowInShadeWhenBubble(true); + + mActiveNotifications = new ArrayList<>(); + mActiveNotifications.add(mNotificationRow.getEntry()); + mActiveNotifications.add(mBubbleNotificationRow.getEntry()); + when(mNotificationData.getActiveNotifications()).thenReturn(mActiveNotifications); + when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); mNotificationActivityStarter = new StatusBarNotificationActivityStarter(getContext(), mock(CommandQueue.class), mAssistManager, mock(NotificationPanelView.class), @@ -147,16 +170,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mock(StatusBarRemoteInputCallback.class), mock(NotificationGroupManager.class), mock(NotificationLockscreenUserManager.class), mShadeController, mKeyguardMonitor, mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class), - mock(LockPatternUtils.class), mHandler, mActivityIntentHelper); - - - when(mContentIntent.isActivity()).thenReturn(true); - when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1)); - - // SBNActivityStarter expects contentIntent or fullScreenIntent to be set - mNotificationRow.getEntry().notification.getNotification().contentIntent = mContentIntent; - - when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); + mock(LockPatternUtils.class), mHandler, mHandler, mActivityIntentHelper, + mBubbleController); // set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg doAnswer(mCallOnDismiss).when(mActivityStarter).dismissKeyguardThenExecute( @@ -173,33 +188,26 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // set up Handler to synchronously invoke the Runnable arg doAnswer(answerVoid(Runnable::run)) .when(mHandler).post(any(Runnable.class)); + + doAnswer(answerVoid(Runnable::run)) + .when(mHandler).postAtFrontOfQueue(any(Runnable.class)); } @Test - public void testOnNotificationClicked_whileKeyguardVisible() + public void testOnNotificationClicked_keyGuardShowing() throws PendingIntent.CanceledException, RemoteException { // Given + StatusBarNotification sbn = mNotificationRow.getStatusBarNotification(); + sbn.getNotification().contentIntent = mContentIntent; + sbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL; + when(mKeyguardMonitor.isShowing()).thenReturn(true); when(mShadeController.isOccluded()).thenReturn(true); - when(mContentIntent.isActivity()).thenReturn(true); - when(mActivityIntentHelper.wouldShowOverLockscreen(any(Intent.class), anyInt())) - .thenReturn(false); - when(mActivityIntentHelper.wouldLaunchResolverActivity(any(Intent.class), anyInt())) - .thenReturn(false); - - StatusBarNotification statusBarNotification = mNotificationRow.getEntry().notification; - statusBarNotification.getNotification().flags |= Notification.FLAG_AUTO_CANCEL; // When - mNotificationActivityStarter.onNotificationClicked(statusBarNotification, - mNotificationRow); + mNotificationActivityStarter.onNotificationClicked(sbn, mNotificationRow); // Then - verify(mActivityStarter).dismissKeyguardThenExecute( - any(ActivityStarter.OnDismissAction.class), - any() /* cancel */, - anyBoolean() /* afterKeyguardGone */); - verify(mShadeController, atLeastOnce()).collapsePanel(); verify(mContentIntent).sendAndReturnResult( @@ -214,9 +222,100 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { verify(mAssistManager).hideAssist(); verify(mStatusBarService).onNotificationClick( - eq(mNotificationRow.getEntry().key), any(NotificationVisibility.class)); + eq(sbn.getKey()), any(NotificationVisibility.class)); // Notification is removed due to FLAG_AUTO_CANCEL - verify(mEntryManager).performRemoveNotification(eq(statusBarNotification)); + verify(mEntryManager).performRemoveNotification(eq(sbn)); + } + + @Test + public void testOnNotificationClicked_bubble_noContentIntent_noKeyGuard() + throws RemoteException { + StatusBarNotification sbn = mBubbleNotificationRow.getStatusBarNotification(); + + // Given + sbn.getNotification().contentIntent = null; + + // When + mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow); + + // Then + verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey())); + + // This is called regardless, and simply short circuits when there is nothing to do. + verify(mShadeController, atLeastOnce()).collapsePanel(); + + verify(mAssistManager).hideAssist(); + + verify(mStatusBarService).onNotificationClick( + eq(sbn.getKey()), any(NotificationVisibility.class)); + + // The content intent should NOT be sent on click. + verifyZeroInteractions(mContentIntent); + + // Notification should not be cancelled. + verify(mEntryManager, never()).performRemoveNotification(eq(sbn)); + } + + @Test + public void testOnNotificationClicked_bubble_noContentIntent_keyGuardShowing() + throws RemoteException { + StatusBarNotification sbn = mBubbleNotificationRow.getStatusBarNotification(); + + // Given + sbn.getNotification().contentIntent = null; + when(mKeyguardMonitor.isShowing()).thenReturn(true); + when(mShadeController.isOccluded()).thenReturn(true); + + // When + mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow); + + // Then + verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey())); + + verify(mShadeController, atLeastOnce()).collapsePanel(); + + verify(mAssistManager).hideAssist(); + + verify(mStatusBarService).onNotificationClick( + eq(sbn.getKey()), any(NotificationVisibility.class)); + + // The content intent should NOT be sent on click. + verifyZeroInteractions(mContentIntent); + + // Notification should not be cancelled. + verify(mEntryManager, never()).performRemoveNotification(eq(sbn)); + } + + @Test + public void testOnNotificationClicked_bubble_withContentIntent_keyGuardShowing() + throws RemoteException { + StatusBarNotification sbn = mBubbleNotificationRow.getStatusBarNotification(); + + // Given + sbn.getNotification().contentIntent = mContentIntent; + when(mKeyguardMonitor.isShowing()).thenReturn(true); + when(mShadeController.isOccluded()).thenReturn(true); + + // When + mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow); + + // Then + verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey())); + + verify(mShadeController, atLeastOnce()).collapsePanel(); + + verify(mAssistManager).hideAssist(); + + verify(mStatusBarService).onNotificationClick( + eq(sbn.getKey()), any(NotificationVisibility.class)); + + // The content intent should NOT be sent on click. + verify(mContentIntent).getIntent(); + verify(mContentIntent).isActivity(); + verifyNoMoreInteractions(mContentIntent); + + // Notification should not be cancelled. + verify(mEntryManager, never()).performRemoveNotification(eq(sbn)); } } diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java index 72ce9c4efdc0..989470fdb2e9 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java @@ -29,6 +29,7 @@ import android.os.UserManager; import android.text.Html; import android.text.Html.ImageGetter; import android.util.Log; +import android.util.TypedValue; import android.view.View; import android.widget.Button; import android.widget.TextView; @@ -111,8 +112,16 @@ public class ConfirmDialog extends AlertActivity @Override public Drawable getDrawable(String source) { // Should only reach this when fetching the VPN icon for the warning string. - Drawable icon = getDrawable(R.drawable.ic_vpn_dialog); + final Drawable icon = getDrawable(R.drawable.ic_vpn_dialog); icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); + + final TypedValue tv = new TypedValue(); + if (getTheme().resolveAttribute(android.R.attr.textColorPrimary, tv, true)) { + icon.setTint(getColor(tv.resourceId)); + } else { + Log.w(TAG, "Unable to resolve theme color"); + } + return icon; } diff --git a/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml new file mode 100644 index 000000000000..5f7d519ca996 --- /dev/null +++ b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml @@ -0,0 +1,27 @@ +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="@*android:color/accent_device_default" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M17.12,7.38c0-2.96-2.41-5.38-5.37-5.38H11v7.94L6.03,4.97c-0.29-0.29-0.77-0.29-1.06,0s-0.29,0.77,0,1.06L10.94,12 l-5.97,5.97c-0.29,0.29-0.29,0.77,0,1.06c0.15,0.15,0.34,0.22,0.53,0.22s0.38-0.07,0.53-0.22L11,14.06V22h0.75 c2.96,0,5.37-2.41,5.37-5.38c0-1.97-1.06-3.69-2.64-4.62C16.06,11.06,17.12,9.34,17.12,7.38z M15.62,16.62 c0,1.88-1.34,3.45-3.12,3.8v-7.6C14.28,13.17,15.62,14.75,15.62,16.62z M12.5,11.18v-7.6c1.78,0.35,3.12,1.92,3.12,3.8 S14.28,10.83,12.5,11.18z" /> +</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_qs_airplane.xml b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_qs_airplane.xml new file mode 100644 index 000000000000..9743ceb327a7 --- /dev/null +++ b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_qs_airplane.xml @@ -0,0 +1,26 @@ +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="18dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M22,15.89v-2.57c0-1.1-0.65-2.09-1.67-2.53L14.5,8.28V3.75c0-1.38-1.12-2.5-2.5-2.5s-2.5,1.12-2.5,2.5v4.53l-5.83,2.51 C2.65,11.22,2,12.22,2,13.32v2.57l7.5-1.28v3.03l-1.47,1.17C7.38,19.34,7,20.12,7,20.96v1.33l4.96-0.3L17,22.3v-1.33 c0-0.84-0.38-1.62-1.03-2.15l-1.47-1.17v-3.03L22,15.89z M15.03,19.98c0.23,0.18,0.38,0.44,0.44,0.72l-3.52-0.2l-3.43,0.2 c0.06-0.28,0.21-0.53,0.44-0.72L11,18.36v-5.53l-7.5,1.28v-0.79c0-0.5,0.3-0.95,0.76-1.15L11,9.27V3.75c0-0.55,0.45-1,1-1 s1,0.45,1,1v5.52l6.74,2.9c0.46,0.2,0.76,0.65,0.76,1.15v0.79L13,12.83v5.53L15.03,19.98z" /> +</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_qs_auto_rotate.xml b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_qs_auto_rotate.xml new file mode 100644 index 000000000000..8dfa4a4f304c --- /dev/null +++ b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_qs_auto_rotate.xml @@ -0,0 +1,29 @@ +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M2.75,6.5v4.75H7.5c0.41,0,0.75-0.34,0.75-0.75S7.91,9.75,7.5,9.75H5.39l4.5-4.22c0.89-0.84,2.27-0.82,3.13,0.05l4.94,4.95 c0.29,0.29,0.77,0.29,1.06,0c0.29-0.29,0.29-0.77,0-1.06l-4.94-4.95c-1.44-1.44-3.73-1.48-5.22-0.08L4.25,8.77V6.5 c0-0.41-0.34-0.75-0.75-0.75S2.75,6.09,2.75,6.5z" /> + <path + android:fillColor="@android:color/white" + android:pathData="M21.25,17.5v-4.75H16.5c-0.41,0-0.75,0.34-0.75,0.75s0.34,0.75,0.75,0.75h2.11l-4.5,4.22c-0.89,0.84-2.27,0.82-3.13-0.05 l-4.94-4.95c-0.29-0.29-0.77-0.29-1.06,0c-0.29,0.29-0.29,0.77,0,1.06l4.94,4.95c0.74,0.74,1.69,1.11,2.65,1.11 c0.92,0,1.84-0.34,2.57-1.02l4.62-4.33v2.27c0,0.41,0.34,0.75,0.75,0.75S21.25,17.91,21.25,17.5z" /> +</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_invert_colors.xml b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_qs_bluetooth.xml index 0564c737c703..c12a2ebf4d9b 100644 --- a/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_invert_colors.xml +++ b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_qs_bluetooth.xml @@ -21,8 +21,6 @@ android:viewportHeight="24"> <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M17.44,7.71,12.7,3a1,1,0,0,0-1.41,0h0L6.56,7.71a8.21,8.21,0,0,0-0.62,11.1,8,8,0,0,0,12.12,0A8.21,8.21,0,0,0,17.44,7.71ZM12,19.59A6,6,0,0,1,7.76,9.35L12,5.1Z" /> + android:fillColor="@android:color/white" + android:pathData="M17.12,7.38c0-2.96-2.41-5.38-5.37-5.38H11v7.94L6.03,4.97c-0.29-0.29-0.77-0.29-1.06,0s-0.29,0.77,0,1.06L10.94,12 l-5.97,5.97c-0.29,0.29-0.29,0.77,0,1.06c0.15,0.15,0.34,0.22,0.53,0.22s0.38-0.07,0.53-0.22L11,14.06V22h0.75 c2.96,0,5.37-2.41,5.37-5.38c0-1.97-1.06-3.69-2.64-4.62C16.06,11.06,17.12,9.34,17.12,7.38z M15.62,16.62 c0,1.88-1.34,3.45-3.12,3.8v-7.6C14.28,13.17,15.62,14.75,15.62,16.62z M12.5,11.18v-7.6c1.78,0.35,3.12,1.92,3.12,3.8 S14.28,10.83,12.5,11.18z" /> </vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_invert_colors.xml b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_qs_dnd.xml index 59e7838b9c67..d36464600e2e 100644 --- a/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_invert_colors.xml +++ b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_qs_dnd.xml @@ -15,16 +15,15 @@ */ --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="17dp" + android:height="17dp" android:viewportWidth="24" android:viewportHeight="24"> <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> + android:fillColor="@android:color/white" + android:pathData="M12,22c5.52,0,10-4.48,10-10c0-5.52-4.48-10-10-10S2,6.48,2,12C2,17.52,6.48,22,12,22z M12,3.5c4.69,0,8.5,3.81,8.5,8.5 c0,4.69-3.81,8.5-8.5,8.5S3.5,16.69,3.5,12C3.5,7.31,7.31,3.5,12,3.5z" /> <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M12.62,2.23A1,1,0,0,0,12,2a1.07,1.07,0,0,0-0.63 0.22 C9.48,3.75,4,8.5,4,14a7.89,7.89,0,0,0,8,8,8,8,0,0,0,8-8C20,8.5,14.5,3.73,12.62,2.23ZM12,21a6.92,6.92,0,0,1-7-7C5,9.16,9.89,4.71,12,3Z" /> + android:fillColor="@android:color/white" + android:pathData="M6.75,12.75h10.5c0.41,0,0.75-0.34,0.75-0.75s-0.34-0.75-0.75-0.75H6.75C6.34,11.25,6,11.59,6,12S6.34,12.75,6.75,12.75z" /> </vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_signal_flashlight.xml b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_qs_flashlight.xml index 15266910de6c..cce36e30d753 100644 --- a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_signal_flashlight.xml +++ b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_qs_flashlight.xml @@ -21,13 +21,9 @@ android:viewportHeight="24"> <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> + android:fillColor="@android:color/white" + android:pathData="M11,22h2c1.1,0,2-0.9,2-2V10c1.95-1.17,3-3.5,3-6V3H6v1c0,2.5,1.05,4.83,3,6v10C9,21.1,9.9,22,11,22z M16.48,4.5 C16.45,5.03,16.35,5.53,16.2,6H7.8C7.65,5.53,7.55,5.03,7.52,4.5H16.48z M8.51,7.5h6.99c-0.35,0.5-0.77,0.92-1.26,1.21L13.5,9.15 V20c0,0.28-0.22,0.5-0.5,0.5h-2c-0.28,0-0.5-0.22-0.5-0.5V9.15L9.77,8.71C9.28,8.42,8.85,8,8.51,7.5z" /> <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M11,22h2a2,2,0,0,0,2-2V10a6.84,6.84,0,0,0,3-6V3H6V4a6.84,6.84,0,0,0,3,6V20A2,2,0,0,0,11,22ZM17,4a8.26,8.26,0,0,1-0.07,1H7.07A8.26,8.26,0,0,1,7,4ZM7.28,6h9.45a5.24,5.24,0,0,1-2.24,3.14L14,9.43V20a1,1,0,0,1-1,1H11a1,1,0,0,1-1-1V9.43l-0.49-0.29A5.25,5.25,0,0,1,7.28,6Z" /> - <path - android:fillColor="#000000" + android:fillColor="@android:color/white" android:pathData="M 12 13 C 12.5522847498 13 13 13.4477152502 13 14 C 13 14.5522847498 12.5522847498 15 12 15 C 11.4477152502 15 11 14.5522847498 11 14 C 11 13.4477152502 11.4477152502 13 12 13 Z" /> </vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_settings_bluetooth.xml b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_settings_bluetooth.xml new file mode 100644 index 000000000000..c12a2ebf4d9b --- /dev/null +++ b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_settings_bluetooth.xml @@ -0,0 +1,26 @@ +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M17.12,7.38c0-2.96-2.41-5.38-5.37-5.38H11v7.94L6.03,4.97c-0.29-0.29-0.77-0.29-1.06,0s-0.29,0.77,0,1.06L10.94,12 l-5.97,5.97c-0.29,0.29-0.29,0.77,0,1.06c0.15,0.15,0.34,0.22,0.53,0.22s0.38-0.07,0.53-0.22L11,14.06V22h0.75 c2.96,0,5.37-2.41,5.37-5.38c0-1.97-1.06-3.69-2.64-4.62C16.06,11.06,17.12,9.34,17.12,7.38z M15.62,16.62 c0,1.88-1.34,3.45-3.12,3.8v-7.6C14.28,13.17,15.62,14.75,15.62,16.62z M12.5,11.18v-7.6c1.78,0.35,3.12,1.92,3.12,3.8 S14.28,10.83,12.5,11.18z" /> +</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackCircularSettingsOverlay/res/drawable/ic_airplanemode_active.xml b/packages/overlays/IconPackCircularSettingsOverlay/res/drawable/ic_airplanemode_active.xml new file mode 100644 index 000000000000..8dbae49a1cb3 --- /dev/null +++ b/packages/overlays/IconPackCircularSettingsOverlay/res/drawable/ic_airplanemode_active.xml @@ -0,0 +1,26 @@ +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M22,15.89v-2.57c0-1.1-0.65-2.09-1.67-2.53L14.5,8.28V3.75c0-1.38-1.12-2.5-2.5-2.5s-2.5,1.12-2.5,2.5v4.53l-5.83,2.51 C2.65,11.22,2,12.22,2,13.32v2.57l7.5-1.28v3.03l-1.47,1.17C7.38,19.34,7,20.12,7,20.96v1.33l4.96-0.3L17,22.3v-1.33 c0-0.84-0.38-1.62-1.03-2.15l-1.47-1.17v-3.03L22,15.89z M15.03,19.98c0.23,0.18,0.38,0.44,0.44,0.72l-3.52-0.2l-3.43,0.2 c0.06-0.28,0.21-0.53,0.44-0.72L11,18.36v-5.53l-7.5,1.28v-0.79c0-0.5,0.3-0.95,0.76-1.15L11,9.27V3.75c0-0.55,0.45-1,1-1 s1,0.45,1,1v5.52l6.74,2.9c0.46,0.2,0.76,0.65,0.76,1.15v0.79L13,12.83v5.53L15.03,19.98z" /> +</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_lockscreen_ime.xml b/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_lockscreen_ime.xml deleted file mode 100644 index 04a2c24ce45a..000000000000 --- a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_lockscreen_ime.xml +++ /dev/null @@ -1,57 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M21,21a2,2,0,0,0,2-2V6a2,2,0,0,0-2-2H3A2,2,0,0,0,1,6V19a2,2,0,0,0,2,2ZM2,19V6A1,1,0,0,1,3,5H21a1,1,0,0,1,1,1V19a1,1,0,0,1-1,1H3A1,1,0,0,1,2,19Z" /> - <path - android:fillColor="#000000" - android:pathData="M 9.5 8 L 10.5 8 Q 11 8 11 8.5 L 11 9.5 Q 11 10 10.5 10 L 9.5 10 Q 9 10 9 9.5 L 9 8.5 Q 9 8 9.5 8 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 5.5 8 L 6.5 8 Q 7 8 7 8.5 L 7 9.5 Q 7 10 6.5 10 L 5.5 10 Q 5 10 5 9.5 L 5 8.5 Q 5 8 5.5 8 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 13.5 8 L 14.5 8 Q 15 8 15 8.5 L 15 9.5 Q 15 10 14.5 10 L 13.5 10 Q 13 10 13 9.5 L 13 8.5 Q 13 8 13.5 8 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 9.5 12 L 10.5 12 Q 11 12 11 12.5 L 11 13.5 Q 11 14 10.5 14 L 9.5 14 Q 9 14 9 13.5 L 9 12.5 Q 9 12 9.5 12 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 5.5 12 L 6.5 12 Q 7 12 7 12.5 L 7 13.5 Q 7 14 6.5 14 L 5.5 14 Q 5 14 5 13.5 L 5 12.5 Q 5 12 5.5 12 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 13.5 12 L 14.5 12 Q 15 12 15 12.5 L 15 13.5 Q 15 14 14.5 14 L 13.5 14 Q 13 14 13 13.5 L 13 12.5 Q 13 12 13.5 12 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 17.5 8 L 18.5 8 Q 19 8 19 8.5 L 19 9.5 Q 19 10 18.5 10 L 17.5 10 Q 17 10 17 9.5 L 17 8.5 Q 17 8 17.5 8 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 17.5 12 L 18.5 12 Q 19 12 19 12.5 L 19 13.5 Q 19 14 18.5 14 L 17.5 14 Q 17 14 17 13.5 L 17 12.5 Q 17 12 17.5 12 Z" /> - <path - android:fillColor="#000000" - android:pathData="M8.5,17h7a0.5 0.5 ,0,0,0,0-1h-7a0.5 0.5 ,0,0,0,0,1Z" /> -</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_qs_brightness_auto_on.xml b/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_qs_brightness_auto_on.xml deleted file mode 100644 index fc990d87f2fb..000000000000 --- a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_qs_brightness_auto_on.xml +++ /dev/null @@ -1,33 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M3.41,14.72A2,2,0,0,1,4,16.14V18a2,2,0,0,0,2,2H7.86a2,2,0,0,1,1.42 0.59 l1.31,1.31a2,2,0,0,0,2.82,0l1.31-1.31A2,2,0,0,1,16.14,20H18a2,2,0,0,0,2-2V16.14a2,2,0,0,1,0.59-1.42l1.31-1.31a2,2,0,0,0,0-2.82L20.59,9.28A2,2,0,0,1,20,7.86V6a2,2,0,0,0-2-2H16.14a2,2,0,0,1-1.42-0.59L13.41,2.1a2,2,0,0,0-2.82,0L9.28,3.41A2,2,0,0,1,7.86,4H6A2,2,0,0,0,4,6V7.86a2,2,0,0,1-0.59,1.42L2.1,10.59a2,2,0,0,0,0,2.82Zm-0.6-3.43L4.12,10A3,3,0,0,0,5,7.86V6A1,1,0,0,1,6,5H7.86A3,3,0,0,0,10,4.12l1.31-1.31a1,1,0,0,1,1.42,0L14,4.12A3,3,0,0,0,16.14,5H18a1,1,0,0,1,1,1V7.86A3,3,0,0,0,19.88,10l1.31,1.31a1,1,0,0,1,0,1.42L19.88,14A3,3,0,0,0,19,16.14V18a1,1,0,0,1-1,1H16.14a3,3,0,0,0-2.12 0.88 l-1.31,1.31a1,1,0,0,1-1.42,0L10,19.88A3,3,0,0,0,7.86,19H6a1,1,0,0,1-1-1V16.14A3,3,0,0,0,4.12,14L2.81,12.71a1,1,0,0,1,0-1.42Z" /> - <path - android:fillColor="#000000" - android:pathData="M10,14.61h4L14.85,17H17L13.11,7H10.87L7,17H9.15Zm1.92-5.44h0.11l1.29,3.71H10.63Z" /> -</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_qs_cast_on.xml b/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_qs_cast_on.xml deleted file mode 100644 index d12cf9ee85fd..000000000000 --- a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_qs_cast_on.xml +++ /dev/null @@ -1,42 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M22,17.5V6.5A2.5,2.5,0,0,0,19.5,4H4.5A2.5,2.5,0,0,0,2,6.5v2a0.5 0.5 ,0,0,0,1,0v-2A1.5,1.5,0,0,1,4.5,5h15A1.5,1.5,0,0,1,21,6.5v11A1.5,1.5,0,0,1,19.5,19h-6a0.5 0.5 ,0,0,0,0,1h6A2.5,2.5,0,0,0,22,17.5Z" /> - <path - android:fillColor="#000000" - android:pathData="M2.21,19.61A1,1,0,0,0,3,20a1,1,0,0,0,0-2H3a1,1,0,0,0-0.79,1.61Z" /> - <path - android:fillColor="#000000" - android:pathData="M2.5,12A7.5,7.5,0,0,1,10,19.5a0.5 0.5 ,0,0,0,1,0A8.51,8.51,0,0,0,2.5,11a0.5 0.5 ,0,0,0,0,1Z" /> - <path - android:fillColor="#000000" - android:pathData="M2.5,16A3.5,3.5,0,0,1,6,19.5a0.5 0.5 ,0,0,0,1,0A4.51,4.51,0,0,0,2.5,15a0.5 0.5 ,0,0,0,0,1Z" /> - <path - android:fillColor="#000000" - android:pathData="M16.5,15h-3a0.5 0.5 ,0,0,0,0,1h3A1.5,1.5,0,0,0,18,14.5v-5A1.5,1.5,0,0,0,16.5,8H6.5a0.5 0.5 ,0,0,0,0,1h10a0.5 0.5 ,0,0,1,0.5 0.5 v5A0.5 0.5 ,0,0,1,16.5,15Z" /> -</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_settings_16dp.xml b/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_settings_16dp.xml deleted file mode 100644 index 33d172cd2517..000000000000 --- a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_settings_16dp.xml +++ /dev/null @@ -1,33 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="16dp" - android:height="16dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M5.31,19.26a1.6,1.6,0,0,0,0.53-0.09l1.8-0.7c0.26 0.16 0.52 0.31 0.79 0.45 l0.27,1.84A1.44,1.44,0,0,0,10.15,22h3.7a1.46,1.46,0,0,0,1.46-1.19l0.27-1.87c0.26-0.13 0.52 -0.28 0.78 -0.44l1.8 0.7 a1.47,1.47,0,0,0,0.54 0.1 A1.44,1.44,0,0,0,20,18.58l1.86-3.14a1.4,1.4,0,0,0-0.37-1.81l-1.52-1.17c0-0.14,0-0.29,0-0.45s0-0.3,0-0.44l1.52-1.17a1.41,1.41,0,0,0,0.36-1.83L20,5.47a1.46,1.46,0,0,0-1.29-0.73,1.69,1.69,0,0,0-0.53 0.09 l-1.8 0.7 c-0.26-0.16-0.52-0.31-0.79-0.45l-0.27-1.84A1.44,1.44,0,0,0,13.84,2h-3.7A1.45,1.45,0,0,0,8.7,3.22L8.43,5.08q-0.39 0.21 -0.78 0.45 L5.84,4.82a1.47,1.47,0,0,0-0.54-0.1,1.43,1.43,0,0,0-1.25 0.72 L2.2,8.55a1.37,1.37,0,0,0,0.37,1.83l1.52,1.17c0,0.14,0,0.3,0,0.45s0,0.3,0,0.44L2.56,13.61a1.42,1.42,0,0,0-0.36,1.83L4,18.53A1.46,1.46,0,0,0,5.31,19.26ZM3.16,14.4l1.53-1.16 0.43 -0.33,0-0.53c0-0.13,0-0.25,0-0.38s0-0.26,0-0.39l0-0.53-0.42-0.33L3.17,9.58a0.38 0.38 ,0,0,1-0.11-0.52L4.92,5.93a0.43 0.43 ,0,0,1,0.38-0.21 0.47 0.47,0,0,1,0.17,0l1.81 0.71 0.48 0.19 0.43-0.27A6.39,6.39,0,0,1,8.9,6l0.45-0.24 0.07 -0.5 0.27 -1.88A0.44 0.44 ,0,0,1,10.14,3h3.7a0.44 0.44 ,0,0,1,0.46 0.38 l0.27,1.85 0.08 0.51 0.46 0.24a5.3,5.3,0,0,1,0.7 0.4 l0.43 0.27 0.47-0.19,1.78-0.69a0.63 0.63 ,0,0,1,0.19,0,0.47 0.47 ,0,0,1,0.43 0.24 l1.83,3.08a0.42 0.42 ,0,0,1-0.1 0.55 l-1.52,1.16-0.42 0.33 ,0,0.53c0,0.13,0,0.25,0,0.38s0,0.26,0,0.39l0,0.53 0.42 0.33,1.51,1.15a0.42 0.42 ,0,0,1,0.13 0.52 l-1.87,3.16a0.43 0.43 ,0,0,1-0.39 0.21 0.57 0.57 ,0,0,1-0.18,0l-1.8-0.71-0.47-0.18-0.43 0.27 a7.46,7.46,0,0,1-0.71 0.41 l-0.45 0.24 -0.07 0.5 -0.27,1.86a0.47 0.47 ,0,0,1-0.47 0.34 h-3.7a0.44 0.44 ,0,0,1-0.46-0.38l-0.27-1.85-0.08-0.51L8.88,18a5.3,5.3,0,0,1-0.7-0.4l-0.43-0.27-0.47 0.19 -1.78 0.69 a0.58 0.58 ,0,0,1-0.19,0A0.48 0.48 ,0,0,1,4.89,18L3.08,15A0.42 0.42 ,0,0,1,3.16,14.4Z" /> - <path - android:fillColor="#000000" - android:pathData="M12,15.91A3.92,3.92,0,1,0,8,12,4,4,0,0,0,12,15.91Zm0-6.83A2.92,2.92,0,1,1,9,12,3,3,0,0,1,12,9.08Z" /> -</vector> diff --git a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_signal_airplane.xml b/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_signal_airplane.xml deleted file mode 100644 index a5ef380c4a4d..000000000000 --- a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_signal_airplane.xml +++ /dev/null @@ -1,30 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M2.8,15l7.2-0.73v3.49L8,19.38a1.52,1.52,0,0,0-0.54,1.16v1a0.52 0.52 ,0,0,0,0.17 0.38 0.51 0.51 ,0,0,0,0.39 0.12 L12,21.5l3.94 0.5 H16a0.5 0.5 ,0,0,0,0.33-0.12 0.52 0.52,0,0,0,0.17-0.38v-1A1.52,1.52,0,0,0,16,19.38l-2-1.62V14.27l7.2 0.73 a0.51 0.51 ,0,0,0,0.55-0.5,3.49,3.49,0,0,0-2.15-3.23L14,8.94V3.5a2,2,0,0,0-4,0V8.94L4.4,11.27A3.49,3.49,0,0,0,2.25,14.5a0.51 0.51 ,0,0,0,0.55 0.5 Zm2-2.81,5.9-2.45A0.5 0.5 ,0,0,0,11,9.28V3.5a1,1,0,0,1,2,0V9.28a0.5 0.5 ,0,0,0,0.31 0.46 l5.9,2.45a2.51,2.51,0,0,1,1.48,1.75l-7.14-0.72a0.52 0.52 ,0,0,0-0.38 0.13 0.5 0.5 ,0,0,0-0.17 0.37 V18a0.53 0.53 ,0,0,0,0.18 0.39 l2.14,1.76a0.53 0.53 ,0,0,1,0.18 0.39 v0.39l-3.44-0.43h-0.12l-3.44 0.43 v-0.39a0.53 0.53 ,0,0,1,0.18-0.39l2.14-1.76A0.53 0.53 ,0,0,0,11,18V13.72a0.5 0.5 ,0,0,0-0.17-0.37 0.52 0.52,0,0,0-0.38-0.13l-7.14 0.72 A2.51,2.51,0,0,1,4.79,12.19Z" /> -</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml new file mode 100644 index 000000000000..c5c3f0644de6 --- /dev/null +++ b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml @@ -0,0 +1,27 @@ +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="@*android:color/accent_device_default" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M17.21,6.79l-4.5-4.5c-0.29-0.29-0.72-0.37-1.09-0.22C11.25,2.23,11,2.6,11,3v6.59l-3.8-3.8c-0.39-0.39-1.02-0.39-1.41,0 c-0.39,0.39-0.39,1.02,0,1.41l4.8,4.8l-4.8,4.8c-0.39,0.39-0.39,1.02,0,1.41c0.39,0.39,1.02,0.39,1.41,0l3.8-3.8V21 c0,0.4,0.24,0.77,0.62,0.92C11.74,21.98,11.87,22,12,22c0.26,0,0.52-0.1,0.71-0.29l4.5-4.5c0.39-0.39,0.39-1.02,0-1.41L13.42,12 l3.79-3.79C17.6,7.82,17.6,7.18,17.21,6.79z M15.09,16.5L13,18.58v-4.17L15.09,16.5z M13,9.58V5.42l2.08,2.08L13,9.58z" /> +</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_qs_airplane.xml b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_qs_airplane.xml new file mode 100644 index 000000000000..85260c0d0614 --- /dev/null +++ b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_qs_airplane.xml @@ -0,0 +1,26 @@ +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="18dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M2.65,15.8L10,13.5V19l-1.6,1.2C8.15,20.39,8,20.69,8,21v0.67c0,0.17,0.14,0.28,0.31,0.24c1.94-0.55,1.3-0.37,3.19-0.91 c1.21,0.35,1.99,0.57,3.19,0.91c0.17,0.04,0.31-0.07,0.31-0.24V21c0-0.31-0.15-0.61-0.4-0.8L13,19v-5.5l7.35,2.3 c0.32,0.1,0.65-0.14,0.65-0.48v-0.49c0-0.52-0.27-1-0.7-1.27L13,9V3.5C13,2.67,12.33,2,11.5,2S10,2.67,10,3.5V9l-7.3,4.56 C2.27,13.83,2,14.31,2,14.83v0.49C2,15.66,2.33,15.9,2.65,15.8z" /> +</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_qs_auto_rotate.xml b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_qs_auto_rotate.xml index f823812ddf3f..bcdb618f8b24 100644 --- a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_qs_auto_rotate.xml +++ b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_qs_auto_rotate.xml @@ -21,11 +21,9 @@ android:viewportHeight="24"> <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> + android:fillColor="@android:color/white" + android:pathData="M16.41,10.96h2.83l-8.18-8.18c-0.62-0.62-1.65-0.6-2.29,0.04L4.27,7.31L2.85,5.89C2.54,5.58,2,5.8,2,6.25v4.25 C2,10.78,2.22,11,2.5,11h4.25c0.45,0,0.67-0.54,0.35-0.85L5.69,8.73l4.24-4.24L16.41,10.96z" /> <path - android:fillColor="#000000" - android:pathData="M16.41,11h2.83L11.05,2.78a1.62,1.62,0,0,0-2.29,0L4.27,7.31,2.85,5.89A0.5 0.5 ,0,0,0,2,6.25V10.5a0.5 0.5 ,0,0,0,0.5 0.5 H6.75a0.5 0.5 ,0,0,0,0.36-0.85L5.69,8.73,9.93,4.49Z" /> - <path - android:fillColor="#000000" - android:pathData="M22,13.51a0.5 0.5 ,0,0,0-0.5-0.5H17.25a0.5 0.5 ,0,0,0-0.36 0.85 l1.35,1.35-4.31,4.31L7.44,13H4.61l8.19,8.18a1.62,1.62,0,0,0,2.29,0l4.57-4.55,1.49,1.49a0.5 0.5 ,0,0,0,0.85-0.36Z" /> + android:fillColor="@android:color/white" + android:pathData="M22,13.51c0-0.28-0.22-0.5-0.5-0.5h-4.25c-0.45,0-0.67,0.54-0.35,0.85l1.34,1.34l-4.31,4.31l-6.48-6.48H4.61l8.19,8.19 c0.62,0.62,1.65,0.6,2.29-0.04l4.57-4.55l1.49,1.49c0.32,0.31,0.85,0.09,0.85-0.35V13.51z" /> </vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_qs_bluetooth.xml b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_qs_bluetooth.xml new file mode 100644 index 000000000000..cf7cab5d5e64 --- /dev/null +++ b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_qs_bluetooth.xml @@ -0,0 +1,26 @@ +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M17.21,6.79l-4.5-4.5c-0.29-0.29-0.72-0.37-1.09-0.22C11.25,2.23,11,2.6,11,3v6.59l-3.8-3.8c-0.39-0.39-1.02-0.39-1.41,0 c-0.39,0.39-0.39,1.02,0,1.41l4.8,4.8l-4.8,4.8c-0.39,0.39-0.39,1.02,0,1.41c0.39,0.39,1.02,0.39,1.41,0l3.8-3.8V21 c0,0.4,0.24,0.77,0.62,0.92C11.74,21.98,11.87,22,12,22c0.26,0,0.52-0.1,0.71-0.29l4.5-4.5c0.39-0.39,0.39-1.02,0-1.41L13.42,12 l3.79-3.79C17.6,7.82,17.6,7.18,17.21,6.79z M15.09,16.5L13,18.58v-4.17L15.09,16.5z M13,9.58V5.42l2.08,2.08L13,9.58z" /> +</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_dnd.xml b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_qs_dnd.xml index e6086f3813c1..a094698a5d3f 100644 --- a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_dnd.xml +++ b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_qs_dnd.xml @@ -15,14 +15,12 @@ */ --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="17dp" + android:height="17dp" android:viewportWidth="24" android:viewportHeight="24"> <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M12,22A10,10,0,1,0,2,12,10,10,0,0,0,12,22ZM8,11h8a1,1,0,0,1,0,2H8a1,1,0,0,1,0-2Z" /> + android:fillColor="@android:color/white" + android:pathData="M12,22c5.52,0,10-4.48,10-10c0-5.52-4.48-10-10-10S2,6.48,2,12C2,17.52,6.48,22,12,22z M8,11h8c0.55,0,1,0.45,1,1 s-0.45,1-1,1H8c-0.55,0-1-0.45-1-1S7.45,11,8,11z" /> </vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_signal_flashlight.xml b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_qs_flashlight.xml index 1ffb32b90144..4427305c4b0b 100644 --- a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_signal_flashlight.xml +++ b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_qs_flashlight.xml @@ -21,11 +21,9 @@ android:viewportHeight="24"> <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> + android:fillColor="@android:color/white" + android:pathData="M10,22h4c0.55,0,1-0.45,1-1V10c1.1,0,2-0.9,2-2V5.5H7V8c0,1.1,0.9,2,2,2v11C9,21.55,9.45,22,10,22z M11,12 c0-0.55,0.45-1,1-1s1,0.45,1,1v2c0,0.55-0.45,1-1,1s-1-0.45-1-1V12z" /> <path - android:fillColor="#000000" - android:pathData="M10,22h4a1,1,0,0,0,1-1V10a2,2,0,0,0,2-2V5.5H7V8a2,2,0,0,0,2,2V21A1,1,0,0,0,10,22Zm2-10a1.5,1.5,0,1,1-1.5,1.5A1.5,1.5,0,0,1,12,12Z" /> - <path - android:fillColor="#000000" - android:pathData="M17,3a1,1,0,0,0-1-1H8A1,1,0,0,0,7,3V4H17Z" /> + android:fillColor="@android:color/white" + android:pathData="M17,3c0-0.55-0.45-1-1-1H8C7.45,2,7,2.45,7,3v0.96h10V3z" /> </vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_settings_bluetooth.xml b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_settings_bluetooth.xml new file mode 100644 index 000000000000..cf7cab5d5e64 --- /dev/null +++ b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_settings_bluetooth.xml @@ -0,0 +1,26 @@ +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M17.21,6.79l-4.5-4.5c-0.29-0.29-0.72-0.37-1.09-0.22C11.25,2.23,11,2.6,11,3v6.59l-3.8-3.8c-0.39-0.39-1.02-0.39-1.41,0 c-0.39,0.39-0.39,1.02,0,1.41l4.8,4.8l-4.8,4.8c-0.39,0.39-0.39,1.02,0,1.41c0.39,0.39,1.02,0.39,1.41,0l3.8-3.8V21 c0,0.4,0.24,0.77,0.62,0.92C11.74,21.98,11.87,22,12,22c0.26,0,0.52-0.1,0.71-0.29l4.5-4.5c0.39-0.39,0.39-1.02,0-1.41L13.42,12 l3.79-3.79C17.6,7.82,17.6,7.18,17.21,6.79z M15.09,16.5L13,18.58v-4.17L15.09,16.5z M13,9.58V5.42l2.08,2.08L13,9.58z" /> +</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_dnd.xml b/packages/overlays/IconPackFilledSettingsOverlay/res/drawable/ic_airplanemode_active.xml index 3e32b3bd6664..bddc57ee70a7 100644 --- a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_dnd.xml +++ b/packages/overlays/IconPackFilledSettingsOverlay/res/drawable/ic_airplanemode_active.xml @@ -21,13 +21,6 @@ android:viewportHeight="24"> <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M12,22A10,10,0,1,0,2,12,10,10,0,0,0,12,22ZM12,3a9,9,0,1,1-9,9A9,9,0,0,1,12,3Z" /> - <path - android:fillColor="#000000" - android:pathData="M7,12.5H17a0.5 0.5 ,0,0,0,0-1H7a0.5 0.5 ,0,0,0,0,1Z" /> + android:fillColor="@android:color/white" + android:pathData="M2.65,15.8L10,13.5V19l-1.6,1.2C8.15,20.39,8,20.69,8,21v0.67c0,0.17,0.14,0.28,0.31,0.24c1.94-0.55,1.3-0.37,3.19-0.91 c1.21,0.35,1.99,0.57,3.19,0.91c0.17,0.04,0.31-0.07,0.31-0.24V21c0-0.31-0.15-0.61-0.4-0.8L13,19v-5.5l7.35,2.3 c0.32,0.1,0.65-0.14,0.65-0.48v-0.49c0-0.52-0.27-1-0.7-1.27L13,9V3.5C13,2.67,12.33,2,11.5,2S10,2.67,10,3.5V9l-7.3,4.56 C2.27,13.83,2,14.31,2,14.83v0.49C2,15.66,2.33,15.9,2.65,15.8z" /> </vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_lockscreen_ime.xml b/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_lockscreen_ime.xml deleted file mode 100644 index 8b9f5627a98b..000000000000 --- a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_lockscreen_ime.xml +++ /dev/null @@ -1,55 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M3,21H21a2,2,0,0,0,2-2V6a2,2,0,0,0-2-2H3A2,2,0,0,0,1,6V19A2,2,0,0,0,3,21ZM3,6H21V19H3Z" /> - <path - android:fillColor="#000000" - android:pathData="M 9 8 H 11 V 10 H 9 V 8 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 5 8 H 7 V 10 H 5 V 8 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 8 16 H 16 V 17 H 8 V 16 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 13 8 H 15 V 10 H 13 V 8 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 9 12 H 11 V 14 H 9 V 12 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 5 12 H 7 V 14 H 5 V 12 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 13 12 H 15 V 14 H 13 V 12 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 17 8 H 19 V 10 H 17 V 8 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 17 12 H 19 V 14 H 17 V 12 Z" /> -</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_qs_brightness_auto_on.xml b/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_qs_brightness_auto_on.xml deleted file mode 100644 index f3b1c016c301..000000000000 --- a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_qs_brightness_auto_on.xml +++ /dev/null @@ -1,31 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M10,14.61h4L14.85,17H17L13.11,7H10.87L7,17H9.15Zm1.54-4.24 0.38 -1.2h0.11l0.38,1.2 0.91 ,2.51H10.63Z" /> - <path - android:fillColor="#000000" - android:pathData="M4,20H8.69L12,23.31,15.31,20H20V15.31L23.31,12,20,8.69V4H15.31L12,0.69,8.69,4H4V8.69L0.69,12,4,15.31Zm-0.48-8L6,9.52V6H9.52L12,3.52,14.48,6H18V9.52L20.48,12,18,14.48V18H14.48L12,20.48,9.52,18H6V14.48Z" /> -</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_qs_cast_on.xml b/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_qs_cast_on.xml deleted file mode 100644 index 0fd763b8d325..000000000000 --- a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_qs_cast_on.xml +++ /dev/null @@ -1,40 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M1.82,16.08a5,5,0,0,1,4.1,4.08,1,1,0,0,0,1,0.84,1,1,0,0,0,1-1.14,7,7,0,0,0-5.8-5.78,1,1,0,0,0-0.29,2Z" /> - <path - android:fillColor="#000000" - android:pathData="M19,7H5V8.63A13,13,0,0,1,13.37,17H19Z" /> - <path - android:fillColor="#000000" - android:pathData="M3,5H21V19H14v2h7a2,2,0,0,0,2-2V5a2,2,0,0,0-2-2H3A2,2,0,0,0,1,5V8H3Z" /> - <path - android:fillColor="#000000" - android:pathData="M1.85,12A9.06,9.06,0,0,1,10,20.12a1,1,0,0,0,1,0.88,1,1,0,0,0,1-1.1,11,11,0,0,0-9.87-9.85A1,1,0,0,0,1,11,1,1,0,0,0,1.85,12Z" /> - <path - android:fillColor="#000000" - android:pathData="M2,21H4a3,3,0,0,0-3-3v2A1,1,0,0,0,2,21Z" /> -</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_settings_16dp.xml b/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_settings_16dp.xml deleted file mode 100644 index d292b13c8faf..000000000000 --- a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_settings_16dp.xml +++ /dev/null @@ -1,28 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="16dp" - android:height="16dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M21.64,8.39,20,5.63a1.12,1.12,0,0,0-1.36-0.5L16.54,6A7.26,7.26,0,0,0,15,5.12l-0.27-2.2A1.1,1.1,0,0,0,13.59,2H10.41a1.1,1.1,0,0,0-1.11 0.92 L9,5.11A7.1,7.1,0,0,0,7.46,6L5.32,5.12A1.12,1.12,0,0,0,4,5.62L2.36,8.38A1.1,1.1,0,0,0,2.6,9.8l1.94,1.45a6.06,6.06,0,0,0,0,0.75,6.34,6.34,0,0,0,0,0.76L2.6,14.2a1.09,1.09,0,0,0-0.24,1.41L4,18.37a1.12,1.12,0,0,0,1.36 0.5 L7.46,18A7.26,7.26,0,0,0,9,18.88l0.27,2.19a1.1,1.1,0,0,0,1.11 0.93 h3.18a1.11,1.11,0,0,0,1.11-0.92L15,18.89A7.26,7.26,0,0,0,16.54,18l2.14 0.91 a1.12,1.12,0,0,0,1.36-0.5l1.6-2.76a1.1,1.1,0,0,0-0.24-1.42l-1.94-1.45a7.24,7.24,0,0,0,0-1.52L21.4,9.8A1.09,1.09,0,0,0,21.64,8.39ZM12,15.5A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z" /> -</vector> diff --git a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_signal_airplane.xml b/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_signal_airplane.xml deleted file mode 100644 index 999a9bf3cf45..000000000000 --- a/packages/overlays/IconPackFilledSystemUIOverlay/res/drawable/ic_signal_airplane.xml +++ /dev/null @@ -1,28 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M3.15,15.8l7.35-2.3V19L8.9,20.2a1,1,0,0,0-0.4 0.8 v0.67a0.24 0.24 ,0,0,0,0.31 0.24 L12,21l3.19 0.91 a0.24 0.24 ,0,0,0,0.31-0.24V21a1,1,0,0,0-0.4-0.8L13.5,19V13.5l7.35,2.3a0.5 0.5 ,0,0,0,0.65-0.48v-0.49a1.5,1.5,0,0,0-0.7-1.27L13.5,9V3.5a1.5,1.5,0,0,0-3,0V9L3.2,13.56a1.5,1.5,0,0,0-0.7,1.27v0.49A0.5 0.5 ,0,0,0,3.15,15.8Z" /> -</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml new file mode 100644 index 000000000000..bd06e7cead0b --- /dev/null +++ b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml @@ -0,0 +1,27 @@ +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="@*android:color/accent_device_default" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M17.53,6.72l-4.75-4.75c-0.21-0.21-0.54-0.28-0.82-0.16C11.68,1.92,11.5,2.2,11.5,2.5v7.69L7.53,6.22 c-0.29-0.29-0.77-0.29-1.06,0s-0.29,0.77,0,1.06L11.19,12l-4.72,4.72c-0.29,0.29-0.29,0.77,0,1.06s0.77,0.29,1.06,0l3.97-3.97v7.69 c0,0.3,0.18,0.58,0.46,0.69c0.09,0.04,0.19,0.06,0.29,0.06c0.2,0,0.39-0.08,0.53-0.22l4.75-4.75c0.29-0.29,0.29-0.77,0-1.06 L13.31,12l4.22-4.22C17.82,7.49,17.82,7.01,17.53,6.72z M15.94,16.75L13,19.69v-5.88L15.94,16.75z M13,10.19V4.31l2.94,2.94 L13,10.19z" /> +</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_invert_colors.xml b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_invert_colors.xml deleted file mode 100644 index 173824be8a64..000000000000 --- a/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_invert_colors.xml +++ /dev/null @@ -1,28 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M12.62,2.23A1,1,0,0,0,12,2a1.07,1.07,0,0,0-0.63 0.22 C9.48,3.75,4,8.5,4,14a7.89,7.89,0,0,0,8,8,8,8,0,0,0,8-8C20,8.5,14.5,3.73,12.62,2.23ZM5.5,14c0-4.4,4.32-8.53,6.5-10.33V20.49A6.43,6.43,0,0,1,5.5,14Z" /> -</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_qs_airplane.xml b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_qs_airplane.xml new file mode 100644 index 000000000000..5f9bdd24518e --- /dev/null +++ b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_qs_airplane.xml @@ -0,0 +1,26 @@ +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="18dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M2.52,16.17c0.32,0.23,0.74,0.31,1.11,0.19l5.87-1.84v3.87L8,19.52c-0.31,0.24-0.5,0.61-0.5,1v0.75 c0,0.69,0.56,1.25,1.25,1.25h6.5c0.69,0,1.25-0.56,1.25-1.25v-0.75c0-0.39-0.19-0.76-0.5-1l-1.5-1.12v-3.87l5.88,1.84 c0.38,0.12,0.79,0.05,1.11-0.19c0.32-0.23,0.51-0.61,0.51-1.01l0-1.84c0-0.63-0.34-1.21-0.89-1.52L14.5,8.06V4 c0-1.38-1.12-2.5-2.5-2.5S9.5,2.62,9.5,4v4.07L2.89,11.8C2.35,12.11,2,12.7,2,13.33l0,1.83C2.01,15.56,2.2,15.94,2.52,16.17z M3.63,13.11L11,8.94V4c0-0.55,0.45-1,1-1s1,0.45,1,1v4.94l7.37,4.17c0.08,0.04,0.13,0.13,0.13,0.22l0,1.5L13,12.48v6.66l2,1.5 v0.38H9v-0.38l2-1.5v-6.66l-7.5,2.34l0-1.49C3.5,13.24,3.55,13.15,3.63,13.11z" /> +</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_qs_auto_rotate.xml b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_qs_auto_rotate.xml index fe7ecfd9d615..0f9effdad996 100644 --- a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_qs_auto_rotate.xml +++ b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_qs_auto_rotate.xml @@ -21,11 +21,9 @@ android:viewportHeight="24"> <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> + android:fillColor="@android:color/white" + android:pathData="M3.5,7C3.09,7,2.75,7.34,2.75,7.75v2.73c0,0.41,0.34,0.75,0.75,0.75h2.75c0.41,0,0.75-0.34,0.75-0.75S6.66,9.73,6.25,9.73 H5.33l5.42-5.42l6.47,6.47c0.15,0.15,0.34,0.22,0.53,0.22s0.38-0.07,0.53-0.22c0.29-0.29,0.29-0.77,0-1.06l-7-7 c-0.29-0.29-0.77-0.29-1.06,0L4.25,8.69V7.75C4.25,7.34,3.91,7,3.5,7z" /> <path - android:fillColor="#000000" - android:pathData="M3,6.81a0.75 0.75 ,0,0,0-0.75 0.75 v2.83a0.74 0.74 ,0,0,0,0.75 0.75 H5.8a0.75 0.75 ,0,1,0,0-1.5h-1l5.6-5.59,7.07,7.06a0.75 0.75 ,0,0,0,1.06-1.06l-7.6-7.6a0.75 0.75 ,0,0,0-1.06,0L3.72,8.62V7.56A0.76 0.76 ,0,0,0,3,6.81Z" /> - <path - android:fillColor="#000000" - android:pathData="M21,12.85H18.21a0.75 0.75 ,0,0,0,0,1.5h1L13.59,20,6.52,12.89a0.75 0.75 ,0,0,0-1.06,0,0.74 0.74 ,0,0,0,0,1.06l7.6,7.6a0.75 0.75 ,0,0,0,1.06,0l6.17-6.17v1.06a0.75 0.75 ,0,1,0,1.5,0V13.6A0.76 0.76 ,0,0,0,21,12.85Z" /> + android:fillColor="@android:color/white" + android:pathData="M20.5,17c0.41,0,0.75-0.34,0.75-0.75V13.5c0-0.41-0.34-0.75-0.75-0.75h-2.75c-0.41,0-0.75,0.34-0.75,0.75 s0.34,0.75,0.75,0.75h0.94l-5.44,5.44l-6.47-6.47c-0.29-0.29-0.77-0.29-1.06,0s-0.29,0.77,0,1.06l7,7 c0.15,0.15,0.34,0.22,0.53,0.22s0.38-0.07,0.53-0.22l5.97-5.97v0.94C19.75,16.66,20.09,17,20.5,17z" /> </vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_qs_bluetooth.xml b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_qs_bluetooth.xml new file mode 100644 index 000000000000..d2eb2d3fed26 --- /dev/null +++ b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_qs_bluetooth.xml @@ -0,0 +1,26 @@ +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M17.53,6.72l-4.75-4.75c-0.21-0.21-0.54-0.28-0.82-0.16C11.68,1.92,11.5,2.2,11.5,2.5v7.69L7.53,6.22 c-0.29-0.29-0.77-0.29-1.06,0s-0.29,0.77,0,1.06L11.19,12l-4.72,4.72c-0.29,0.29-0.29,0.77,0,1.06s0.77,0.29,1.06,0l3.97-3.97v7.69 c0,0.3,0.18,0.58,0.46,0.69c0.09,0.04,0.19,0.06,0.29,0.06c0.2,0,0.39-0.08,0.53-0.22l4.75-4.75c0.29-0.29,0.29-0.77,0-1.06 L13.31,12l4.22-4.22C17.82,7.49,17.82,7.01,17.53,6.72z M15.94,16.75L13,19.69v-5.88L15.94,16.75z M13,10.19V4.31l2.94,2.94 L13,10.19z" /> +</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_qs_dnd.xml b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_qs_dnd.xml new file mode 100644 index 000000000000..77a84ba9509d --- /dev/null +++ b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_qs_dnd.xml @@ -0,0 +1,29 @@ +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="17dp" + android:height="17dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M16.25,11.25h-8.5C7.34,11.25,7,11.59,7,12s0.34,0.75,0.75,0.75h8.5c0.41,0,0.75-0.34,0.75-0.75S16.66,11.25,16.25,11.25z" /> + <path + android:fillColor="@android:color/white" + android:pathData="M12,2C6.49,2,2,6.49,2,12s4.49,10,10,10c0,0,0.01,0,0.01,0c5.5,0,9.98-4.47,9.99-9.98V12C22,6.49,17.51,2,12,2z M20.5,12.02c0,4.68-3.81,8.48-8.49,8.48c0,0-0.01,0-0.01,0c-4.69,0-8.5-3.81-8.5-8.5S7.31,3.5,12,3.5s8.5,3.81,8.5,8.5V12.02z" /> +</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_signal_flashlight.xml b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_qs_flashlight.xml index 3bde46fcb948..9168c20edff3 100644 --- a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_signal_flashlight.xml +++ b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_qs_flashlight.xml @@ -21,11 +21,9 @@ android:viewportHeight="24"> <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> + android:fillColor="@android:color/white" + android:pathData="M17,2H7C6.59,2,6.25,2.34,6.25,2.75v5c0,0.14,0.04,0.27,0.11,0.39l1.89,3.07v10.04C8.25,21.66,8.59,22,9,22h6 c0.41,0,0.75-0.34,0.75-0.75v-9.79l1.89-3.07c0.07-0.12,0.11-0.25,0.11-0.39V2.75C17.75,2.34,17.41,2,17,2z M16.25,7.79 l-1.89,3.07c-0.07,0.12-0.11,0.25-0.11,0.39v9.25h-4.5V11c0-0.14-0.04-0.27-0.11-0.39L7.75,7.54V6.5h8.5V7.79z M16.25,5h-8.5V3.5 h8.5V5z" /> <path - android:fillColor="#000000" - android:pathData="M9,22h6a0.76 0.76 ,0,0,0,0.75-0.75V11.46l1.89-3.07A0.77 0.77 ,0,0,0,17.75,8V2.75A0.76 0.76 ,0,0,0,17,2H7a0.76 0.76 ,0,0,0-0.75 0.75 v5a0.77 0.77 ,0,0,0,0.11 0.39 l1.89,3.07v10A0.76 0.76 ,0,0,0,9,22ZM16.25,3.5V5H7.75V3.5Zm-8.5,4v-1h8.5V7.79l-1.89,3.07a0.77 0.77 ,0,0,0-0.11 0.39 V20.5H9.75V11a0.77 0.77 ,0,0,0-0.11-0.39Z" /> - <path - android:fillColor="#000000" + android:fillColor="@android:color/white" android:pathData="M 12 12.75 C 12.6903559373 12.75 13.25 13.3096440627 13.25 14 C 13.25 14.6903559373 12.6903559373 15.25 12 15.25 C 11.3096440627 15.25 10.75 14.6903559373 10.75 14 C 10.75 13.3096440627 11.3096440627 12.75 12 12.75 Z" /> </vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_settings_bluetooth.xml b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_settings_bluetooth.xml new file mode 100644 index 000000000000..d2eb2d3fed26 --- /dev/null +++ b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_settings_bluetooth.xml @@ -0,0 +1,26 @@ +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M17.53,6.72l-4.75-4.75c-0.21-0.21-0.54-0.28-0.82-0.16C11.68,1.92,11.5,2.2,11.5,2.5v7.69L7.53,6.22 c-0.29-0.29-0.77-0.29-1.06,0s-0.29,0.77,0,1.06L11.19,12l-4.72,4.72c-0.29,0.29-0.29,0.77,0,1.06s0.77,0.29,1.06,0l3.97-3.97v7.69 c0,0.3,0.18,0.58,0.46,0.69c0.09,0.04,0.19,0.06,0.29,0.06c0.2,0,0.39-0.08,0.53-0.22l4.75-4.75c0.29-0.29,0.29-0.77,0-1.06 L13.31,12l4.22-4.22C17.82,7.49,17.82,7.01,17.53,6.72z M15.94,16.75L13,19.69v-5.88L15.94,16.75z M13,10.19V4.31l2.94,2.94 L13,10.19z" /> +</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_qs_auto_rotate.xml b/packages/overlays/IconPackRoundedSettingsOverlay/res/drawable/ic_airplanemode_active.xml index f98e2b8b2a24..e884edb4ad47 100644 --- a/packages/overlays/IconPackCircularSystemUIOverlay/res/drawable/ic_qs_auto_rotate.xml +++ b/packages/overlays/IconPackRoundedSettingsOverlay/res/drawable/ic_airplanemode_active.xml @@ -21,13 +21,6 @@ android:viewportHeight="24"> <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M3.5,6a0.5 0.5 ,0,0,0-0.5 0.5 V11H7.5a0.5 0.5 ,0,0,0,0-1H4.76l5-4.65a2.49,2.49,0,0,1,3.48 0.05 l4.95,4.95a0.49 0.49 ,0,1,0,0.7-0.7L13.91,4.7A3.47,3.47,0,0,0,9,4.62L4,9.35V6.5A0.5 0.5 ,0,0,0,3.5,6Z" /> - <path - android:fillColor="#000000" - android:pathData="M20.5,18a0.5 0.5 ,0,0,0,0.5-0.5V13H16.5a0.5 0.5 ,0,0,0,0,1h2.74l-5,4.65a2.49,2.49,0,0,1-3.48,0l-5-5a0.49 0.49 ,0,0,0-0.7 0.7 l4.94,5a3.47,3.47,0,0,0,4.87 0.08 l5-4.73V17.5A0.5 0.5 ,0,0,0,20.5,18Z" /> + android:fillColor="@android:color/white" + android:pathData="M2.52,16.17c0.32,0.23,0.74,0.31,1.11,0.19l5.87-1.84v3.87L8,19.52c-0.31,0.24-0.5,0.61-0.5,1v0.75 c0,0.69,0.56,1.25,1.25,1.25h6.5c0.69,0,1.25-0.56,1.25-1.25v-0.75c0-0.39-0.19-0.76-0.5-1l-1.5-1.12v-3.87l5.88,1.84 c0.38,0.12,0.79,0.05,1.11-0.19c0.32-0.23,0.51-0.61,0.51-1.01l0-1.84c0-0.63-0.34-1.21-0.89-1.52L14.5,8.06V4 c0-1.38-1.12-2.5-2.5-2.5S9.5,2.62,9.5,4v4.07L2.89,11.8C2.35,12.11,2,12.7,2,13.33l0,1.83C2.01,15.56,2.2,15.94,2.52,16.17z M3.63,13.11L11,8.94V4c0-0.55,0.45-1,1-1s1,0.45,1,1v4.94l7.37,4.17c0.08,0.04,0.13,0.13,0.13,0.22l0,1.5L13,12.48v6.66l2,1.5 v0.38H9v-0.38l2-1.5v-6.66l-7.5,2.34l0-1.49C3.5,13.24,3.55,13.15,3.63,13.11z" /> </vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_dnd.xml b/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_dnd.xml deleted file mode 100644 index a9a32ee5bce4..000000000000 --- a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_dnd.xml +++ /dev/null @@ -1,31 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M7.25,12.75h9.5a0.75 0.75 ,0,0,0,0-1.5H7.25a0.75 0.75 ,0,0,0,0,1.5Z" /> - <path - android:fillColor="#000000" - android:pathData="M12,22h0A10,10,0,0,0,22,12v0A10,10,0,1,0,12,22ZM12,3.5A8.51,8.51,0,0,1,20.5,12h0.75l-0.75,0A8.49,8.49,0,0,1,12,20.5h0a8.5,8.5,0,0,1,0-17Z" /> -</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_lockscreen_ime.xml b/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_lockscreen_ime.xml deleted file mode 100644 index 7897fa3a40a9..000000000000 --- a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_lockscreen_ime.xml +++ /dev/null @@ -1,55 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M3,21H21a2,2,0,0,0,2-2V6a2,2,0,0,0-2-2H3A2,2,0,0,0,1,6V19A2,2,0,0,0,3,21ZM2.5,6A0.51 0.51 ,0,0,1,3,5.5H21a0.51 0.51 ,0,0,1,0.5 0.5 V19a0.51 0.51 ,0,0,1-0.5 0.5 H3a0.51 0.51 ,0,0,1-0.5-0.5Z" /> - <path - android:fillColor="#000000" - android:pathData="M 9.5 8 L 10.5 8 Q 11 8 11 8.5 L 11 9.5 Q 11 10 10.5 10 L 9.5 10 Q 9 10 9 9.5 L 9 8.5 Q 9 8 9.5 8 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 5.5 8 L 6.5 8 Q 7 8 7 8.5 L 7 9.5 Q 7 10 6.5 10 L 5.5 10 Q 5 10 5 9.5 L 5 8.5 Q 5 8 5.5 8 Z" /> - <path - android:fillColor="#000000" - android:pathData="M8.75,17.5h6.5a0.75 0.75 ,0,0,0,0-1.5H8.75a0.75 0.75 ,0,0,0,0,1.5Z" /> - <path - android:fillColor="#000000" - android:pathData="M 13.5 8 L 14.5 8 Q 15 8 15 8.5 L 15 9.5 Q 15 10 14.5 10 L 13.5 10 Q 13 10 13 9.5 L 13 8.5 Q 13 8 13.5 8 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 9.5 12 L 10.5 12 Q 11 12 11 12.5 L 11 13.5 Q 11 14 10.5 14 L 9.5 14 Q 9 14 9 13.5 L 9 12.5 Q 9 12 9.5 12 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 5.5 12 L 6.5 12 Q 7 12 7 12.5 L 7 13.5 Q 7 14 6.5 14 L 5.5 14 Q 5 14 5 13.5 L 5 12.5 Q 5 12 5.5 12 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 13.5 12 L 14.5 12 Q 15 12 15 12.5 L 15 13.5 Q 15 14 14.5 14 L 13.5 14 Q 13 14 13 13.5 L 13 12.5 Q 13 12 13.5 12 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 17.5 8 L 18.5 8 Q 19 8 19 8.5 L 19 9.5 Q 19 10 18.5 10 L 17.5 10 Q 17 10 17 9.5 L 17 8.5 Q 17 8 17.5 8 Z" /> - <path - android:fillColor="#000000" - android:pathData="M 17.5 12 L 18.5 12 Q 19 12 19 12.5 L 19 13.5 Q 19 14 18.5 14 L 17.5 14 Q 17 14 17 13.5 L 17 12.5 Q 17 12 17.5 12 Z" /> -</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_qs_brightness_auto_on.xml b/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_qs_brightness_auto_on.xml deleted file mode 100644 index 9bf12749cca6..000000000000 --- a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_qs_brightness_auto_on.xml +++ /dev/null @@ -1,52 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M12.75,2.25V0.75a0.75 0.75 ,0,0,0-1.5,0v1.5a0.75 0.75 ,0,0,0,1.5,0Z" /> - <path - android:fillColor="#000000" - android:pathData="M4.57,5.64a0.79 0.79 ,0,0,0,1.07,0,0.77 0.77 ,0,0,0,0-1.07L4.58,3.51A0.76 0.76 ,0,0,0,3.51,4.58Z" /> - <path - android:fillColor="#000000" - android:pathData="M0.75,12.75h1.5a0.75 0.75 ,0,0,0,0-1.5H0.75a0.75 0.75 ,0,0,0,0,1.5Z" /> - <path - android:fillColor="#000000" - android:pathData="M3.51,20.49a0.76 0.76 ,0,0,0,1.07,0l1.06-1.06a0.76 0.76 ,0,1,0-1.07-1.07L3.51,19.42A0.77 0.77 ,0,0,0,3.51,20.49Z" /> - <path - android:fillColor="#000000" - android:pathData="M11.25,21.75v1.5a0.75 0.75 ,0,0,0,1.5,0v-1.5a0.75 0.75 ,0,0,0-1.5,0Z" /> - <path - android:fillColor="#000000" - android:pathData="M20,20.71a0.79 0.79 ,0,0,0,0.53-0.22 0.77 0.77,0,0,0,0-1.07l-1.06-1.06a0.76 0.76 ,0,0,0-1.07,1.07l1.06,1.06A0.79 0.79 ,0,0,0,20,20.71Z" /> - <path - android:fillColor="#000000" - android:pathData="M23.25,11.25h-1.5a0.75 0.75 ,0,0,0,0,1.5h1.5a0.75 0.75 ,0,0,0,0-1.5Z" /> - <path - android:fillColor="#000000" - android:pathData="M19.42,3.51,18.36,4.57a0.77 0.77 ,0,0,0,0,1.07 0.79 0.79,0,0,0,1.07,0l1.06-1.06a0.76 0.76 ,0,0,0-1.07-1.07Z" /> - <path - android:fillColor="#000000" - android:pathData="M14.85,17H17L13.11,7H10.87L7,17H9.15L10,14.61h4Zm-4.22-4.12,1.31-3.71h0.11l1.29,3.71Z" /> -</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_qs_cast_on.xml b/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_qs_cast_on.xml deleted file mode 100644 index 34ca21a76edc..000000000000 --- a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_qs_cast_on.xml +++ /dev/null @@ -1,40 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M22,18V5.5A1.5,1.5,0,0,0,20.5,4h-17A1.5,1.5,0,0,0,2,5.53V8.46a0.75 0.75 ,0,0,0,1.5,0v-3l17,0V18H13.15a0.75 0.75 ,0,1,0,0,1.5h7.38A1.5,1.5,0,0,0,22,18Z" /> - <path - android:fillColor="#000000" - android:pathData="M3.25,19.5a1.25,1.25,0,0,0,0-2.5h0a1.25,1.25,0,0,0,0,2.5Z" /> - <path - android:fillColor="#000000" - android:pathData="M2,11.25a0.76 0.76 ,0,0,0,0.75 0.75 A6.75,6.75,0,0,1,9.5,18.75a0.75 0.75 ,0,0,0,1.5,0A8.25,8.25,0,0,0,2.75,10.5 0.76 0.76,0,0,0,2,11.25Z" /> - <path - android:fillColor="#000000" - android:pathData="M2.75,15.25a3.5,3.5,0,0,1,3.5,3.5 0.75 0.75,0,0,0,1.5,0,5,5,0,0,0-5-5,0.75 0.75 ,0,0,0,0,1.5Z" /> - <path - android:fillColor="#000000" - android:pathData="M13.25,15a0.75 0.75 ,0,0,0,0,1.5h5a0.76 0.76 ,0,0,0,0.75-0.75v-8A0.76 0.76 ,0,0,0,18.25,7H5.75A0.76 0.76 ,0,0,0,5,7.75V8.5a0.75 0.75 ,0,0,0,1.5,0h11V15Z" /> -</vector>
\ No newline at end of file diff --git a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_settings_16dp.xml b/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_settings_16dp.xml deleted file mode 100644 index 4237323ca2f7..000000000000 --- a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_settings_16dp.xml +++ /dev/null @@ -1,31 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="16dp" - android:height="16dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M2.2,15.53,4,18.7a1.46,1.46,0,0,0,1.28 0.75 ,1.61,1.61,0,0,0,0.53-0.1l1.8-0.72a9,9,0,0,0,0.79 0.46 L8.7,21a1.45,1.45,0,0,0,1.45,1.27h3.7A1.47,1.47,0,0,0,15.31,21l0.27-1.91c0.26-0.14 0.52 -0.29 0.78 -0.46l1.8 0.72 a1.47,1.47,0,0,0,0.54 0.1 A1.43,1.43,0,0,0,20,18.75l1.86-3.22a1.47,1.47,0,0,0-0.37-1.86l-1.52-1.19c0-0.15,0-0.3,0-0.46s0-0.31,0-0.46l1.52-1.19a1.47,1.47,0,0,0,0.36-1.88L20,5.31a1.46,1.46,0,0,0-1.29-0.75,1.71,1.71,0,0,0-0.53 0.1 l-1.8 0.72 a9,9,0,0,0-0.79-0.46L15.29,3a1.45,1.45,0,0,0-1.45-1.27h-3.7A1.46,1.46,0,0,0,8.7,3L8.43,4.92c-0.26 0.14 -0.52 0.29 -0.78 0.46 L5.84,4.65a1.47,1.47,0,0,0-0.54-0.1,1.42,1.42,0,0,0-1.25 0.73 L2.2,8.47a1.44,1.44,0,0,0,0.37,1.88l1.52,1.19c0,0.15,0,0.31,0,0.46s0,0.31,0,0.46L2.56,13.65A1.48,1.48,0,0,0,2.2,15.53ZM5,13.64l0.63-0.49,0-0.79c0-0.12,0-0.23,0-0.36s0-0.24,0-0.36l0-0.79L5,10.36,3.52,9.19,5.33,6.06l1.76 0.71 0.73 0.29 0.65-0.42a6.59,6.59,0,0,1,0.67-0.4l0.67-0.36 0.11 -0.75 0.26 -1.87h3.63l0.27,1.87 0.1 0.77 0.69 0.35a6,6,0,0,1,0.66 0.39 l0.65 0.42 0.73-0.29,1.76-0.71L20.5,9.21,19,10.38l-0.63 0.49 0.05 0.79 c0,0.12,0,0.23,0,0.36s0,0.24,0,0.36l-0.05 0.79 0.63 0.49 ,1.48,1.17L18.68,18l-1.76-0.7L16.19,17l-0.65 0.42 a6.59,6.59,0,0,1-0.67 0.4 l-0.67 0.36 -0.11 0.75 -0.26,1.84H10.18l-0.27-1.87-0.1-0.77-0.69-0.35a6,6,0,0,1-0.66-0.39L7.81,17l-0.73 0.29 L5.33,18,3.52,14.8Z" /> - <path - android:fillColor="#000000" - android:pathData="M12,16a4,4,0,1,0-4-4A4,4,0,0,0,12,16Zm0-6.5A2.5,2.5,0,1,1,9.5,12,2.5,2.5,0,0,1,12,9.5Z" /> -</vector> diff --git a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_signal_airplane.xml b/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_signal_airplane.xml deleted file mode 100644 index 2d36a3986107..000000000000 --- a/packages/overlays/IconPackRoundedSystemUIOverlay/res/drawable/ic_signal_airplane.xml +++ /dev/null @@ -1,28 +0,0 @@ -<!-- -/** - * Copyright (c) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - - <path - android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" /> - <path - android:fillColor="#000000" - android:pathData="M2.52,16.17a1.25,1.25,0,0,0,1.11 0.19 L9.5,14.52v3.87L8,19.52a1.26,1.26,0,0,0-0.5,1v0.75a1.25,1.25,0,0,0,1.25,1.25h6.5a1.25,1.25,0,0,0,1.25-1.25v-0.75a1.26,1.26,0,0,0-0.5-1l-1.5-1.13V14.52l5.88,1.84a1.23,1.23,0,0,0,1.11-0.19,1.25,1.25,0,0,0,0.51-1V13.33a1.74,1.74,0,0,0-0.89-1.52L14.5,8.06V3.75a2.5,2.5,0,0,0-5,0V8.06L2.89,11.8A1.78,1.78,0,0,0,2,13.33v1.83A1.25,1.25,0,0,0,2.52,16.17Zm1.11-3.06L11,8.94V3.75a1,1,0,0,1,2,0V8.94l7.37,4.17a0.26 0.26 ,0,0,1,0.13 0.22 v1.49L13,12.48v6.66l2,1.5V21H9v-0.38l2-1.5V12.48L3.51,14.82V13.33A0.25 0.25 ,0,0,1,3.63,13.11Z" /> -</vector>
\ No newline at end of file diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml index 48c37695d4a1..23d2e7babdcc 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml +++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml @@ -22,4 +22,15 @@ 1: 2 button mode (back, home buttons + swipe up for overview) 2: gestures only for back, home and overview --> <integer name="config_navBarInteractionMode">2</integer> -</resources>
\ No newline at end of file + + <!-- Controls whether the nav bar can move from the bottom to the side in landscape. + Only applies if the device display is not square. --> + <bool name="config_navBarCanMove">false</bool> + + <!-- Controls whether the navigation bar lets through taps. --> + <bool name="config_navBarTapThrough">true</bool> + + <!-- Controls the size of the back gesture inset. --> + <dimen name="config_backGestureInset">48dp</dimen> + +</resources> diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml index 721d11be8cb5..c839b2c2eb76 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml +++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml @@ -19,6 +19,8 @@ <resources> <!-- Height of the bottom navigation / system bar. --> <dimen name="navigation_bar_height">16dp</dimen> + <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height --> + <dimen name="navigation_bar_height_landscape">16dp</dimen> <!-- Width of the navigation bar when it is placed vertically on the screen --> <dimen name="navigation_bar_width">16dp</dimen> <!-- Height of the bottom navigation / system bar. --> diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index f52b94ff9ea1..ce39a709e637 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -7158,6 +7158,10 @@ message MetricsEvent { // OPEN: Settings > System > Aware > Info dialog DIALOG_AWARE_STATUS = 1701; + + // Open: Settings > app > bubble settings > confirmation dialog + DIALOG_APP_BUBBLE_SETTINGS = 1702; + // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto index 1ce0c5292ab8..af77df60638a 100644 --- a/proto/src/wifi.proto +++ b/proto/src/wifi.proto @@ -1752,6 +1752,9 @@ message WifiIsUnusableEvent { // Firmware generated an alert TYPE_FIRMWARE_ALERT = 4; + + // IP Manager lost reachability to network neighbors + TYPE_IP_REACHABILITY_LOST = 5; } // What event triggered WifiIsUnusableEvent. @@ -2012,6 +2015,10 @@ message WifiUsabilityStatsEntry { // Whether the primary registered cell of current entry is same as that of previous entry optional bool is_same_registered_cell = 33; + + // The device mobility state + optional DeviceMobilityStatePnoScanStats.DeviceMobilityState + device_mobility_state = 34; } message WifiUsabilityStats { @@ -2041,6 +2048,9 @@ message WifiUsabilityStats { // Firmware generated an alert TYPE_FIRMWARE_ALERT = 4; + + // IP Manager lost reachability to network neighbors + TYPE_IP_REACHABILITY_LOST = 5; } // The current wifi usability state diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 904817e1763e..3c69bb73f893 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2552,6 +2552,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub pw.append(", autoclickEnabled=" + userState.mIsAutoclickEnabled); pw.append(", nonInteractiveUiTimeout=" + userState.mNonInteractiveUiTimeout); pw.append(", interactiveUiTimeout=" + userState.mInteractiveUiTimeout); + pw.append(", installedServiceCount=" + userState.mInstalledServices.size()); if (mUiAutomationManager.isUiAutomationRunningLocked()) { pw.append(", "); mUiAutomationManager.dumpUiAutomationService(fd, pw, args); @@ -2559,7 +2560,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } pw.append("}"); pw.println(); - pw.append(" services:{"); + pw.append(" Bound services:{"); final int serviceCount = userState.mBoundServices.size(); for (int j = 0; j < serviceCount; j++) { if (j > 0) { @@ -2570,6 +2571,30 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityServiceConnection service = userState.mBoundServices.get(j); service.dump(fd, pw, args); } + pw.println("}"); + pw.append(" Enabled services:{"); + Iterator<ComponentName> it = userState.mEnabledServices.iterator(); + if (it.hasNext()) { + ComponentName componentName = it.next(); + pw.append(componentName.toShortString()); + while (it.hasNext()) { + componentName = it.next(); + pw.append(", "); + pw.append(componentName.toShortString()); + } + } + pw.println("}"); + pw.append(" Binding services:{"); + it = userState.mBindingServices.iterator(); + if (it.hasNext()) { + ComponentName componentName = it.next(); + pw.append(componentName.toShortString()); + while (it.hasNext()) { + componentName = it.next(); + pw.append(", "); + pw.append(componentName.toShortString()); + } + } pw.println("}]"); pw.println(); } @@ -2585,6 +2610,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub pw.append(window.toString()); pw.append(']'); } + pw.println(); } } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 7020e7ea6965..fdc3567aa002 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -42,6 +42,7 @@ import android.content.pm.UserInfo; import android.database.ContentObserver; import android.graphics.Rect; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.Parcelable; @@ -61,6 +62,7 @@ import android.util.ArrayMap; import android.util.LocalLog; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager.SmartSuggestionMode; @@ -72,6 +74,8 @@ import android.view.autofill.IAutoFillManagerClient; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AbstractRemoteService; +import com.android.internal.infra.GlobalWhitelistState; +import com.android.internal.infra.WhitelistHelper; import com.android.internal.os.IResultReceiver; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; @@ -146,6 +150,7 @@ public final class AutofillManagerService private final LocalLog mWtfHistory = new LocalLog(50); private final AutofillCompatState mAutofillCompatState = new AutofillCompatState(); + private final LocalService mLocalService = new LocalService(); private final ActivityManagerInternal mAm; @@ -178,6 +183,8 @@ public final class AutofillManagerService @GuardedBy("mLock") int mAugmentedServiceRequestTimeoutMs; + final AugmentedAutofillState mAugmentedAutofillState = new AugmentedAutofillState(); + public AutofillManagerService(Context context) { super(context, new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE), @@ -187,7 +194,7 @@ public final class AutofillManagerService DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_AUTOFILL, ActivityThread.currentApplication().getMainExecutor(), - (namespace, key, value) -> onDeviceConfigChange(key, value)); + (namespace, key, value) -> onDeviceConfigChange(key)); setLogLevelFromSettings(); setMaxPartitionsFromSettings(); @@ -201,15 +208,20 @@ public final class AutofillManagerService mAugmentedAutofillResolver = new FrameworkResourcesServiceNameResolver(getContext(), com.android.internal.R.string.config_defaultAugmentedAutofillService); mAugmentedAutofillResolver.setOnTemporaryServiceNameChangedCallback( - (u, s) -> getServiceForUserLocked(u).updateRemoteAugmentedAutofillService()); + (u, s, t) -> onAugmentedServiceNameChanged(u, s, t)); if (mSupportedSmartSuggestionModes != AutofillManager.FLAG_SMART_SUGGESTION_OFF) { - // Must eager load the services so they bind to the augmented autofill service final UserManager um = getContext().getSystemService(UserManager.class); final List<UserInfo> users = um.getUsers(); for (int i = 0; i < users.size(); i++) { final int userId = users.get(i).id; + // Must eager load the services so they bind to the augmented autofill service getServiceForUserLocked(userId); + + // And also set the global state + mAugmentedAutofillState.setServiceInfo(userId, + mAugmentedAutofillResolver.getServiceName(userId), + mAugmentedAutofillResolver.isTemporary(userId)); } } } @@ -258,7 +270,7 @@ public final class AutofillManagerService } } - private void onDeviceConfigChange(@NonNull String key, @Nullable String value) { + private void onDeviceConfigChange(@NonNull String key) { switch (key) { case AutofillManager.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES: case AutofillManager.DEVICE_CONFIG_AUGMENTED_SERVICE_IDLE_UNBIND_TIMEOUT: @@ -270,6 +282,14 @@ public final class AutofillManagerService } } + private void onAugmentedServiceNameChanged(@UserIdInt int userId, @Nullable String serviceName, + boolean isTemporary) { + mAugmentedAutofillState.setServiceInfo(userId, serviceName, isTemporary); + synchronized (mLock) { + getServiceForUserLocked(userId).updateRemoteAugmentedAutofillService(); + } + } + @Override // from AbstractMasterSystemService protected AutofillManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId, boolean disabled) { @@ -783,15 +803,7 @@ public final class AutofillManagerService final boolean compatModeEnabled = mAutofillCompatState.isCompatibilityModeRequested( packageName, versionCode, userId); final AutofillOptions options = new AutofillOptions(loggingLevel, compatModeEnabled); - - synchronized (mLock) { - final AutofillManagerServiceImpl service = - getServiceForUserLocked(UserHandle.getCallingUserId()); - if (service != null) { - service.setAugmentedAutofillWhitelistLocked(options, packageName); - } - } - + mAugmentedAutofillState.injectAugmentedAutofillInfo(options, userId, packageName); return options; } } @@ -934,6 +946,89 @@ public final class AutofillManagerService } } + /** + * Augmented autofill metadata associated with all services. + * + * <p>This object is defined here instead of on each {@link AutofillManagerServiceImpl} because + * it cannot hold a lock on the main lock when + * {@link AugmentedAutofillState#injectAugmentedAutofillInfo(AutofillOptions, int, String)} + * is called by external services. + */ + static final class AugmentedAutofillState extends GlobalWhitelistState { + + @GuardedBy("mGlobalWhitelistStateLock") + private final SparseArray<String> mServicePackages = new SparseArray<>(); + @GuardedBy("mGlobalWhitelistStateLock") + private final SparseBooleanArray mTemporaryServices = new SparseBooleanArray(); + + private void setServiceInfo(@UserIdInt int userId, @Nullable String serviceName, + boolean isTemporary) { + synchronized (mGlobalWhitelistStateLock) { + if (isTemporary) { + mTemporaryServices.put(userId, true); + } else { + mTemporaryServices.delete(userId); + } + if (serviceName != null) { + final ComponentName componentName = + ComponentName.unflattenFromString(serviceName); + if (componentName == null) { + Slog.w(TAG, "setServiceInfo(): invalid name: " + serviceName); + mServicePackages.remove(userId); + } else { + mServicePackages.put(userId, componentName.getPackageName()); + } + } else { + mServicePackages.remove(userId); + } + } + } + + public void injectAugmentedAutofillInfo(@NonNull AutofillOptions options, + @UserIdInt int userId, @NonNull String packageName) { + synchronized (mGlobalWhitelistStateLock) { + if (mWhitelisterHelpers == null) return; + final WhitelistHelper helper = mWhitelisterHelpers.get(userId); + if (helper != null) { + options.augmentedAutofillEnabled = helper.isWhitelisted(packageName); + options.whitelistedActivitiesForAugmentedAutofill = helper + .getWhitelistedComponents(packageName); + } + } + } + + @Override + public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) { + synchronized (mGlobalWhitelistStateLock) { + if (!super.isWhitelisted(userId, componentName)) return false; + + if (Build.IS_USER && mTemporaryServices.get(userId)) { + final String packageName = componentName.getPackageName(); + if (!packageName.equals(mServicePackages.get(userId))) { + Slog.w(TAG, "Ignoring package " + packageName + " for augmented autofill " + + "while using temporary service " + mServicePackages.get(userId)); + return false; + } + } + } + return true; + } + + @Override + public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { + super.dump(prefix, pw); + + synchronized (mGlobalWhitelistStateLock) { + if (mServicePackages.size() > 0) { + pw.print(prefix); pw.print("Service packages: "); pw.println(mServicePackages); + } + if (mTemporaryServices.size() > 0) { + pw.print(prefix); pw.print("Temp services: "); pw.println(mTemporaryServices); + } + } + } + } + final class AutoFillManagerServiceStub extends IAutoFillManager.Stub { @Override public void addClient(IAutoFillManagerClient client, ComponentName componentName, @@ -1370,6 +1465,8 @@ public final class AutofillManagerService pw.println(); pw.println("WTF history:"); pw.println(); mWtfHistory.reverseDump(fd, pw, args); } + pw.println("Augmented Autofill State: "); + mAugmentedAutofillState.dump(prefix, pw); } } finally { sDebug = realDebug; diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index fe540bf3a8cf..4bd6fbd39de4 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -31,7 +31,6 @@ import android.annotation.Nullable; import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; -import android.content.AutofillOptions; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -40,7 +39,6 @@ import android.graphics.Rect; import android.metrics.LogMaker; import android.os.AsyncTask; import android.os.Binder; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -76,7 +74,6 @@ import android.view.autofill.IAutoFillManagerClient; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; -import com.android.internal.infra.WhitelistHelper; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.LocalServices; @@ -170,12 +167,6 @@ final class AutofillManagerServiceImpl @Nullable private ServiceInfo mRemoteAugmentedAutofillServiceInfo; - /** - * List of packages/activities that are whitelisted to be trigger augmented autofill. - */ - @GuardedBy("mLock") - private final WhitelistHelper mAugmentedWhitelistHelper = new WhitelistHelper(); - AutofillManagerServiceImpl(AutofillManagerService master, Object lock, LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui, AutofillCompatState autofillCompatState, @@ -951,8 +942,6 @@ final class AutofillManagerServiceImpl pw.println(mRemoteAugmentedAutofillServiceInfo); } - mAugmentedWhitelistHelper.dump(prefix, "Augmented autofill whitelist", pw); - pw.print(prefix); pw.print("Field classification enabled: "); pw.println(isFieldClassificationEnabledLocked()); pw.print(prefix); pw.print("Compat pkgs: "); @@ -1234,27 +1223,7 @@ final class AutofillManagerServiceImpl @GuardedBy("mLock") boolean isWhitelistedForAugmentedAutofillLocked(@NonNull ComponentName componentName) { - if (Build.IS_USER && mMaster.mAugmentedAutofillResolver.isTemporary(mUserId)) { - final String serviceName = mMaster.mAugmentedAutofillResolver.getServiceName(mUserId); - final ComponentName component = ComponentName.unflattenFromString(serviceName); - final String servicePackage = component == null ? null : component.getPackageName(); - final String packageName = componentName.getPackageName(); - if (!packageName.equals(servicePackage)) { - Slog.w(TAG, "Ignoring package " + packageName + " for augmented autofill while " - + "using temporary service " + servicePackage); - return false; - } - } - - return mAugmentedWhitelistHelper.isWhitelisted(componentName); - } - - @GuardedBy("mLock") - void setAugmentedAutofillWhitelistLocked(@NonNull AutofillOptions options, - @NonNull String packageName) { - options.augmentedAutofillEnabled = mAugmentedWhitelistHelper.isWhitelisted(packageName); - options.whitelistedActivitiesForAugmentedAutofill = mAugmentedWhitelistHelper - .getWhitelistedComponents(packageName); + return mMaster.mAugmentedAutofillState.isWhitelisted(mUserId, componentName); } /** @@ -1268,7 +1237,7 @@ final class AutofillManagerServiceImpl if (mMaster.verbose) { Slog.v(TAG, "whitelisting packages: " + packages + "and activities: " + components); } - mAugmentedWhitelistHelper.setWhitelist(packages, components); + mMaster.mAugmentedAutofillState.setWhitelist(mUserId, packages, components); } } @@ -1280,7 +1249,7 @@ final class AutofillManagerServiceImpl if (mMaster.verbose) { Slog.v(TAG, "resetting augmented autofill whitelist"); } - whitelistForAugmentedAutofillPackages(null, null); + mMaster.mAugmentedAutofillState.resetWhitelist(mUserId); } private void sendStateToClients(boolean resetClient) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index c62794d3e3d2..0402b8fb9285 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -2540,13 +2540,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState boolean saveOnFinish = true; final SaveInfo saveInfo = response.getSaveInfo(); final AutofillId saveTriggerId; + final int flags; if (saveInfo != null) { saveTriggerId = saveInfo.getTriggerId(); if (saveTriggerId != null) { writeLog(MetricsEvent.AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION); } - mSaveOnAllViewsInvisible = - (saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0; + flags = saveInfo.getFlags(); + mSaveOnAllViewsInvisible = (flags & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0; // We only need to track views if we want to save once they become invisible. if (mSaveOnAllViewsInvisible) { @@ -2561,11 +2562,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Collections.addAll(trackedViews, saveInfo.getOptionalIds()); } } - if ((saveInfo.getFlags() & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) { + if ((flags & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) { saveOnFinish = false; } } else { + flags = 0; saveTriggerId = null; } @@ -2592,7 +2594,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState try { if (sVerbose) { Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds - + " triggerId: " + saveTriggerId + " saveOnFinish:" + saveOnFinish); + + " triggerId: " + saveTriggerId + " saveOnFinish:" + saveOnFinish + + " flags: " + flags + " hasSaveInfo: " + (saveInfo != null)); } mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible, saveOnFinish, toArray(fillableIds), saveTriggerId); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 3865b2779466..a7404bc63e2a 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -72,7 +72,9 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; +import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.wm.ActivityTaskManagerInternal; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -84,6 +86,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -519,6 +522,11 @@ public class CompanionDeviceManagerService extends SystemService implements Bind if (size(old) == size(associations)) return; Set<Association> finalAssociations = associations; + Set<String> companionAppPackages = new HashSet<>(); + for (Association association : finalAssociations) { + companionAppPackages.add(association.companionAppPackage); + } + file.write((out) -> { XmlSerializer xml = Xml.newSerializer(); try { @@ -542,6 +550,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } }); + ActivityTaskManagerInternal atmInternal = LocalServices.getService( + ActivityTaskManagerInternal.class); + atmInternal.setCompanionAppPackages(userId, companionAppPackages); } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index b2760e037f44..9b02c4e72cfc 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -18,6 +18,7 @@ package com.android.server.contentcapture; import static android.Manifest.permission.MANAGE_CONTENT_CAPTURE; import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE; +import static android.view.contentcapture.ContentCaptureHelper.toList; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_OK; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION; @@ -40,6 +41,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -50,9 +52,12 @@ import android.os.UserManager; import android.provider.DeviceConfig; import android.provider.Settings; import android.service.contentcapture.ActivityEvent.ActivityEventType; +import android.util.ArraySet; import android.util.LocalLog; import android.util.Slog; +import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.view.contentcapture.ContentCaptureCondition; import android.view.contentcapture.ContentCaptureHelper; import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.IContentCaptureManager; @@ -60,6 +65,7 @@ import android.view.contentcapture.UserDataRemovalRequest; import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.AbstractRemoteService; +import com.android.internal.infra.GlobalWhitelistState; import com.android.internal.os.IResultReceiver; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; @@ -117,10 +123,13 @@ public final class ContentCaptureManagerService extends @GuardedBy("mLock") int mDevCfgLogHistorySize; @GuardedBy("mLock") int mDevCfgIdleUnbindTimeoutMs; + final GlobalContentCaptureOptions mGlobalContentCaptureOptions = + new GlobalContentCaptureOptions(); + public ContentCaptureManagerService(@NonNull Context context) { super(context, new FrameworkResourcesServiceNameResolver(context, com.android.internal.R.string.config_defaultContentCaptureService), - UserManager.DISALLOW_CONTENT_CAPTURE, /* refreshServiceOnPackageUpdate=*/ false); + UserManager.DISALLOW_CONTENT_CAPTURE, /* refreshServiceOnPackageUpdate= */ false); DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_CONTENT_CAPTURE, ActivityThread.currentApplication().getMainExecutor(), (namespace, key, value) -> onDeviceConfigChange(key, value)); @@ -136,12 +145,12 @@ public final class ContentCaptureManagerService extends mRequestsHistory = null; } - // Sets which services are disabled by settings final UserManager um = getContext().getSystemService(UserManager.class); final List<UserInfo> users = um.getUsers(); for (int i = 0; i < users.size(); i++) { final int userId = users.get(i).id; final boolean disabled = !isEnabledBySettings(userId); + // Sets which services are disabled by settings if (disabled) { Slog.i(mTag, "user " + userId + " disabled by settings"); if (mDisabledBySettings == null) { @@ -149,6 +158,10 @@ public final class ContentCaptureManagerService extends } mDisabledBySettings.put(userId, true); } + // Sets the global options for the service. + mGlobalContentCaptureOptions.setServiceInfo(userId, + mServiceNameResolver.getServiceName(userId), + mServiceNameResolver.isTemporary(userId)); } } @@ -188,6 +201,14 @@ public final class ContentCaptureManagerService extends } @Override // from AbstractMasterSystemService + protected void onServiceNameChanged(@UserIdInt int userId, @NonNull String serviceName, + boolean isTemporary) { + mGlobalContentCaptureOptions.setServiceInfo(userId, serviceName, isTemporary); + + super.onServiceNameChanged(userId, serviceName, isTemporary); + } + + @Override // from AbstractMasterSystemService protected void enforceCallingPermissionForManagement() { getContext().enforceCallingPermission(MANAGE_CONTENT_CAPTURE, mTag); } @@ -429,23 +450,16 @@ public final class ContentCaptureManagerService extends } @GuardedBy("mLock") - private boolean assertCalledByServiceLocked(@NonNull String methodName, @UserIdInt int userId, - int callingUid, @NonNull IResultReceiver result) { - final boolean isService = isCalledByServiceLocked(methodName, userId, callingUid); - if (isService) return true; - - try { - result.send(RESULT_CODE_SECURITY_EXCEPTION, /* resultData= */ null); - } catch (RemoteException e) { - Slog.w(mTag, "Unable to send isContentCaptureFeatureEnabled(): " + e); + private void assertCalledByServiceLocked(@NonNull String methodName) { + if (!isCalledByServiceLocked(methodName)) { + throw new SecurityException("caller is not user's ContentCapture service"); } - return false; } @GuardedBy("mLock") - private boolean isCalledByServiceLocked(@NonNull String methodName, @UserIdInt int userId, - int callingUid) { - + private boolean isCalledByServiceLocked(@NonNull String methodName) { + final int userId = UserHandle.getCallingUserId(); + final int callingUid = Binder.getCallingUid(); final String serviceName = mServiceNameResolver.getServiceName(userId); if (serviceName == null) { Slog.e(mTag, methodName + ": called by UID " + callingUid @@ -453,7 +467,7 @@ public final class ContentCaptureManagerService extends return false; } - final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); + final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); if (serviceComponent == null) { Slog.w(mTag, methodName + ": invalid service name: " + serviceName); return false; @@ -478,6 +492,27 @@ public final class ContentCaptureManagerService extends return true; } + /** + * Executes the given {@code runnable} and if it throws a {@link SecurityException}, + * send it back to the receiver. + * + * @return whether the exception was thrown or not. + */ + private boolean throwsSecurityException(@NonNull IResultReceiver result, + @NonNull Runnable runable) { + try { + runable.run(); + return false; + } catch (SecurityException e) { + try { + result.send(RESULT_CODE_SECURITY_EXCEPTION, bundleFor(e.getMessage())); + } catch (RemoteException e2) { + Slog.w(mTag, "Unable to send security exception (" + e + "): ", e2); + } + } + return true; + } + @Override // from AbstractMasterSystemService protected void dumpLocked(String prefix, PrintWriter pw) { super.dumpLocked(prefix, pw); @@ -496,13 +531,15 @@ public final class ContentCaptureManagerService extends pw.print(prefix2); pw.print("logHistorySize: "); pw.println(mDevCfgLogHistorySize); pw.print(prefix2); pw.print("idleUnbindTimeoutMs: "); pw.println(mDevCfgIdleUnbindTimeoutMs); + pw.print(prefix); pw.println("Global Options:"); + mGlobalContentCaptureOptions.dump(prefix2, pw); } final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub { @Override public void startSession(@NonNull IBinder activityToken, - @NonNull ComponentName componentName, @NonNull String sessionId, int flags, + @NonNull ComponentName componentName, int sessionId, int flags, @NonNull IResultReceiver result) { Preconditions.checkNotNull(activityToken); Preconditions.checkNotNull(sessionId); @@ -519,7 +556,7 @@ public final class ContentCaptureManagerService extends } @Override - public void finishSession(@NonNull String sessionId) { + public void finishSession(int sessionId) { Preconditions.checkNotNull(sessionId); final int userId = UserHandle.getCallingUserId(); @@ -547,6 +584,8 @@ public final class ContentCaptureManagerService extends @Override public void removeUserData(@NonNull UserDataRemovalRequest request) { Preconditions.checkNotNull(request); + assertCalledByPackageOwner(request.getPackageName()); + final int userId = UserHandle.getCallingUserId(); synchronized (mLock) { final ContentCapturePerUserService service = getServiceForUserLocked(userId); @@ -556,13 +595,14 @@ public final class ContentCaptureManagerService extends @Override public void isContentCaptureFeatureEnabled(@NonNull IResultReceiver result) { - final int userId = UserHandle.getCallingUserId(); boolean enabled; synchronized (mLock) { - final boolean isService = assertCalledByServiceLocked( - "isContentCaptureFeatureEnabled()", userId, Binder.getCallingUid(), result); - if (!isService) return; + if (throwsSecurityException(result, + () -> assertCalledByServiceLocked("isContentCaptureFeatureEnabled()"))) { + return; + } + final int userId = UserHandle.getCallingUserId(); enabled = !mDisabledByDeviceConfig && !isDisabledBySettingsLocked(userId); } try { @@ -574,15 +614,8 @@ public final class ContentCaptureManagerService extends @Override public void getServiceSettingsActivity(@NonNull IResultReceiver result) { - try { - enforceCallingPermissionForManagement(); - } catch (SecurityException e) { - try { - result.send(RESULT_CODE_SECURITY_EXCEPTION, bundleFor(e.getMessage())); - } catch (RemoteException e2) { - Slog.w(mTag, "Unable to send getServiceSettingsIntent() exception: " + e2); - return; - } + if (throwsSecurityException(result, () -> enforceCallingPermissionForManagement())) { + return; } final int userId = UserHandle.getCallingUserId(); @@ -600,13 +633,34 @@ public final class ContentCaptureManagerService extends } @Override + public void getContentCaptureConditions(@NonNull String packageName, + @NonNull IResultReceiver result) { + if (throwsSecurityException(result, () -> assertCalledByPackageOwner(packageName))) { + return; + } + + final int userId = UserHandle.getCallingUserId(); + final ArrayList<ContentCaptureCondition> conditions; + synchronized (mLock) { + final ContentCapturePerUserService service = getServiceForUserLocked(userId); + conditions = service == null ? null + : toList(service.getContentCaptureConditionsLocked(packageName)); + } + try { + result.send(RESULT_CODE_OK, bundleFor(conditions)); + } catch (RemoteException e) { + Slog.w(mTag, "Unable to send getServiceComponentName(): " + e); + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), mTag, pw)) return; boolean showHistory = true; if (args != null) { for (String arg : args) { - switch(arg) { + switch (arg) { case "--no-history": showHistory = false; break; @@ -670,13 +724,7 @@ public final class ContentCaptureManagerService extends @Override public ContentCaptureOptions getOptionsForPackage(int userId, @NonNull String packageName) { - synchronized (mLock) { - final ContentCapturePerUserService service = peekServiceForUserLocked(userId); - if (service != null) { - return service.getOptionsForPackageLocked(packageName); - } - } - return null; + return mGlobalContentCaptureOptions.getOptions(userId, packageName); } @Override @@ -690,4 +738,92 @@ public final class ContentCaptureManagerService extends } } } + + /** + * Content capture options associated with all services. + * + * <p>This object is defined here instead of on each {@link ContentCapturePerUserService} + * because it cannot hold a lock on the main lock when + * {@link GlobalContentCaptureOptions#getOptions(int, String)} is called by external services. + */ + final class GlobalContentCaptureOptions extends GlobalWhitelistState { + + @GuardedBy("mGlobalWhitelistStateLock") + private final SparseArray<String> mServicePackages = new SparseArray<>(); + @GuardedBy("mGlobalWhitelistStateLock") + private final SparseBooleanArray mTemporaryServices = new SparseBooleanArray(); + + private void setServiceInfo(@UserIdInt int userId, @Nullable String serviceName, + boolean isTemporary) { + synchronized (mGlobalWhitelistStateLock) { + if (isTemporary) { + mTemporaryServices.put(userId, true); + } else { + mTemporaryServices.delete(userId); + } + if (serviceName != null) { + final ComponentName componentName = + ComponentName.unflattenFromString(serviceName); + if (componentName == null) { + Slog.w(mTag, "setServiceInfo(): invalid name: " + serviceName); + mServicePackages.remove(userId); + } else { + mServicePackages.put(userId, componentName.getPackageName()); + } + } else { + mServicePackages.remove(userId); + } + } + } + + @Nullable + @GuardedBy("mGlobalWhitelistStateLock") + public ContentCaptureOptions getOptions(@UserIdInt int userId, + @NonNull String packageName) { + synchronized (mGlobalWhitelistStateLock) { + if (!isWhitelisted(userId, packageName)) { + if (packageName.equals(mServicePackages.get(userId))) { + if (verbose) Slog.v(mTag, "getOptionsForPackage() lite for " + packageName); + return new ContentCaptureOptions(mDevCfgLoggingLevel); + } + if (verbose) { + Slog.v(mTag, "getOptionsForPackage(" + packageName + "): not whitelisted"); + } + return null; + } + + final ArraySet<ComponentName> whitelistedComponents = + getWhitelistedComponents(userId, packageName); + if (Build.IS_USER && mServiceNameResolver.isTemporary(userId)) { + if (!packageName.equals(mServicePackages.get(userId))) { + Slog.w(mTag, "Ignoring package " + packageName + + " while using temporary service " + mServicePackages.get(userId)); + return null; + } + } + final ContentCaptureOptions options = new ContentCaptureOptions(mDevCfgLoggingLevel, + mDevCfgMaxBufferSize, mDevCfgIdleFlushingFrequencyMs, + mDevCfgTextChangeFlushingFrequencyMs, mDevCfgLogHistorySize, + whitelistedComponents); + if (verbose) { + Slog.v(mTag, "getOptionsForPackage(" + packageName + "): " + options); + } + return options; + } + } + + @Override + public void dump(@NonNull String prefix, @NonNull PrintWriter pw) { + super.dump(prefix, pw); + + synchronized (mGlobalWhitelistStateLock) { + if (mServicePackages.size() > 0) { + pw.print(prefix); pw.print("Service packages: "); pw.println(mServicePackages); + } + if (mTemporaryServices.size() > 0) { + pw.print(prefix); pw.print("Temp services: "); pw.println(mTemporaryServices); + } + } + } + } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index f0c6f7e7b0b8..564952697250 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -17,6 +17,7 @@ package com.android.server.contentcapture; import static android.service.contentcapture.ContentCaptureService.setClientState; +import static android.view.contentcapture.ContentCaptureSession.NO_SESSION_ID; import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED; import static android.view.contentcapture.ContentCaptureSession.STATE_DUPLICATED_ID; import static android.view.contentcapture.ContentCaptureSession.STATE_INTERNAL_ERROR; @@ -54,6 +55,8 @@ import android.service.contentcapture.SnapshotData; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import android.util.SparseArray; +import android.view.contentcapture.ContentCaptureCondition; import android.view.contentcapture.UserDataRemovalRequest; import com.android.internal.annotations.GuardedBy; @@ -78,8 +81,7 @@ final class ContentCapturePerUserService private static final String TAG = ContentCapturePerUserService.class.getSimpleName(); @GuardedBy("mLock") - private final ArrayMap<String, ContentCaptureServerSession> mSessions = - new ArrayMap<>(); + private final SparseArray<ContentCaptureServerSession> mSessions = new SparseArray<>(); /** * Reference to the remote service. @@ -101,6 +103,13 @@ final class ContentCapturePerUserService private final WhitelistHelper mWhitelistHelper = new WhitelistHelper(); /** + * List of conditions keyed by package. + */ + @GuardedBy("mLock") + private final ArrayMap<String, ArraySet<ContentCaptureCondition>> mConditionsByPkg = + new ArrayMap<>(); + + /** * When {@code true}, remote service died but service state is kept so it's restored after * the system re-binds to it. */ @@ -226,9 +235,8 @@ final class ContentCapturePerUserService // TODO(b/119613670): log metrics @GuardedBy("mLock") public void startSessionLocked(@NonNull IBinder activityToken, - @NonNull ActivityPresentationInfo activityPresentationInfo, - @NonNull String sessionId, int uid, int flags, - @NonNull IResultReceiver clientReceiver) { + @NonNull ActivityPresentationInfo activityPresentationInfo, int sessionId, int uid, + int flags, @NonNull IResultReceiver clientReceiver) { if (activityPresentationInfo == null) { Slog.w(TAG, "basic activity info is null"); setClientState(clientReceiver, STATE_DISABLED | STATE_INTERNAL_ERROR, @@ -238,7 +246,8 @@ final class ContentCapturePerUserService final int taskId = activityPresentationInfo.taskId; final int displayId = activityPresentationInfo.displayId; final ComponentName componentName = activityPresentationInfo.componentName; - final boolean whiteListed = isWhitelistedLocked(componentName); + final boolean whiteListed = mMaster.mGlobalContentCaptureOptions.isWhitelisted(mUserId, + componentName); final ComponentName serviceComponentName = getServiceComponentName(); final boolean enabled = isEnabledLocked(); if (mMaster.mRequestsHistory != null) { @@ -315,14 +324,9 @@ final class ContentCapturePerUserService newSession.notifySessionStartedLocked(clientReceiver); } - @GuardedBy("mLock") - private boolean isWhitelistedLocked(@NonNull ComponentName componentName) { - return mWhitelistHelper.isWhitelisted(componentName); - } - // TODO(b/119613670): log metrics @GuardedBy("mLock") - public void finishSessionLocked(@NonNull String sessionId) { + public void finishSessionLocked(int sessionId) { if (!isEnabledLocked()) { return; } @@ -386,8 +390,8 @@ final class ContentCapturePerUserService @GuardedBy("mLock") public boolean sendActivityAssistDataLocked(@NonNull IBinder activityToken, @NonNull Bundle data) { - final String id = getSessionId(activityToken); - if (id != null) { + final int id = getSessionId(activityToken); + if (id != NO_SESSION_ID) { final ContentCaptureServerSession session = mSessions.get(id); final Bundle assistData = data.getBundle(ASSIST_KEY_DATA); final AssistStructure assistStructure = data.getParcelable(ASSIST_KEY_STRUCTURE); @@ -403,7 +407,7 @@ final class ContentCapturePerUserService } @GuardedBy("mLock") - public void removeSessionLocked(@NonNull String sessionId) { + public void removeSessionLocked(int sessionId) { mSessions.remove(sessionId); } @@ -480,7 +484,7 @@ final class ContentCapturePerUserService return null; } } - ContentCaptureOptions options = new ContentCaptureOptions(mMaster.mDevCfgLoggingLevel, + final ContentCaptureOptions options = new ContentCaptureOptions(mMaster.mDevCfgLoggingLevel, mMaster.mDevCfgMaxBufferSize, mMaster.mDevCfgIdleFlushingFrequencyMs, mMaster.mDevCfgTextChangeFlushingFrequencyMs, mMaster.mDevCfgLogHistorySize, whitelistedComponents); @@ -491,6 +495,13 @@ final class ContentCapturePerUserService } @GuardedBy("mLock") + @Nullable + ArraySet<ContentCaptureCondition> getContentCaptureConditionsLocked( + @NonNull String packageName) { + return mConditionsByPkg.get(packageName); + } + + @GuardedBy("mLock") void onActivityEventLocked(@NonNull ComponentName componentName, @ActivityEventType int type) { if (mRemoteService == null) { if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): no remote service"); @@ -522,9 +533,7 @@ final class ContentCapturePerUserService mRemoteService.dump(prefix2, pw); } - mWhitelistHelper.dump(prefix, "Whitelist", pw); - - if (mSessions.isEmpty()) { + if (mSessions.size() == 0) { pw.print(prefix); pw.println("no sessions"); } else { final int sessionsSize = mSessions.size(); @@ -542,14 +551,14 @@ final class ContentCapturePerUserService * Returns the session id associated with the given activity. */ @GuardedBy("mLock") - private String getSessionId(@NonNull IBinder activityToken) { + private int getSessionId(@NonNull IBinder activityToken) { for (int i = 0; i < mSessions.size(); i++) { ContentCaptureServerSession session = mSessions.valueAt(i); if (session.isActivitySession(activityToken)) { return mSessions.keyAt(i); } } - return null; + return NO_SESSION_ID; } /** @@ -560,7 +569,7 @@ final class ContentCapturePerUserService if (mMaster.verbose) { Slog.v(TAG, "resetting content capture whitelist"); } - mWhitelistHelper.setWhitelist((List) null, null); + mMaster.mGlobalContentCaptureOptions.resetWhitelist(mUserId); } private final class ContentCaptureServiceRemoteCallback extends @@ -576,9 +585,24 @@ final class ContentCapturePerUserService + ", " + (activities == null ? "null_activities" : activities.size() + " activities") + ")"); } + mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities); + } + + @Override + public void setContentCaptureConditions(String packageName, + List<ContentCaptureCondition> conditions) { + if (mMaster.verbose) { + Slog.v(TAG, "setContentCaptureConditions(" + packageName + "): " + + (conditions == null ? "null" : conditions.size() + " conditions")); + } synchronized (mLock) { - mWhitelistHelper.setWhitelist(packages, activities); + if (conditions == null) { + mConditionsByPkg.remove(packageName); + } else { + mConditionsByPkg.put(packageName, new ArraySet<>(conditions)); + } } + // TODO(b/119613670): log metrics } @Override diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java index 9b2c05f7fa79..1ad66d869eae 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java @@ -16,6 +16,7 @@ package com.android.server.contentcapture; import static android.service.contentcapture.ContentCaptureService.setClientState; +import static android.view.contentcapture.ContentCaptureSession.NO_SESSION_ID; import static android.view.contentcapture.ContentCaptureSession.STATE_ACTIVE; import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED; import static android.view.contentcapture.ContentCaptureSession.STATE_SERVICE_RESURRECTED; @@ -57,7 +58,7 @@ final class ContentCaptureServerSession { /** * Canonical session id. */ - private final String mId; + private final int mId; /** * UID of the app whose contents is being captured. @@ -66,11 +67,12 @@ final class ContentCaptureServerSession { ContentCaptureServerSession(@NonNull IBinder activityToken, @NonNull ContentCapturePerUserService service, @NonNull ComponentName appComponentName, - @NonNull IResultReceiver sessionStateReceiver, - int taskId, int displayId, @NonNull String sessionId, int uid, int flags) { + @NonNull IResultReceiver sessionStateReceiver, int taskId, int displayId, int sessionId, + int uid, int flags) { + Preconditions.checkArgument(sessionId != NO_SESSION_ID); mActivityToken = activityToken; mService = service; - mId = Preconditions.checkNotNull(sessionId); + mId = sessionId; mUid = uid; mContentCaptureContext = new ContentCaptureContext(/* clientContext= */ null, appComponentName, taskId, displayId, flags); diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java index 0afe252d96bd..3fa3fdf6d36e 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java @@ -98,9 +98,8 @@ final class RemoteContentCaptureService * Called by {@link ContentCaptureServerSession} to generate a call to the * {@link RemoteContentCaptureService} to indicate the session was created. */ - public void onSessionStarted(@Nullable ContentCaptureContext context, - @NonNull String sessionId, int uid, @NonNull IResultReceiver clientReceiver, - int initialState) { + public void onSessionStarted(@Nullable ContentCaptureContext context, int sessionId, int uid, + @NonNull IResultReceiver clientReceiver, int initialState) { scheduleAsyncRequest( (s) -> s.onSessionStarted(context, sessionId, uid, clientReceiver, initialState)); } @@ -109,15 +108,14 @@ final class RemoteContentCaptureService * Called by {@link ContentCaptureServerSession} to generate a call to the * {@link RemoteContentCaptureService} to indicate the session was finished. */ - public void onSessionFinished(@NonNull String sessionId) { + public void onSessionFinished(int sessionId) { scheduleAsyncRequest((s) -> s.onSessionFinished(sessionId)); } /** * Called by {@link ContentCaptureServerSession} to send snapshot data to the service. */ - public void onActivitySnapshotRequest(@NonNull String sessionId, - @NonNull SnapshotData snapshotData) { + public void onActivitySnapshotRequest(int sessionId, @NonNull SnapshotData snapshotData) { scheduleAsyncRequest((s) -> s.onActivitySnapshot(sessionId, snapshotData)); } diff --git a/services/core/Android.bp b/services/core/Android.bp index c154240f2b9a..9e1b3b8abc99 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -51,6 +51,7 @@ java_library_static { "android.hardware.configstore-V1.0-java", "android.hardware.contexthub-V1.0-java", "android.hidl.manager-V1.2-java", + "dnsresolver_aidl_interface-java", "netd_aidl_interface-java", "netd_event_listener_interface-java", ], diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 3d918fc052c8..0f39029f47b9 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -3825,6 +3825,7 @@ class AlarmManagerService extends SystemService { Slog.w(TAG, "Failure sending alarm.", e); } Trace.traceEnd(Trace.TRACE_TAG_POWER); + decrementAlarmCount(alarm.uid); } } @@ -4148,6 +4149,10 @@ class AlarmManagerService extends SystemService { public void handleMessage(Message msg) { switch (msg.what) { case ALARM_EVENT: { + // This code is used when the kernel timer driver is not available, which + // shouldn't happen. Here, we try our best to simulate it, which may be useful + // when porting Android to a new device. Note that we can't wake up a device + // this way, so WAKE_UP alarms will be delivered only when the device is awake. ArrayList<Alarm> triggerList = new ArrayList<Alarm>(); synchronized (mLock) { final long nowELAPSED = mInjector.getElapsedRealtime(); @@ -4167,6 +4172,7 @@ class AlarmManagerService extends SystemService { removeImpl(alarm.operation, null); } } + decrementAlarmCount(alarm.uid); } break; } @@ -4760,7 +4766,6 @@ class AlarmManagerService extends SystemService { mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage, UserHandle.getUserId(alarm.creatorUid), nowELAPSED); } - decrementAlarmCount(alarm.uid); final BroadcastStats bs = inflight.mBroadcastStats; bs.count++; if (bs.nesting == 0) { diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 39f7f0f0d1a8..6a9f5b651016 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -357,10 +357,27 @@ public final class BatteryService extends SystemService { && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel); } + private boolean shouldShutdownLocked() { + if (mHealthInfo.batteryLevel > 0) { + return false; + } + + // Battery-less devices should not shutdown. + if (!mHealthInfo.batteryPresent) { + return false; + } + + // If battery state is not CHARGING, shutdown. + // - If battery present and state == unknown, this is an unexpected error state. + // - If level <= 0 and state == full, this is also an unexpected state + // - All other states (NOT_CHARGING, DISCHARGING) means it is not charging. + return mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_CHARGING; + } + private void shutdownIfNoPowerLocked() { // shut down gracefully if our battery is critically low and we are not powered. // wait until the system has booted before attempting to display the shutdown dialog. - if (mHealthInfo.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) { + if (shouldShutdownLocked()) { mHandler.post(new Runnable() { @Override public void run() { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 2be92cde68ec..ed0399f1e883 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -63,6 +63,7 @@ import android.net.ConnectionInfo; import android.net.ConnectivityManager; import android.net.ICaptivePortal; import android.net.IConnectivityManager; +import android.net.IDnsResolver; import android.net.IIpConnectivityMetrics; import android.net.INetd; import android.net.INetdEventCallback; @@ -294,6 +295,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private INetworkManagementService mNMS; @VisibleForTesting + protected IDnsResolver mDnsResolver; + @VisibleForTesting protected INetd mNetd; private INetworkStatsService mStatsService; private INetworkPolicyManager mPolicyManager; @@ -525,6 +528,11 @@ public class ConnectivityService extends IConnectivityManager.Stub return sMagicDecoderRing.get(what, Integer.toString(what)); } + private static IDnsResolver getDnsResolver() { + return IDnsResolver.Stub + .asInterface(ServiceManager.getService("dnsresolver")); + } + /** Handler thread used for both of the handlers below. */ @VisibleForTesting protected final HandlerThread mHandlerThread; @@ -810,13 +818,14 @@ public class ConnectivityService extends IConnectivityManager.Stub public ConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager) { - this(context, netManager, statsService, policyManager, new IpConnectivityLog()); + this(context, netManager, statsService, policyManager, + getDnsResolver(), new IpConnectivityLog()); } @VisibleForTesting protected ConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager, - IpConnectivityLog logger) { + IDnsResolver dnsresolver, IpConnectivityLog logger) { if (DBG) log("ConnectivityService starting up"); mSystemProperties = getSystemProperties(); @@ -853,6 +862,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mPolicyManagerInternal = checkNotNull( LocalServices.getService(NetworkPolicyManagerInternal.class), "missing NetworkPolicyManagerInternal"); + mDnsResolver = checkNotNull(dnsresolver, "missing IDnsResolver"); mProxyTracker = makeProxyTracker(); mNetd = NetdService.getInstance(); @@ -1006,7 +1016,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mMultipathPolicyTracker = new MultipathPolicyTracker(mContext, mHandler); - mDnsManager = new DnsManager(mContext, mNMS, mSystemProperties); + mDnsManager = new DnsManager(mContext, mDnsResolver, mSystemProperties); registerPrivateDnsSettingsCallbacks(); } @@ -3021,9 +3031,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // NetworkFactories, so network traffic isn't interrupted for an unnecessarily // long time. try { - mNMS.removeNetwork(nai.network.netId); - } catch (Exception e) { - loge("Exception removing network: " + e); + mNetd.networkDestroy(nai.network.netId); + } catch (RemoteException | ServiceSpecificException e) { + loge("Exception destroying network: " + e); } mDnsManager.removeNetwork(nai.network); } @@ -5372,10 +5382,10 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), new Network(reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore, - mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd, mNMS, - factorySerialNumber); + mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd, mDnsResolver, + mNMS, factorySerialNumber); // Make sure the network capabilities reflect what the agent info says. - nai.networkCapabilities = mixInCapabilities(nai, nc); + nai.setNetworkCapabilities(mixInCapabilities(nai, nc)); final String extraInfo = networkInfo.getExtraInfo(); final String name = TextUtils.isEmpty(extraInfo) ? nai.networkCapabilities.getSSID() : extraInfo; @@ -5468,12 +5478,12 @@ public class ConnectivityService extends IConnectivityManager.Stub // Start or stop DNS64 detection and 464xlat according to network state. networkAgent.clatd.update(); notifyIfacesChangedForNetworkStats(); + try { + networkAgent.networkMonitor().notifyLinkPropertiesChanged(newLp); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } if (networkAgent.everConnected) { - try { - networkAgent.networkMonitor().notifyLinkPropertiesChanged(); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED); } } @@ -5701,7 +5711,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkCapabilities prevNc; synchronized (nai) { prevNc = nai.networkCapabilities; - nai.networkCapabilities = newNc; + nai.setNetworkCapabilities(newNc); } updateUids(nai, prevNc, newNc); @@ -5716,11 +5726,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // If the requestable capabilities have changed or the score changed, we can't have been // called by rematchNetworkAndRequests, so it's safe to start a rematch. rematchAllNetworksAndRequests(nai, oldScore); - try { - nai.networkMonitor().notifyNetworkCapabilitiesChanged(); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); } @@ -5979,11 +5984,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (capabilitiesChanged) { - try { - nai.networkMonitor().notifyNetworkCapabilitiesChanged(); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); } @@ -6392,7 +6392,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (networkAgent.networkMisc.acceptPartialConnectivity) { networkAgent.networkMonitor().setAcceptPartialConnectivity(); } - networkAgent.networkMonitor().notifyNetworkConnected(); + networkAgent.networkMonitor().notifyNetworkConnected( + networkAgent.linkProperties, networkAgent.networkCapabilities); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java index f0f8adbb6dda..33b846f7bb52 100644 --- a/services/core/java/com/android/server/DropBoxManagerService.java +++ b/services/core/java/com/android/server/DropBoxManagerService.java @@ -23,6 +23,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Resources; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; @@ -32,6 +33,9 @@ import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.os.ShellCommand; import android.os.StatFs; import android.os.SystemClock; import android.os.UserHandle; @@ -39,8 +43,11 @@ import android.provider.Settings; import android.text.TextUtils; import android.text.format.Time; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IDropBoxManagerService; import com.android.internal.util.DumpUtils; @@ -76,9 +83,6 @@ public final class DropBoxManagerService extends SystemService { private static final int DEFAULT_RESERVE_PERCENT = 10; private static final int QUOTA_RESCAN_MILLIS = 5000; - // mHandler 'what' value. - private static final int MSG_SEND_BROADCAST = 1; - private static final boolean PROFILE_DUMP = false; // TODO: This implementation currently uses one file per entry, which is @@ -95,6 +99,9 @@ public final class DropBoxManagerService extends SystemService { private FileList mAllFiles = null; private ArrayMap<String, FileList> mFilesByTag = null; + private long mLowPriorityRateLimitPeriod = 0; + private ArraySet<String> mLowPriorityTags = null; + // Various bits of disk information private StatFs mStatFs = null; @@ -105,7 +112,7 @@ public final class DropBoxManagerService extends SystemService { private volatile boolean mBooted = false; // Provide a way to perform sendBroadcast asynchronously to avoid deadlocks. - private final Handler mHandler; + private final DropBoxManagerBroadcastHandler mHandler; private int mMaxFiles = -1; // -1 means uninitialized. @@ -152,8 +159,142 @@ public final class DropBoxManagerService extends SystemService { public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { DropBoxManagerService.this.dump(fd, pw, args); } + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, + FileDescriptor err, String[] args, ShellCallback callback, + ResultReceiver resultReceiver) { + (new ShellCmd()).exec(this, in, out, err, args, callback, resultReceiver); + } }; + private class ShellCmd extends ShellCommand { + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + final PrintWriter pw = getOutPrintWriter(); + try { + switch (cmd) { + case "set-rate-limit": + final long period = Long.parseLong(getNextArgRequired()); + DropBoxManagerService.this.setLowPriorityRateLimit(period); + break; + case "add-low-priority": + final String addedTag = getNextArgRequired(); + DropBoxManagerService.this.addLowPriorityTag(addedTag); + break; + case "remove-low-priority": + final String removeTag = getNextArgRequired(); + DropBoxManagerService.this.removeLowPriorityTag(removeTag); + break; + case "restore-defaults": + DropBoxManagerService.this.restoreDefaults(); + break; + default: + return handleDefaultCommands(cmd); + } + } catch (Exception e) { + pw.println(e); + } + return 0; + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("Dropbox manager service commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" set-rate-limit PERIOD"); + pw.println(" Sets low priority broadcast rate limit period to PERIOD ms"); + pw.println(" add-low-priority TAG"); + pw.println(" Add TAG to dropbox low priority list"); + pw.println(" remove-low-priority TAG"); + pw.println(" Remove TAG from dropbox low priority list"); + pw.println(" restore-defaults"); + pw.println(" restore dropbox settings to defaults"); + } + } + + private class DropBoxManagerBroadcastHandler extends Handler { + private final Object mLock = new Object(); + + static final int MSG_SEND_BROADCAST = 1; + static final int MSG_SEND_DEFERRED_BROADCAST = 2; + + @GuardedBy("mLock") + private final ArrayMap<String, Intent> mDeferredMap = new ArrayMap(); + + DropBoxManagerBroadcastHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SEND_BROADCAST: + prepareAndSendBroadcast((Intent) msg.obj); + break; + case MSG_SEND_DEFERRED_BROADCAST: + Intent deferredIntent; + synchronized (mLock) { + deferredIntent = mDeferredMap.remove((String) msg.obj); + } + if (deferredIntent != null) { + prepareAndSendBroadcast(deferredIntent); + } + break; + } + } + + private void prepareAndSendBroadcast(Intent intent) { + if (!DropBoxManagerService.this.mBooted) { + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + } + getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM, + android.Manifest.permission.READ_LOGS); + } + + private Intent createIntent(String tag, long time) { + final Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED); + dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag); + dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time); + return dropboxIntent; + } + + /** + * Schedule a dropbox broadcast to be sent asynchronously. + */ + public void sendBroadcast(String tag, long time) { + sendMessage(obtainMessage(MSG_SEND_BROADCAST, createIntent(tag, time))); + } + + /** + * Possibly schedule a delayed dropbox broadcast. The broadcast will only be scheduled if + * no broadcast is currently scheduled. Otherwise updated the scheduled broadcast with the + * new intent information, effectively dropping the previous broadcast. + */ + public void maybeDeferBroadcast(String tag, long time) { + synchronized (mLock) { + final Intent intent = mDeferredMap.get(tag); + if (intent == null) { + // Schedule new delayed broadcast. + mDeferredMap.put(tag, createIntent(tag, time)); + sendMessageDelayed(obtainMessage(MSG_SEND_DEFERRED_BROADCAST, tag), + mLowPriorityRateLimitPeriod); + } else { + // Broadcast is already scheduled. Update intent with new data. + intent.putExtra(DropBoxManager.EXTRA_TIME, time); + final int dropped = intent.getIntExtra(DropBoxManager.EXTRA_DROPPED_COUNT, 0); + intent.putExtra(DropBoxManager.EXTRA_DROPPED_COUNT, dropped + 1); + return; + } + } + } + } + /** * Creates an instance of managed drop box storage using the default dropbox * directory. @@ -176,15 +317,7 @@ public final class DropBoxManagerService extends SystemService { super(context); mDropBoxDir = path; mContentResolver = getContext().getContentResolver(); - mHandler = new Handler(looper) { - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_SEND_BROADCAST) { - getContext().sendBroadcastAsUser((Intent)msg.obj, UserHandle.SYSTEM, - android.Manifest.permission.READ_LOGS); - } - } - }; + mHandler = new DropBoxManagerBroadcastHandler(looper); } @Override @@ -211,6 +344,8 @@ public final class DropBoxManagerService extends SystemService { mReceiver.onReceive(getContext(), (Intent) null); } }); + + getLowPriorityResourceConfigs(); break; case PHASE_BOOT_COMPLETED: @@ -298,17 +433,16 @@ public final class DropBoxManagerService extends SystemService { long time = createEntry(temp, tag, flags); temp = null; - final Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED); - dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag); - dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time); - if (!mBooted) { - dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - } // Call sendBroadcast after returning from this call to avoid deadlock. In particular // the caller may be holding the WindowManagerService lock but sendBroadcast requires a // lock in ActivityManagerService. ActivityManagerService has been caught holding that // very lock while waiting for the WindowManagerService lock. - mHandler.sendMessage(mHandler.obtainMessage(MSG_SEND_BROADCAST, dropboxIntent)); + if (mLowPriorityTags != null && mLowPriorityTags.contains(tag)) { + // Rate limit low priority Dropbox entries + mHandler.maybeDeferBroadcast(tag, time); + } else { + mHandler.sendBroadcast(tag, time); + } } catch (IOException e) { Slog.e(TAG, "Can't write: " + tag, e); } finally { @@ -382,6 +516,22 @@ public final class DropBoxManagerService extends SystemService { return null; } + private synchronized void setLowPriorityRateLimit(long period) { + mLowPriorityRateLimitPeriod = period; + } + + private synchronized void addLowPriorityTag(String tag) { + mLowPriorityTags.add(tag); + } + + private synchronized void removeLowPriorityTag(String tag) { + mLowPriorityTags.remove(tag); + } + + private synchronized void restoreDefaults() { + getLowPriorityResourceConfigs(); + } + public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return; @@ -421,6 +571,10 @@ public final class DropBoxManagerService extends SystemService { out.append("Drop box contents: ").append(mAllFiles.contents.size()).append(" entries\n"); out.append("Max entries: ").append(mMaxFiles).append("\n"); + out.append("Low priority rate limit period: "); + out.append(mLowPriorityRateLimitPeriod).append(" ms\n"); + out.append("Low priority tags: ").append(mLowPriorityTags).append("\n"); + if (!searchArgs.isEmpty()) { out.append("Searching for:"); for (String a : searchArgs) out.append(" ").append(a); @@ -936,4 +1090,21 @@ public final class DropBoxManagerService extends SystemService { return mCachedQuotaBlocks * mBlockSize; } + + private void getLowPriorityResourceConfigs() { + mLowPriorityRateLimitPeriod = Resources.getSystem().getInteger( + R.integer.config_dropboxLowPriorityBroadcastRateLimitPeriod); + + final String[] lowPrioritytags = Resources.getSystem().getStringArray( + R.array.config_dropboxLowPriorityTags); + final int size = lowPrioritytags.length; + if (size == 0) { + mLowPriorityTags = null; + return; + } + mLowPriorityTags = new ArraySet(size); + for (int i = 0; i < size; i++) { + mLowPriorityTags.add(lowPrioritytags[i]); + } + } } diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java index f50364d70bc7..164837ab98dd 100644 --- a/services/core/java/com/android/server/ExplicitHealthCheckController.java +++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java @@ -39,7 +39,9 @@ import android.text.TextUtils; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; +import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -50,10 +52,22 @@ class ExplicitHealthCheckController { private static final String TAG = "ExplicitHealthCheckController"; private final Object mLock = new Object(); private final Context mContext; - @GuardedBy("mLock") @Nullable private StateCallback mStateCallback; + + // Called everytime the service is connected, so the watchdog can sync it's state with + // the health check service. In practice, should never be null after it has been #setEnabled. + @GuardedBy("mLock") @Nullable private Runnable mOnConnected; + // Called everytime a package passes the health check, so the watchdog is notified of the + // passing check. In practice, should never be null after it has been #setEnabled. + @GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer; + // Actual binder object to the explicit health check service. @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService; - @GuardedBy("mLock") @Nullable private ServiceConnection mConnection; + // Cache for packages supporting explicit health checks. This cache should not change while + // the health check service is running. @GuardedBy("mLock") @Nullable private List<String> mSupportedPackages; + // Connection to the explicit health check service, necessary to unbind + @GuardedBy("mLock") @Nullable private ServiceConnection mConnection; + // Bind state of the explicit health check service. + @GuardedBy("mLock") private boolean mEnabled; ExplicitHealthCheckController(Context context) { mContext = context; @@ -61,28 +75,40 @@ class ExplicitHealthCheckController { /** * Requests an explicit health check for {@code packageName}. - * After this request, the callback registered on {@link startService} can receive explicit + * After this request, the callback registered on {@link #setCallbacks} can receive explicit * health check passed results. * * @throws IllegalStateException if the service is not started */ public void request(String packageName) throws RemoteException { synchronized (mLock) { + if (!mEnabled) { + return; + } + enforceServiceReadyLocked(); + + Slog.i(TAG, "Requesting health check for package " + packageName); mRemoteService.request(packageName); } } /** * Cancels all explicit health checks for {@code packageName}. - * After this request, the callback registered on {@link startService} can no longer receive + * After this request, the callback registered on {@link #setCallbacks} can no longer receive * explicit health check passed results. * * @throws IllegalStateException if the service is not started */ public void cancel(String packageName) throws RemoteException { synchronized (mLock) { + if (!mEnabled) { + return; + } + enforceServiceReadyLocked(); + + Slog.i(TAG, "Cancelling health check for package " + packageName); mRemoteService.cancel(packageName); } } @@ -95,13 +121,21 @@ class ExplicitHealthCheckController { */ public void getSupportedPackages(Consumer<List<String>> consumer) throws RemoteException { synchronized (mLock) { + if (!mEnabled) { + consumer.accept(Collections.emptyList()); + return; + } + enforceServiceReadyLocked(); + if (mSupportedPackages == null) { + Slog.d(TAG, "Getting health check supported packages"); mRemoteService.getSupportedPackages(new RemoteCallback(result -> { mSupportedPackages = result.getStringArrayList(EXTRA_SUPPORTED_PACKAGES); consumer.accept(mSupportedPackages); })); } else { + Slog.d(TAG, "Getting cached health check supported packages"); consumer.accept(mSupportedPackages); } } @@ -115,95 +149,113 @@ class ExplicitHealthCheckController { */ public void getRequestedPackages(Consumer<List<String>> consumer) throws RemoteException { synchronized (mLock) { + if (!mEnabled) { + consumer.accept(Collections.emptyList()); + return; + } + enforceServiceReadyLocked(); + + Slog.d(TAG, "Getting health check requested packages"); mRemoteService.getRequestedPackages(new RemoteCallback( result -> consumer.accept( result.getStringArrayList(EXTRA_REQUESTED_PACKAGES)))); } } + /** Enables or disables explicit health checks. */ + public void setEnabled(boolean enabled) { + synchronized (mLock) { + if (enabled == mEnabled) { + return; + } + + Slog.i(TAG, "Setting explicit health checks enabled " + enabled); + mEnabled = enabled; + if (enabled) { + bindService(); + } else { + unbindService(); + } + } + } + /** - * Starts the explicit health check service. - * - * @param stateCallback will receive important state changes changes - * @param passedConsumer will accept packages that pass explicit health checks - * - * @throws IllegalStateException if the service is already started + * Sets callbacks to listen to important events from the controller. + * Should be called at initialization. */ - public void startService(StateCallback stateCallback, Consumer<String> passedConsumer) { + public void setCallbacks(Runnable onConnected, Consumer<String> passedConsumer) { + Preconditions.checkNotNull(onConnected); + Preconditions.checkNotNull(passedConsumer); + mOnConnected = onConnected; + mPassedConsumer = passedConsumer; + } + + /** Binds to the explicit health check service. */ + private void bindService() { synchronized (mLock) { if (mRemoteService != null) { - throw new IllegalStateException("Explicit health check service already started."); + return; + } + ComponentName component = getServiceComponentNameLocked(); + if (component == null) { + Slog.wtf(TAG, "Explicit health check service not found"); + return; } - mStateCallback = stateCallback; + + Intent intent = new Intent(); + intent.setComponent(component); + // TODO: Fix potential race conditions during mConnection state transitions. + // E.g after #onServiceDisconected, the mRemoteService object is invalid until + // we get an #onServiceConnected. mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { - synchronized (mLock) { - mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service); - try { - mRemoteService.setCallback(new RemoteCallback(result -> { - String packageName = - result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE); - if (!TextUtils.isEmpty(packageName)) { - passedConsumer.accept(packageName); - } else { - Slog.w(TAG, "Empty package passed explicit health check?"); - } - })); - mStateCallback.onStart(); - Slog.i(TAG, "Explicit health check service is connected " + name); - } catch (RemoteException e) { - Slog.wtf(TAG, "Coud not setCallback on explicit health check service"); - } - } + initState(service); + Slog.i(TAG, "Explicit health check service is connected " + name); } @Override @MainThread public void onServiceDisconnected(ComponentName name) { - resetState(); + // Service crashed or process was killed, #onServiceConnected will be called. + // Don't need to re-bind. Slog.i(TAG, "Explicit health check service is disconnected " + name); } @Override public void onBindingDied(ComponentName name) { - resetState(); + // Application hosting service probably got updated + // Need to re-bind. + synchronized (mLock) { + if (mEnabled) { + unbindService(); + bindService(); + } + } Slog.i(TAG, "Explicit health check service binding is dead " + name); } @Override public void onNullBinding(ComponentName name) { - resetState(); - Slog.i(TAG, "Explicit health check service binding is null " + name); + // Should never happen. Service returned null from #onBind. + Slog.wtf(TAG, "Explicit health check service binding is null?? " + name); } }; - ComponentName component = getServiceComponentNameLocked(); - if (component != null) { - Intent intent = new Intent(); - intent.setComponent(component); - mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, - UserHandle.of(UserHandle.USER_SYSTEM)); - } + Slog.i(TAG, "Binding to explicit health service"); + mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, + UserHandle.of(UserHandle.USER_SYSTEM)); } } - // TODO: Differentiate between expected vs unexpected stop? - /** Callback to receive important {@link ExplicitHealthCheckController} state changes. */ - abstract static class StateCallback { - /** The controller is ready and we can request explicit health checks for packages */ - public void onStart() {} - - /** The controller is not ready and we cannot request explicit health checks for packages */ - public void onStop() {} - } - - /** Stops the explicit health check service. */ - public void stopService() { + /** Unbinds the explicit health check service. */ + private void unbindService() { synchronized (mLock) { if (mRemoteService != null) { + Slog.i(TAG, "Unbinding from explicit health service"); mContext.unbindService(mConnection); + mRemoteService = null; } } } @@ -247,19 +299,41 @@ class ExplicitHealthCheckController { return name; } - private void resetState() { + private void initState(IBinder service) { synchronized (mLock) { - mStateCallback.onStop(); - mStateCallback = null; mSupportedPackages = null; - mRemoteService = null; - mConnection = null; + mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service); + try { + mRemoteService.setCallback(new RemoteCallback(result -> { + String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE); + if (!TextUtils.isEmpty(packageName)) { + synchronized (mLock) { + if (mPassedConsumer == null) { + Slog.w(TAG, "Health check passed for package " + packageName + + "but no consumer registered."); + } else { + mPassedConsumer.accept(packageName); + } + } + } else { + Slog.w(TAG, "Empty package passed explicit health check?"); + } + })); + if (mOnConnected == null) { + Slog.w(TAG, "Health check service connected but no runnable registered."); + } else { + mOnConnected.run(); + } + } catch (RemoteException e) { + Slog.wtf(TAG, "Could not setCallback on explicit health check service"); + } } } @GuardedBy("mLock") private void enforceServiceReadyLocked() { if (mRemoteService == null) { + // TODO: Try to bind to service throw new IllegalStateException("Explicit health check service not ready"); } } diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 2ded1e58bf2d..be5838944bd0 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -323,16 +323,24 @@ public class LocationManagerService extends ILocationManager.Stub { }); mPackageManager.addOnPermissionsChangeListener( uid -> { - synchronized (mLock) { - onPermissionsChangedLocked(); - } + // listener invoked on ui thread, move to our thread to reduce risk of blocking + // ui thread + mHandler.post(() -> { + synchronized (mLock) { + onPermissionsChangedLocked(); + } + }); }); mActivityManager.addOnUidImportanceListener( (uid, importance) -> { - synchronized (mLock) { - onUidImportanceChangedLocked(uid, importance); - } + // listener invoked on ui thread, move to our thread to reduce risk of blocking + // ui thread + mHandler.post(() -> { + synchronized (mLock) { + onUidImportanceChangedLocked(uid, importance); + } + }); }, FOREGROUND_IMPORTANCE_CUTOFF); mContext.getContentResolver().registerContentObserver( @@ -394,9 +402,13 @@ public class LocationManagerService extends ILocationManager.Stub { LocalServices.getService(PowerManagerInternal.class); localPowerManager.registerLowPowerModeObserver(ServiceType.LOCATION, state -> { - synchronized (mLock) { - onBatterySaverModeChangedLocked(state.locationMode); - } + // listener invoked on ui thread, move to our thread to reduce risk of blocking + // ui thread + mHandler.post(() -> { + synchronized (mLock) { + onBatterySaverModeChangedLocked(state.locationMode); + } + }); }); new PackageMonitor() { diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 61a718231dd4..d1ae284512d2 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -1610,20 +1610,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } @Override - public void setDnsConfigurationForNetwork(int netId, String[] servers, String[] domains, - int[] params, String tlsHostname, String[] tlsServers) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - - final String[] tlsFingerprints = new String[0]; - try { - mNetdService.setResolverConfiguration( - netId, servers, domains, params, tlsHostname, tlsServers, tlsFingerprints); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - @Override public void addVpnUidRanges(int netId, UidRange[] ranges) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); @@ -2082,21 +2068,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } @Override - public void removeNetwork(int netId) { - mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG); - - try { - mNetdService.networkDestroy(netId); - } catch (ServiceSpecificException e) { - Log.w(TAG, "removeNetwork(" + netId + "): ", e); - throw e; - } catch (RemoteException e) { - Log.w(TAG, "removeNetwork(" + netId + "): ", e); - throw e.rethrowAsRuntimeException(); - } - } - - @Override public void addInterfaceToNetwork(String iface, int netId) { modifyInterfaceInNetwork(MODIFY_OPERATION_ADD, netId, iface); } diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index 660109cf6114..2ba4d975a6b0 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -26,11 +26,12 @@ import android.content.pm.VersionedPackage; import android.os.Environment; import android.os.Handler; import android.os.Looper; +import android.os.RemoteException; import android.os.SystemClock; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; -import android.util.Log; import android.util.Slog; import android.util.Xml; @@ -54,10 +55,12 @@ import java.io.InputStream; import java.lang.annotation.Retention; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.function.Consumer; /** * Monitors the health of packages on the system and notifies interested observers when packages @@ -84,10 +87,10 @@ public class PackageWatchdog { private final Object mLock = new Object(); // System server context private final Context mContext; - // Handler to run package cleanup runnables - private final Handler mTimerHandler; - // Handler for processing IO and observer actions - private final Handler mWorkerHandler; + // Handler to run short running tasks + private final Handler mShortTaskHandler; + // Handler for processing IO and long running tasks + private final Handler mLongTaskHandler; // Contains (observer-name -> observer-handle) that have ever been registered from // previous boots. Observers with all packages expired are periodically pruned. // It is saved to disk on system shutdown and repouplated on startup so it survives reboots. @@ -97,6 +100,12 @@ public class PackageWatchdog { private final AtomicFile mPolicyFile; // Runnable to prune monitored packages that have expired private final Runnable mPackageCleanup; + private final ExplicitHealthCheckController mHealthCheckController; + // Flag to control whether explicit health checks are supported or not + @GuardedBy("mLock") + private boolean mIsHealthCheckEnabled = true; + @GuardedBy("mLock") + private boolean mIsPackagesReady; // Last SystemClock#uptimeMillis a package clean up was executed. // 0 if mPackageCleanup not running. private long mUptimeAtLastRescheduleMs; @@ -104,32 +113,30 @@ public class PackageWatchdog { // 0 if mPackageCleanup not running. private long mDurationAtLastReschedule; - // TODO(b/120598832): Remove redundant context param private PackageWatchdog(Context context) { - mContext = context; - mPolicyFile = new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"), - "package-watchdog.xml")); - mTimerHandler = new Handler(Looper.myLooper()); - mWorkerHandler = BackgroundThread.getHandler(); - mPackageCleanup = this::rescheduleCleanup; - loadFromFile(); + // Needs to be constructed inline + this(context, new AtomicFile( + new File(new File(Environment.getDataDirectory(), "system"), + "package-watchdog.xml")), + new Handler(Looper.myLooper()), BackgroundThread.getHandler(), + new ExplicitHealthCheckController(context)); } /** - * Creates a PackageWatchdog for testing that uses the same {@code looper} for all handlers - * and creates package-watchdog.xml in an apps data directory. + * Creates a PackageWatchdog that allows injecting dependencies. */ @VisibleForTesting - PackageWatchdog(Context context, Looper looper) { + PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler, + Handler longTaskHandler, ExplicitHealthCheckController controller) { mContext = context; - mPolicyFile = new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml")); - mTimerHandler = new Handler(looper); - mWorkerHandler = mTimerHandler; + mPolicyFile = policyFile; + mShortTaskHandler = shortTaskHandler; + mLongTaskHandler = longTaskHandler; mPackageCleanup = this::rescheduleCleanup; + mHealthCheckController = controller; loadFromFile(); } - /** Creates or gets singleton instance of PackageWatchdog. */ public static PackageWatchdog getInstance(Context context) { synchronized (PackageWatchdog.class) { @@ -141,6 +148,20 @@ public class PackageWatchdog { } /** + * Called during boot to notify when packages are ready on the device so we can start + * binding. + */ + public void onPackagesReady() { + synchronized (mLock) { + mIsPackagesReady = true; + mHealthCheckController.setCallbacks(this::updateHealthChecks, + packageName -> onHealthCheckPassed(packageName)); + // Controller is disabled at creation until here where we may enable it + mHealthCheckController.setEnabled(mIsHealthCheckEnabled); + } + } + + /** * Registers {@code observer} to listen for package failures * * <p>Observers are expected to call this on boot. It does not specify any packages but @@ -163,40 +184,63 @@ public class PackageWatchdog { * Starts observing the health of the {@code packages} for {@code observer} and notifies * {@code observer} of any package failures within the monitoring duration. * - * <p>If monitoring a package with {@code withExplicitHealthCheck}, at the end of the monitoring - * duration if {@link #onExplicitHealthCheckPassed} was never called, + * <p>If monitoring a package supporting explicit health check, at the end of the monitoring + * duration if {@link #onHealthCheckPassed} was never called, * {@link PackageHealthObserver#execute} will be called as if the package failed. * * <p>If {@code observer} is already monitoring a package in {@code packageNames}, * the monitoring window of that package will be reset to {@code durationMs} and the health - * check state will be reset to a default depending on {@code withExplictHealthCheck}. + * check state will be reset to a default depending on if the package is contained in + * {@link mPackagesWithExplicitHealthCheckEnabled}. * * @throws IllegalArgumentException if {@code packageNames} is empty * or {@code durationMs} is less than 1 */ - public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames, - long durationMs, boolean withExplicitHealthCheck) { - if (packageNames.isEmpty() || durationMs < 1) { + public void startObservingHealth(PackageHealthObserver observer, List<String> packages, + long durationMs) { + if (packages.isEmpty() || durationMs < 1) { throw new IllegalArgumentException("Observation not started, no packages specified" + "or invalid duration"); } + if (!mIsPackagesReady) { + // TODO: Queue observation requests when packages are not ready + Slog.w(TAG, "Attempt to observe when packages not ready"); + return; + } + + try { + Slog.i(TAG, "Getting packages supporting explicit health check"); + mHealthCheckController.getSupportedPackages(supportedPackages -> + startObservingInner(observer, packages, durationMs, supportedPackages)); + } catch (RemoteException e) { + Slog.wtf(TAG, "Failed to fetch supported explicit health check packages"); + } + } + + private void startObservingInner(PackageHealthObserver observer, + List<String> packageNames, long durationMs, + List<String> healthCheckSupportedPackages) { + Slog.i(TAG, "Start observing packages " + packageNames + + ". Explicit health check supported packages " + healthCheckSupportedPackages); List<MonitoredPackage> packages = new ArrayList<>(); for (int i = 0; i < packageNames.size(); i++) { - // When observing packages withExplicitHealthCheck, - // MonitoredPackage#mHasExplicitHealthCheckPassed will be false initially. - packages.add(new MonitoredPackage(packageNames.get(i), durationMs, - !withExplicitHealthCheck)); + String packageName = packageNames.get(i); + boolean shouldEnableHealthCheck = healthCheckSupportedPackages.contains(packageName); + // If we should enable explicit health check for a package, + // MonitoredPackage#mHasHealthCheckPassed will be false + // until PackageWatchdog#onHealthCheckPassed + packages.add(new MonitoredPackage(packageName, durationMs, !shouldEnableHealthCheck)); } synchronized (mLock) { ObserverInternal oldObserver = mAllObservers.get(observer.getName()); if (oldObserver == null) { - Slog.d(TAG, observer.getName() + " started monitoring health of packages " - + packageNames); + Slog.d(TAG, observer.getName() + " started monitoring health " + + "of packages " + packageNames); mAllObservers.put(observer.getName(), new ObserverInternal(observer.getName(), packages)); } else { - Slog.d(TAG, observer.getName() + " added the following packages to monitor " - + packageNames); + Slog.d(TAG, observer.getName() + " added the following " + + "packages to monitor " + packageNames); oldObserver.updatePackages(packages); } } @@ -204,9 +248,97 @@ public class PackageWatchdog { // Always reschedule because we may need to expire packages // earlier than we are already scheduled for rescheduleCleanup(); + updateHealthChecks(); saveToFileAsync(); } + private void requestCheck(String packageName) { + try { + Slog.d(TAG, "Requesting explicit health check for " + packageName); + mHealthCheckController.request(packageName); + } catch (RemoteException e) { + Slog.wtf(TAG, "Failed to request explicit health check for " + packageName, e); + } + } + + private void cancelCheck(String packageName) { + try { + Slog.d(TAG, "Cancelling explicit health check for " + packageName); + mHealthCheckController.cancel(packageName); + } catch (RemoteException e) { + Slog.wtf(TAG, "Failed to cancel explicit health check for " + packageName, e); + } + } + + private void actOnDifference(Collection<String> collection1, Collection<String> collection2, + Consumer<String> action) { + Iterator<String> iterator = collection1.iterator(); + while (iterator.hasNext()) { + String packageName = iterator.next(); + if (!collection2.contains(packageName)) { + action.accept(packageName); + } + } + } + + private void updateChecksInner(List<String> supportedPackages, + List<String> previousRequestedPackages) { + boolean shouldUpdateFile = false; + + synchronized (mLock) { + Slog.i(TAG, "Updating explicit health checks. Supported packages: " + supportedPackages + + ". Requested packages: " + previousRequestedPackages); + Set<String> newRequestedPackages = new ArraySet<>(); + Iterator<ObserverInternal> oit = mAllObservers.values().iterator(); + while (oit.hasNext()) { + ObserverInternal observer = oit.next(); + Iterator<MonitoredPackage> pit = + observer.mPackages.values().iterator(); + while (pit.hasNext()) { + MonitoredPackage monitoredPackage = pit.next(); + String packageName = monitoredPackage.mName; + if (!monitoredPackage.mHasPassedHealthCheck) { + if (supportedPackages.contains(packageName)) { + newRequestedPackages.add(packageName); + } else { + shouldUpdateFile = true; + monitoredPackage.mHasPassedHealthCheck = true; + } + } + } + } + // TODO: Support ending the binding if newRequestedPackages is empty. + // Will have to re-bind when we #startObservingHealth. + + // Cancel packages no longer requested + actOnDifference(previousRequestedPackages, newRequestedPackages, p -> cancelCheck(p)); + // Request packages not yet requested + actOnDifference(newRequestedPackages, previousRequestedPackages, p -> requestCheck(p)); + } + + if (shouldUpdateFile) { + saveToFileAsync(); + } + } + + private void updateHealthChecks() { + mShortTaskHandler.post(() -> { + try { + Slog.i(TAG, "Updating explicit health checks for all available packages"); + mHealthCheckController.getSupportedPackages(supported -> { + try { + mHealthCheckController.getRequestedPackages( + requested -> updateChecksInner(supported, requested)); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to get requested health check packages", e); + } + }); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to get supported health check package", e); + } + }); + } + /** * Unregisters {@code observer} from listening to package failure. * Additionally, this stops observing any packages that may have previously been observed @@ -250,7 +382,7 @@ public class PackageWatchdog { * <p>This method could be called frequently if there is a severe problem on the device. */ public void onPackageFailure(List<VersionedPackage> packages) { - mWorkerHandler.post(() -> { + mLongTaskHandler.post(() -> { synchronized (mLock) { if (mAllObservers.isEmpty()) { return; @@ -286,49 +418,32 @@ public class PackageWatchdog { }); } - /** - * Updates the observers monitoring {@code packageName} that explicit health check has passed. - * - * <p> This update is strictly for registered observers at the time of the call - * Observers that register after this signal will have no knowledge of prior signals and will - * effectively behave as if the explicit health check hasn't passed for {@code packageName}. - * - * <p> {@code packageName} can still be considered failed if reported by - * {@link #onPackageFailure} before the package expires. - * - * <p> Triggered by components outside the system server when they are fully functional after an - * update. - */ - public void onExplicitHealthCheckPassed(String packageName) { - Slog.i(TAG, "Health check passed for package: " + packageName); - boolean shouldUpdateFile = false; - synchronized (mLock) { - for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) { - ObserverInternal observer = mAllObservers.valueAt(observerIdx); - MonitoredPackage monitoredPackage = observer.mPackages.get(packageName); - if (monitoredPackage != null && !monitoredPackage.mHasPassedHealthCheck) { - monitoredPackage.mHasPassedHealthCheck = true; - shouldUpdateFile = true; - } - } - } - if (shouldUpdateFile) { - saveToFileAsync(); - } - } - // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? // This currently adds about 7ms extra to shutdown thread /** Writes the package information to file during shutdown. */ public void writeNow() { if (!mAllObservers.isEmpty()) { - mWorkerHandler.removeCallbacks(this::saveToFile); + mLongTaskHandler.removeCallbacks(this::saveToFile); pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs); saveToFile(); Slog.i(TAG, "Last write to update package durations"); } } + // TODO(b/120598832): Set depending on DeviceConfig flag + /** + * Enables or disables explicit health checks. + * <p> If explicit health checks are enabled, the health check service is started. + * <p> If explicit health checks are disabled, pending explicit health check requests are + * passed and the health check service is stopped. + */ + public void setExplicitHealthCheckEnabled(boolean enabled) { + synchronized (mLock) { + mIsHealthCheckEnabled = enabled; + mHealthCheckController.setEnabled(enabled); + } + } + /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */ @Retention(SOURCE) @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE, @@ -371,6 +486,37 @@ public class PackageWatchdog { String getName(); } + /** + * Updates the observers monitoring {@code packageName} that explicit health check has passed. + * + * <p> This update is strictly for registered observers at the time of the call + * Observers that register after this signal will have no knowledge of prior signals and will + * effectively behave as if the explicit health check hasn't passed for {@code packageName}. + * + * <p> {@code packageName} can still be considered failed if reported by + * {@link #onPackageFailure} before the package expires. + * + * <p> Triggered by components outside the system server when they are fully functional after an + * update. + */ + private void onHealthCheckPassed(String packageName) { + Slog.i(TAG, "Health check passed for package: " + packageName); + boolean shouldUpdateFile = false; + synchronized (mLock) { + for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) { + ObserverInternal observer = mAllObservers.valueAt(observerIdx); + MonitoredPackage monitoredPackage = observer.mPackages.get(packageName); + if (monitoredPackage != null && !monitoredPackage.mHasPassedHealthCheck) { + monitoredPackage.mHasPassedHealthCheck = true; + shouldUpdateFile = true; + } + } + } + if (shouldUpdateFile) { + saveToFileAsync(); + } + } + /** Reschedules handler to prune expired packages from observers. */ private void rescheduleCleanup() { synchronized (mLock) { @@ -393,8 +539,8 @@ public class PackageWatchdog { || nextDurationToScheduleMs < remainingDurationMs) { // First schedule or an earlier reschedule pruneObservers(elapsedDurationMs); - mTimerHandler.removeCallbacks(mPackageCleanup); - mTimerHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs); + mShortTaskHandler.removeCallbacks(mPackageCleanup); + mShortTaskHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs); mDurationAtLastReschedule = nextDurationToScheduleMs; mUptimeAtLastRescheduleMs = uptimeMs; } @@ -437,7 +583,7 @@ public class PackageWatchdog { List<MonitoredPackage> failedPackages = observer.updateMonitoringDurations(elapsedMs); if (!failedPackages.isEmpty()) { - onExplicitHealthCheckFailed(observer, failedPackages); + onHealthCheckFailed(observer, failedPackages); } if (observer.mPackages.isEmpty()) { Slog.i(TAG, "Discarding observer " + observer.mName + ". All packages expired"); @@ -445,12 +591,13 @@ public class PackageWatchdog { } } } + updateHealthChecks(); saveToFileAsync(); } - private void onExplicitHealthCheckFailed(ObserverInternal observer, + private void onHealthCheckFailed(ObserverInternal observer, List<MonitoredPackage> failedPackages) { - mWorkerHandler.post(() -> { + mLongTaskHandler.post(() -> { synchronized (mLock) { PackageHealthObserver registeredObserver = observer.mRegisteredObserver; if (registeredObserver != null) { @@ -458,6 +605,7 @@ public class PackageWatchdog { for (int i = 0; i < failedPackages.size(); i++) { String packageName = failedPackages.get(i).mName; long versionCode = 0; + Slog.i(TAG, "Explicit health check failed for package " + packageName); try { versionCode = pm.getPackageInfo( packageName, 0 /* flags */).getLongVersionCode(); @@ -498,7 +646,7 @@ public class PackageWatchdog { } catch (FileNotFoundException e) { // Nothing to monitor } catch (IOException | NumberFormatException | XmlPullParserException e) { - Log.wtf(TAG, "Unable to read monitored packages, deleting file", e); + Slog.wtf(TAG, "Unable to read monitored packages, deleting file", e); mPolicyFile.delete(); } finally { IoUtils.closeQuietly(infile); @@ -542,8 +690,8 @@ public class PackageWatchdog { } private void saveToFileAsync() { - mWorkerHandler.removeCallbacks(this::saveToFile); - mWorkerHandler.post(this::saveToFile); + mLongTaskHandler.removeCallbacks(this::saveToFile); + mLongTaskHandler.post(this::saveToFile); } /** diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 1a842f72fcb4..b3a667a5f7c1 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3947,6 +3947,23 @@ class StorageManagerService extends IStorageManager.Stub case "ak.alizandro.smartaudiobookplayer": // b/129084042 case "com.campmobile.snow": // b/128803870 case "com.qnap.qfile": // b/126374406 + case "com.google.android.apps.photos": // b/125506293 + case "com.facebook.mlite": // b/126561155 + case "com.ss.android.ugc.trill": // b/126610656 + case "com.instagram.android": // b/127526615 + case "com.facebook.orca": // b/128255453 + case "org.videolan.vlc": // b/128391743 + case "vStudio.Android.Camera360": // b/128882110 + case "com.twitter.android": // b/128948908 + case "com.tumblr": // b/129022664 + case "com.sina.weibo": // b/129029018 + case "com.kwai.video": // b/129037235 + case "com.fotoable.photocollage": // b/129236353 + case "com.xvideostudio.videoeditor": // b/129247146 + case "app.buzz.share": // b/129304005 + case "com.ss.android.article.topbuzzvideo.en": // b/129303979 + case "com.linecorp.b612.android": // b/129318512 + case "com.google.android.GoogleCamera": // b/128326994 return true; } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 8b10267f32ff..1c9931616630 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -79,6 +79,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.OptionalInt; +import java.util.stream.Collectors; /** * Since phone process can be restarted, this class provides a centralized place @@ -260,8 +261,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { static final int ENFORCE_PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR - | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST - | PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE; + | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST; static final int PRECISE_PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_PRECISE_CALL_STATE | @@ -627,11 +627,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { r.callingPackage = callingPackage; r.callerUid = Binder.getCallingUid(); r.callerPid = Binder.getCallingPid(); - if (r.subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID && r.subId != subId) { - throw new IllegalArgumentException( - "PhoneStateListener cannot concurrently listen on multiple " + - "subscriptions. Previously registered on subId: " + r.subId); - } // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID, // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID if (!SubscriptionManager.isValidSubscriptionId(subId)) { @@ -827,7 +822,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } if ((events & PhoneStateListener - .LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0) { + .LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0 + && TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub( + r.context, r.callerPid, r.callerUid, r.callingPackage, + "listen_active_data_subid_change")) { try { r.callback.onActiveDataSubIdChanged(mActiveDataSubId); } catch (RemoteException ex) { @@ -1769,12 +1767,23 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { log("notifyActiveDataSubIdChanged: activeDataSubId=" + activeDataSubId); } + // Create a copy to prevent the IPC call while checking carrier privilege under the lock. + List<Record> copiedRecords; synchronized (mRecords) { - mActiveDataSubId = activeDataSubId; + copiedRecords = new ArrayList<>(mRecords); + } + mActiveDataSubId = activeDataSubId; - for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE)) { + // Filter the record that does not listen to this change or does not have the permission. + copiedRecords = copiedRecords.stream().filter(r -> r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) + && TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub( + mContext, r.callerPid, r.callerUid, r.callingPackage, + "notifyActiveDataSubIdChanged")).collect(Collectors.toCollection(ArrayList::new)); + + synchronized (mRecords) { + for (Record r : copiedRecords) { + if (mRecords.contains(r)) { try { r.callback.onActiveDataSubIdChanged(activeDataSubId); } catch (RemoteException ex) { diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java index 8ccb6e20a614..9325d257904a 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -307,7 +307,6 @@ public class AdbDebuggingManager { } cancelJobToUpdateAdbKeyStore(); - mAdbKeyStore = null; mConnectedKey = null; break; diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index cef245ba332f..261ed4c7e854 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1524,8 +1524,9 @@ public final class ActiveServices { boolean anyClientActivities = false; for (int i=proc.services.size()-1; i>=0 && !anyClientActivities; i--) { ServiceRecord sr = proc.services.valueAt(i); - for (int conni=sr.connections.size()-1; conni>=0 && !anyClientActivities; conni--) { - ArrayList<ConnectionRecord> clist = sr.connections.valueAt(conni); + ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = sr.getConnections(); + for (int conni = connections.size() - 1; conni >= 0 && !anyClientActivities; conni--) { + ArrayList<ConnectionRecord> clist = connections.valueAt(conni); for (int cri=clist.size()-1; cri>=0; cri--) { ConnectionRecord cr = clist.get(cri); if (cr.binding.client == null || cr.binding.client == proc) { @@ -1752,10 +1753,10 @@ public final class ActiveServices { callerApp.uid, callerApp.processName, callingPackage); IBinder binder = connection.asBinder(); - ArrayList<ConnectionRecord> clist = s.connections.get(binder); + ArrayList<ConnectionRecord> clist = s.getConnections().get(binder); if (clist == null) { clist = new ArrayList<ConnectionRecord>(); - s.connections.put(binder, clist); + s.putConnection(binder, clist); } clist.add(c); b.connections.add(c); @@ -1855,8 +1856,9 @@ public final class ActiveServices { b.binder = service; b.requested = true; b.received = true; - for (int conni=r.connections.size()-1; conni>=0; conni--) { - ArrayList<ConnectionRecord> clist = r.connections.valueAt(conni); + ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections(); + for (int conni = connections.size() - 1; conni >= 0; conni--) { + ArrayList<ConnectionRecord> clist = connections.valueAt(conni); for (int i=0; i<clist.size(); i++) { ConnectionRecord c = clist.get(i); if (!filter.equals(c.binding.intent.intent)) { @@ -2722,6 +2724,10 @@ public final class ActiveServices { updateServiceClientActivitiesLocked(app, null, true); + if (newService && created) { + app.addBoundClientUidsOfNewService(r); + } + // If the service is in the started state, and there are no // pending arguments, then fake up one so its onStartCommand() will // be called. @@ -2881,8 +2887,9 @@ public final class ActiveServices { // Report to all of the connections that the service is no longer // available. - for (int conni=r.connections.size()-1; conni>=0; conni--) { - ArrayList<ConnectionRecord> c = r.connections.valueAt(conni); + ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections(); + for (int conni = connections.size() - 1; conni >= 0; conni--) { + ArrayList<ConnectionRecord> c = connections.valueAt(conni); for (int i=0; i<c.size(); i++) { ConnectionRecord cr = c.get(i); // There is still a connection to the service that is @@ -3013,6 +3020,7 @@ public final class ActiveServices { r.stats.stopLaunchedLocked(); } r.app.services.remove(r); + r.app.updateBoundClientUids(); if (r.whitelistManager) { updateWhitelistManagerLocked(r.app); } @@ -3065,11 +3073,11 @@ public final class ActiveServices { IBinder binder = c.conn.asBinder(); AppBindRecord b = c.binding; ServiceRecord s = b.service; - ArrayList<ConnectionRecord> clist = s.connections.get(binder); + ArrayList<ConnectionRecord> clist = s.getConnections().get(binder); if (clist != null) { clist.remove(c); if (clist.size() == 0) { - s.connections.remove(binder); + s.removeConnection(binder); } } b.connections.remove(c); @@ -3294,6 +3302,7 @@ public final class ActiveServices { if (finishing) { if (r.app != null && !r.app.isPersistent()) { r.app.services.remove(r); + r.app.updateBoundClientUids(); if (r.whitelistManager) { updateWhitelistManagerLocked(r.app); } @@ -3389,6 +3398,7 @@ public final class ActiveServices { Slog.i(TAG, " Force stopping service " + service); if (service.app != null && !service.app.isPersistent()) { service.app.services.remove(service); + service.app.updateBoundClientUids(); if (service.whitelistManager) { updateWhitelistManagerLocked(service.app); } @@ -3504,8 +3514,9 @@ public final class ActiveServices { Iterator<ServiceRecord> it = app.services.iterator(); while (it.hasNext()) { ServiceRecord r = it.next(); - for (int conni=r.connections.size()-1; conni>=0; conni--) { - ArrayList<ConnectionRecord> cl = r.connections.valueAt(conni); + ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections(); + for (int conni=connections.size()-1; conni>=0; conni--) { + ArrayList<ConnectionRecord> cl = connections.valueAt(conni); for (int i=0; i<cl.size(); i++) { ConnectionRecord c = cl.get(i); if (c.binding.client != app) { @@ -3542,6 +3553,7 @@ public final class ActiveServices { } if (sr.app != app && sr.app != null && !sr.app.isPersistent()) { sr.app.services.remove(sr); + sr.app.updateBoundClientUids(); } sr.setProcess(null); sr.isolatedProc = null; @@ -3605,6 +3617,7 @@ public final class ActiveServices { // so make sure the service is cleaned out of it. if (!app.isPersistent()) { app.services.removeAt(i); + app.updateBoundClientUids(); } // Sanity check: if the service listed for the app is not one @@ -3655,6 +3668,7 @@ public final class ActiveServices { if (!allowRestart) { app.services.clear(); + app.clearBoundClientUids(); // Make sure there are no more restarting services for this process. for (int i=mRestartingServices.size()-1; i>=0; i--) { @@ -3701,7 +3715,7 @@ public final class ActiveServices { info.foreground = r.isForeground; info.activeSince = r.createRealTime; info.started = r.startRequested; - info.clientCount = r.connections.size(); + info.clientCount = r.getConnections().size(); info.crashCount = r.crashCount; info.lastActivityTime = r.lastActivity; if (r.isForeground) { @@ -3717,8 +3731,9 @@ public final class ActiveServices { info.flags |= ActivityManager.RunningServiceInfo.FLAG_PERSISTENT_PROCESS; } - for (int conni=r.connections.size()-1; conni>=0; conni--) { - ArrayList<ConnectionRecord> connl = r.connections.valueAt(conni); + ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections(); + for (int conni = connections.size() - 1; conni >= 0; conni--) { + ArrayList<ConnectionRecord> connl = connections.valueAt(conni); for (int i=0; i<connl.size(); i++) { ConnectionRecord conn = connl.get(i); if (conn.clientLabel != 0) { @@ -3787,9 +3802,10 @@ public final class ActiveServices { public PendingIntent getRunningServiceControlPanelLocked(ComponentName name) { int userId = UserHandle.getUserId(Binder.getCallingUid()); ServiceRecord r = getServiceByNameLocked(name, userId); + ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections(); if (r != null) { - for (int conni=r.connections.size()-1; conni>=0; conni--) { - ArrayList<ConnectionRecord> conn = r.connections.valueAt(conni); + for (int conni = connections.size() - 1; conni >= 0; conni--) { + ArrayList<ConnectionRecord> conn = connections.valueAt(conni); for (int i=0; i<conn.size(); i++) { if (conn.get(i).clientIntent != null) { return conn.get(i).clientIntent; @@ -4080,11 +4096,12 @@ public final class ActiveServices { pw.print(" started="); pw.print(r.startRequested); pw.print(" connections="); - pw.println(r.connections.size()); - if (r.connections.size() > 0) { + ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections(); + pw.println(connections.size()); + if (connections.size() > 0) { pw.println(" Connections:"); - for (int conni=0; conni<r.connections.size(); conni++) { - ArrayList<ConnectionRecord> clist = r.connections.valueAt(conni); + for (int conni = 0; conni < connections.size(); conni++) { + ArrayList<ConnectionRecord> clist = connections.valueAt(conni); for (int i = 0; i < clist.size(); i++) { ConnectionRecord conn = clist.get(i); pw.print(" "); diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 1751856066fb..b759dd4e8122 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -20,8 +20,10 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK import android.app.ActivityThread; import android.content.ContentResolver; +import android.content.Context; import android.database.ContentObserver; import android.net.Uri; +import android.os.Build; import android.os.Handler; import android.provider.DeviceConfig; import android.provider.DeviceConfig.OnPropertyChangedListener; @@ -38,6 +40,7 @@ import java.io.PrintWriter; * Settings constants that can modify the activity manager's behavior. */ final class ActivityManagerConstants extends ContentObserver { + private static final String TAG = "ActivityManagerConstants"; // Key names stored in the settings value. private static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time"; @@ -272,6 +275,16 @@ final class ActivityManagerConstants extends ContentObserver { // memory trimming. public int CUR_TRIM_CACHED_PROCESSES; + private static final long MIN_AUTOMATIC_HEAP_DUMP_PSS_THRESHOLD_BYTES = 100 * 1024; // 100 KB + + private final boolean mSystemServerAutomaticHeapDumpEnabled; + + /** Package to report to when the memory usage exceeds the limit. */ + private final String mSystemServerAutomaticHeapDumpPackageName; + + /** Byte limit for dump heap monitoring. */ + private long mSystemServerAutomaticHeapDumpPssThresholdBytes; + private static final Uri ACTIVITY_MANAGER_CONSTANTS_URI = Settings.Global.getUriFor( Settings.Global.ACTIVITY_MANAGER_CONSTANTS); @@ -286,6 +299,9 @@ final class ActivityManagerConstants extends ContentObserver { Settings.Global.getUriFor( Settings.Global.BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST); + private static final Uri ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI = + Settings.Global.getUriFor(Settings.Global.ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS); + private final OnPropertyChangedListener mOnDeviceConfigChangedListener = new OnPropertyChangedListener() { @Override @@ -296,9 +312,17 @@ final class ActivityManagerConstants extends ContentObserver { } }; - public ActivityManagerConstants(ActivityManagerService service, Handler handler) { + ActivityManagerConstants(Context context, ActivityManagerService service, Handler handler) { super(handler); mService = service; + mSystemServerAutomaticHeapDumpEnabled = Build.IS_DEBUGGABLE + && context.getResources().getBoolean( + com.android.internal.R.bool.config_debugEnableAutomaticSystemServerHeapDumps); + mSystemServerAutomaticHeapDumpPackageName = context.getPackageName(); + mSystemServerAutomaticHeapDumpPssThresholdBytes = Math.max( + MIN_AUTOMATIC_HEAP_DUMP_PSS_THRESHOLD_BYTES, + context.getResources().getInteger( + com.android.internal.R.integer.config_debugSystemServerPssThresholdBytes)); } public void start(ContentResolver resolver) { @@ -308,10 +332,17 @@ final class ActivityManagerConstants extends ContentObserver { mResolver.registerContentObserver(BACKGROUND_ACTIVITY_STARTS_ENABLED_URI, false, this); mResolver.registerContentObserver(BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST_URI, false, this); + if (mSystemServerAutomaticHeapDumpEnabled) { + mResolver.registerContentObserver(ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI, + false, this); + } updateConstants(); updateActivityStartsLoggingEnabled(); updateBackgroundActivityStartsEnabled(); updateBackgroundActivityStartsPackageNamesWhitelist(); + if (mSystemServerAutomaticHeapDumpEnabled) { + updateEnableAutomaticSystemServerHeapDumps(); + } DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, ActivityThread.currentApplication().getMainExecutor(), mOnDeviceConfigChangedListener); @@ -343,6 +374,8 @@ final class ActivityManagerConstants extends ContentObserver { updateBackgroundActivityStartsEnabled(); } else if (BACKGROUND_ACTIVITY_STARTS_PACKAGE_NAMES_WHITELIST_URI.equals(uri)) { updateBackgroundActivityStartsPackageNamesWhitelist(); + } else if (ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI.equals(uri)) { + updateEnableAutomaticSystemServerHeapDumps(); } } @@ -450,6 +483,24 @@ final class ActivityManagerConstants extends ContentObserver { mPackageNamesWhitelistedForBgActivityStarts = newSet; } + private void updateEnableAutomaticSystemServerHeapDumps() { + if (!mSystemServerAutomaticHeapDumpEnabled) { + Slog.wtf(TAG, + "updateEnableAutomaticSystemServerHeapDumps called when leak detection " + + "disabled"); + return; + } + // Monitoring is on by default, so if the setting hasn't been set by the user, + // monitoring should be on. + final boolean enabled = Settings.Global.getInt(mResolver, + Settings.Global.ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS, 1) == 1; + + // Setting the threshold to 0 stops the checking. + final long threshold = enabled ? mSystemServerAutomaticHeapDumpPssThresholdBytes : 0; + mService.setDumpHeapDebugLimit(null, 0, threshold, + mSystemServerAutomaticHeapDumpPackageName); + } + private void updateMaxCachedProcesses() { String maxCachedProcessesFlag = DeviceConfig.getProperty( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MAX_CACHED_PROCESSES); @@ -460,7 +511,7 @@ final class ActivityManagerConstants extends ContentObserver { : mOverrideMaxCachedProcesses; } catch (NumberFormatException e) { // Bad flag value from Phenotype, revert to default. - Slog.e("ActivityManagerConstants", + Slog.e(TAG, "Unable to parse flag for max_cached_processes: " + maxCachedProcessesFlag, e); CUR_MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 73393ed6e095..3eb7c0374a01 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2135,6 +2135,8 @@ public class ActivityManagerService extends IActivityManager.Stub mService.mServices.systemServicesReady(); } else if (phase == PHASE_ACTIVITY_MANAGER_READY) { mService.startBroadcastObservers(); + } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { + mService.mPackageWatchdog.onPackagesReady(); } } @@ -2288,7 +2290,8 @@ public class ActivityManagerService extends IActivityManager.Stub mBatteryStatsService = null; mHandler = hasHandlerThread ? new MainHandler(handlerThread.getLooper()) : null; mHandlerThread = handlerThread; - mConstants = hasHandlerThread ? new ActivityManagerConstants(this, mHandler) : null; + mConstants = hasHandlerThread + ? new ActivityManagerConstants(mContext, this, mHandler) : null; final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */); mProcessList.init(this, activeUids); mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids); @@ -2336,7 +2339,7 @@ public class ActivityManagerService extends IActivityManager.Stub mProcStartHandlerThread.start(); mProcStartHandler = new Handler(mProcStartHandlerThread.getLooper()); - mConstants = new ActivityManagerConstants(this, mHandler); + mConstants = new ActivityManagerConstants(mContext, this, mHandler); final ActiveUids activeUids = new ActiveUids(this, true /* postChangesToAtm */); mProcessList.init(this, activeUids); mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids); @@ -3128,7 +3131,7 @@ public class ActivityManagerService extends IActivityManager.Stub } @GuardedBy("this") - ProcessChangeItem enqueueProcessChangeItemLocked(int uid, int pid) { + ProcessChangeItem enqueueProcessChangeItemLocked(int pid, int uid) { int i = mPendingProcessChanges.size()-1; ActivityManagerService.ProcessChangeItem item = null; while (i >= 0) { @@ -8835,7 +8838,6 @@ public class ActivityManagerService extends IActivityManager.Stub mAtmInternal.updateTopComponentForFactoryTest(); retrieveSettings(); - final int currentUserId = mUserController.getCurrentUserId(); mUgmInternal.onSystemReady(); final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); @@ -8849,6 +8851,16 @@ public class ActivityManagerService extends IActivityManager.Stub } if (goingCallback != null) goingCallback.run(); + // Check the current user here as a user can be started inside goingCallback.run() from + // other system services. + final int currentUserId = mUserController.getCurrentUserId(); + Slog.i(TAG, "Current user:" + currentUserId); + if (currentUserId != UserHandle.USER_SYSTEM && !mUserController.isSystemUserStarted()) { + // User other than system user has started. Make sure that system user is already + // started before switching user. + throw new RuntimeException("System user not started while current user is:" + + currentUserId); + } traceLog.traceBegin("ActivityManagerStartApps"); mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START, Integer.toString(currentUserId), currentUserId); @@ -16414,7 +16426,7 @@ public class ActivityManagerService extends IActivityManager.Stub } proc.setReportedForegroundServiceTypes(fgServiceTypes); - ProcessChangeItem item = enqueueProcessChangeItemLocked(proc.info.uid, proc.pid); + ProcessChangeItem item = enqueueProcessChangeItemLocked(proc.pid, proc.info.uid); item.changes = ProcessChangeItem.CHANGE_FOREGROUND_SERVICES; item.foregroundServiceTypes = fgServiceTypes; diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index cc901821030a..8a462dabd14c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -16,7 +16,6 @@ package com.android.server.am; -import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM; import static android.app.ActivityTaskManager.RESIZE_MODE_USER; @@ -306,9 +305,7 @@ final class ActivityManagerShellCommand extends ShellCommand { mSamplingInterval = 0; mAutoStop = false; mStreaming = false; - mUserId = mInternal.mUserController.handleIncomingUser(Binder.getCallingPid(), - Binder.getCallingUid(), defUser, false, ALLOW_FULL_ONLY, - "ActivityManagerShellCommand", null); + mUserId = defUser; mDisplayId = INVALID_DISPLAY; mWindowingMode = WINDOWING_MODE_UNDEFINED; mActivityType = ACTIVITY_TYPE_UNDEFINED; diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index c1b9a20d143d..924e3310fbd2 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -53,12 +53,14 @@ import android.app.usage.UsageEvents; import android.content.Context; import android.os.Binder; import android.os.Debug; +import android.os.IBinder; import android.os.PowerManagerInternal; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -1110,12 +1112,13 @@ public final class OomAdjuster { } } - for (int conni = s.connections.size() - 1; + ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = s.getConnections(); + for (int conni = serviceConnections.size() - 1; conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND || procState > ActivityManager.PROCESS_STATE_TOP); conni--) { - ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni); + ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni); for (int i = 0; i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 17b244c75819..ce13cd88a192 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -259,6 +259,8 @@ class ProcessRecord implements WindowProcessListener { // A set of tokens that currently contribute to this process being temporarily whitelisted // to start activities even if it's not in the foreground final ArraySet<Binder> mAllowBackgroundActivityStartsTokens = new ArraySet<>(); + // a set of UIDs of all bound clients + private ArraySet<Integer> mBoundClientUids = new ArraySet<>(); String isolatedEntryPoint; // Class to run on start if this is a special isolated process. String[] isolatedEntryPointArgs; // Arguments to pass to isolatedEntryPoint's main(). @@ -561,6 +563,13 @@ class ProcessRecord implements WindowProcessListener { pw.print(prefix); pw.print(" - "); pw.println(receivers.valueAt(i)); } } + if (mAllowBackgroundActivityStartsTokens.size() > 0) { + pw.print(prefix); pw.println("Background activity start whitelist tokens:"); + for (int i = 0; i < mAllowBackgroundActivityStartsTokens.size(); i++) { + pw.print(prefix); pw.print(" - "); + pw.println(mAllowBackgroundActivityStartsTokens.valueAt(i)); + } + } } ProcessRecord(ActivityManagerService _service, ApplicationInfo _info, String _processName, @@ -1186,6 +1195,53 @@ class ProcessRecord implements WindowProcessListener { !mAllowBackgroundActivityStartsTokens.isEmpty()); } + void addBoundClientUids(ArraySet<Integer> clientUids) { + mBoundClientUids.addAll(clientUids); + mWindowProcessController.setBoundClientUids(mBoundClientUids); + } + + void updateBoundClientUids() { + if (services.isEmpty()) { + clearBoundClientUids(); + return; + } + // grab a set of clientUids of all connections of all services + ArraySet<Integer> boundClientUids = new ArraySet<>(); + final int K = services.size(); + for (int j = 0; j < K; j++) { + ArrayMap<IBinder, ArrayList<ConnectionRecord>> conns = + services.valueAt(j).getConnections(); + final int N = conns.size(); + for (int conni = 0; conni < N; conni++) { + ArrayList<ConnectionRecord> c = conns.valueAt(conni); + for (int i = 0; i < c.size(); i++) { + boundClientUids.add(c.get(i).clientUid); + } + } + } + mBoundClientUids = boundClientUids; + mWindowProcessController.setBoundClientUids(mBoundClientUids); + } + + void addBoundClientUidsOfNewService(ServiceRecord sr) { + if (sr == null) { + return; + } + ArrayMap<IBinder, ArrayList<ConnectionRecord>> conns = sr.getConnections(); + for (int conni = conns.size() - 1; conni >= 0; conni--) { + ArrayList<ConnectionRecord> c = conns.valueAt(conni); + for (int i = 0; i < c.size(); i++) { + mBoundClientUids.add(c.get(i).clientUid); + } + } + mWindowProcessController.setBoundClientUids(mBoundClientUids); + } + + void clearBoundClientUids() { + mBoundClientUids.clear(); + mWindowProcessController.setBoundClientUids(mBoundClientUids); + } + void setActiveInstrumentation(ActiveInstrumentation instr) { mInstr = instr; boolean isInstrumenting = instr != null; diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index eeaa7de85b5c..217fd6d71888 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -38,6 +38,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -88,7 +89,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings = new ArrayMap<Intent.FilterComparison, IntentBindRecord>(); // All active bindings to the service. - final ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections + private final ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = new ArrayMap<IBinder, ArrayList<ConnectionRecord>>(); // IBinder -> ConnectionRecord of all bound clients @@ -542,6 +543,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN } } else if (app != null) { app.removeAllowBackgroundActivityStartsToken(this); + app.updateBoundClientUids(); } app = _proc; if (pendingConnectionGroup > 0 && _proc != null) { @@ -563,6 +565,33 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN } } } + if (_proc != null) { + _proc.updateBoundClientUids(); + } + } + + ArrayMap<IBinder, ArrayList<ConnectionRecord>> getConnections() { + return connections; + } + + void putConnection(IBinder binder, ArrayList<ConnectionRecord> clist) { + connections.put(binder, clist); + // if we have a process attached, add bound client uids of this connection to it + if (app != null) { + ArraySet<Integer> boundClientUids = new ArraySet<>(); + for (int i = 0; i < clist.size(); i++) { + boundClientUids.add(clist.get(i).clientUid); + } + app.addBoundClientUids(boundClientUids); + } + } + + void removeConnection(IBinder binder) { + connections.remove(binder); + // if we have a process attached, tell it to update the state of bound clients + if (app != null) { + app.updateBoundClientUids(); + } } void updateHasBindingWhitelistingBgActivityStarts() { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 07c9cca3f6c3..cc4116ebb789 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -956,15 +956,26 @@ class UserController implements Handler.Callback { final int oldUserId = getCurrentUserId(); if (oldUserId == userId) { final UserState state = getStartedUserState(userId); - if (state != null && state.state == STATE_RUNNING_UNLOCKED) { - // We'll skip all later code, so we must tell listener it's already unlocked. - try { - unlockListener.onFinished(userId, null); - } catch (RemoteException ignore) { - // Ignore. + if (state == null) { + Slog.wtf(TAG, "Current user has no UserState"); + // continue starting. + } else { + if (userId == UserHandle.USER_SYSTEM && state.state == STATE_BOOTING) { + // system user start explicitly requested. should continue starting as it + // is not in running state. + } else { + if (state.state == STATE_RUNNING_UNLOCKED) { + // We'll skip all later code, so we must tell listener it's already + // unlocked. + try { + unlockListener.onFinished(userId, null); + } catch (RemoteException ignore) { + // Ignore. + } + } + return true; } } - return true; } if (foreground) { @@ -1743,6 +1754,24 @@ class UserController implements Handler.Callback { return state.state != UserState.STATE_STOPPING && state.state != UserState.STATE_SHUTDOWN; } + /** + * Check if system user is already started. Unlike other user, system user is in STATE_BOOTING + * even if it is not explicitly started. So isUserRunning cannot give the right state + * to check if system user is started or not. + * @return true if system user is started. + */ + boolean isSystemUserStarted() { + synchronized (mLock) { + UserState uss = mStartedUsers.get(UserHandle.USER_SYSTEM); + if (uss == null) { + return false; + } + return uss.state == UserState.STATE_RUNNING_LOCKED + || uss.state == UserState.STATE_RUNNING_UNLOCKING + || uss.state == UserState.STATE_RUNNING_UNLOCKED; + } + } + UserInfo getCurrentUser() { if ((mInjector.checkCallingPermission(INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) && ( diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 5ec8cfa37e4d..5f624ba9be9d 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -896,7 +896,7 @@ public final class AudioDeviceInventory { final long ident = Binder.clearCallingIdentity(); try { - ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL); + ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_CURRENT); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 82a4f1df2e10..32781a90348b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -640,6 +640,26 @@ public class AudioService extends IAudioService.Stub sAudioVolumeGroups = new AudioVolumeGroups(); // Initialize volume + // Priority 1 - Android Property + // Priority 2 - Audio Policy Service + // Priority 3 - Default Value + if (sAudioProductStrategies.size() > 0) { + int numStreamTypes = AudioSystem.getNumStreamTypes(); + + for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { + AudioAttributes attr = + sAudioProductStrategies.getAudioAttributesForLegacyStreamType(streamType); + int maxVolume = AudioSystem.getMaxVolumeIndexForAttributes(attr); + if (maxVolume != -1) { + MAX_STREAM_VOLUME[streamType] = maxVolume; + } + int minVolume = AudioSystem.getMinVolumeIndexForAttributes(attr); + if (minVolume != -1) { + MIN_STREAM_VOLUME[streamType] = minVolume; + } + } + } + int maxCallVolume = SystemProperties.getInt("ro.config.vc_call_vol_steps", -1); if (maxCallVolume != -1) { MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] = maxCallVolume; @@ -1468,6 +1488,11 @@ public class AudioService extends IAudioService.Stub } private int rescaleIndex(int index, int srcStream, int dstStream) { + int max = mStreamStates[srcStream].getMaxIndex(); + if (max == 0) { + Log.e(TAG, "rescaleIndex : Max index should not be zero"); + return mStreamStates[srcStream].getMinIndex(); + } final int rescaled = (index * mStreamStates[dstStream].getMaxIndex() + mStreamStates[srcStream].getMaxIndex() / 2) diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java index b2c7ff3bfdf6..87b272b434d7 100644 --- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java +++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java @@ -69,6 +69,9 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin AudioEffect.Descriptor[] effects, int activeSource, String packName) { if (MediaRecorder.isSystemOnlyAudioSource(source)) { + // still want to log event, it just won't appear in recording configurations + sEventLogger.log(new RecordingEvent(event, uid, session, source, packName) + .printLog(TAG)); return; } String clientEffectName = clientEffects.length == 0 ? "None" : clientEffects[0].name; diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index c385991e1c7b..fe762c06458a 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -39,12 +39,16 @@ import android.hardware.face.FaceManager; import android.hardware.face.IFaceService; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; +import android.os.Build; import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.os.UserManager; +import android.service.restricted_image.RestrictedImagesDumpProto; +import android.service.restricted_image.RestrictedImageProto; +import android.service.restricted_image.RestrictedImageSetProto; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -281,7 +285,9 @@ public class FaceService extends BiometricServiceBase { final long ident = Binder.clearCallingIdentity(); try { - if (args.length > 0 && "--proto".equals(args[0])) { + if (args.length == 1 && "--restricted_image".equals(args[0])) { + dumpRestrictedImage(fd); + } else if (args.length > 0 && "--proto".equals(args[0])) { dumpProto(fd); } else { dumpInternal(pw); @@ -1063,4 +1069,74 @@ public class FaceService extends BiometricServiceBase { mPerformanceMap.clear(); mCryptoPerformanceMap.clear(); } + + private void dumpRestrictedImage(FileDescriptor fd) { + // WARNING: CDD restricts image data from leaving TEE unencrypted on + // production devices: + // [C-1-10] MUST not allow unencrypted access to identifiable biometric + // data or any data derived from it (such as embeddings) to the + // Application Processor outside the context of the TEE. + // As such, this API should only be enabled for testing purposes on + // engineering and userdebug builds. All modules in the software stack + // MUST enforce final build products do NOT have this functionality. + // Additionally, the following check MUST NOT be removed. + if (!(Build.IS_ENG || Build.IS_USERDEBUG)) { + return; + } + + final ProtoOutputStream proto = new ProtoOutputStream(fd); + + final long setToken = proto.start(RestrictedImagesDumpProto.SETS); + + // Name of the service + proto.write(RestrictedImageSetProto.CATEGORY, "face"); + + // Individual images + for (int i = 0; i < 5; i++) { + final long imageToken = proto.start(RestrictedImageSetProto.IMAGES); + proto.write(RestrictedImageProto.MIME_TYPE, "image/png"); + proto.write(RestrictedImageProto.IMAGE_DATA, new byte[] { + // png image data + -119, 80, 78, 71, 13, 10, 26, 10, + 0, 0, 0, 13, 73, 72, 68, 82, + 0, 0, 0, 100, 0, 0, 0, 100, + 1, 3, 0, 0, 0, 74, 44, 7, + 23, 0, 0, 0, 4, 103, 65, 77, + 65, 0, 0, -79, -113, 11, -4, 97, + 5, 0, 0, 0, 1, 115, 82, 71, + 66, 0, -82, -50, 28, -23, 0, 0, + 0, 6, 80, 76, 84, 69, -1, -1, + -1, 0, 0, 0, 85, -62, -45, 126, + 0, 0, 0, -115, 73, 68, 65, 84, + 56, -53, -19, -46, -79, 17, -128, 32, + 12, 5, -48, 120, 22, -106, -116, -32, + 40, -84, 101, -121, -93, 57, 10, 35, + 88, 82, 112, 126, 3, -60, 104, 6, + -112, 70, 127, -59, -69, -53, 29, 33, + -127, -24, 79, -49, -52, -15, 41, 36, + 34, -105, 85, 124, -14, 88, 27, 6, + 28, 68, 1, 82, 62, 22, -95, -108, + 55, -95, 40, -9, -110, -12, 98, -107, + 76, -41, -105, -62, -50, 111, -60, 46, + -14, -4, 24, -89, 42, -103, 16, 63, + -72, -11, -15, 48, -62, 102, -44, 102, + -73, -56, 56, -21, -128, 92, -70, -124, + 117, -46, -67, -77, 82, 80, 121, -44, + -56, 116, 93, -45, -90, -5, -29, -24, + -83, -75, 52, -34, 55, -22, 102, -21, + -105, -124, -23, 71, 87, -7, -25, -59, + -100, -73, -92, -122, -7, -109, -49, -80, + -89, 0, 0, 0, 0, 73, 69, 78, + 68, -82, 66, 96, -126 + }); + // proto.write(RestrictedImageProto.METADATA, flattened_protobuf); + proto.end(imageToken); + } + + // Face service metadata + // proto.write(RestrictedImageSetProto.METADATA, flattened_protobuf); + + proto.end(setToken); + proto.flush(); + } } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java index b8810c8f379f..2df89821611c 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java @@ -24,6 +24,7 @@ import android.hardware.radio.ICloseHandle; import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; +import android.hardware.radio.RadioTuner; import android.hidl.manager.V1_0.IServiceManager; import android.hidl.manager.V1_0.IServiceNotification; import android.os.IHwBinder.DeathRecipient; @@ -49,9 +50,17 @@ public class BroadcastRadioService { @GuardedBy("mLock") private final Map<String, Integer> mServiceNameToModuleIdMap = new HashMap<>(); + // Map from module ID to RadioModule created by mServiceListener.onRegistration(). @GuardedBy("mLock") private final Map<Integer, RadioModule> mModules = new HashMap<>(); + // Map from module ID to TunerSession created by openSession(). + // + // Because this service currently implements a 1 AIDL to 1 HAL policy, mTunerSessions is used to + // enforce the "aggresive open" policy mandated for IBroadcastRadio.openSession(). In the + // future, this solution will be replaced with a multiple-AIDL to 1 HAL implementation. + private final Map<Integer, TunerSession> mTunerSessions = new HashMap<>(); + private IServiceNotification.Stub mServiceListener = new IServiceNotification.Stub() { @Override public void onRegistration(String fqName, String serviceName, boolean preexisting) { @@ -72,6 +81,7 @@ public class BroadcastRadioService { } Slog.v(TAG, "loaded broadcast radio module " + moduleId + ": " + serviceName + " (HAL 2.0)"); + closeTunerSessionLocked(moduleId); mModules.put(moduleId, module); if (newService) { @@ -96,6 +106,7 @@ public class BroadcastRadioService { synchronized (mLock) { int moduleId = (int) cookie; mModules.remove(moduleId); + closeTunerSessionLocked(moduleId); for (Map.Entry<String, Integer> entry : mServiceNameToModuleIdMap.entrySet()) { if (entry.getValue() == moduleId) { @@ -152,16 +163,20 @@ public class BroadcastRadioService { RadioModule module = null; synchronized (mLock) { module = mModules.get(moduleId); - } - if (module == null) { - throw new IllegalArgumentException("Invalid module ID"); + if (module == null) { + throw new IllegalArgumentException("Invalid module ID"); + } + closeTunerSessionLocked(moduleId); } - TunerSession session = module.openSession(callback); + TunerSession tunerSession = module.openSession(callback); + synchronized (mLock) { + mTunerSessions.put(moduleId, tunerSession); + } if (legacyConfig != null) { - session.setConfiguration(legacyConfig); + tunerSession.setConfiguration(legacyConfig); } - return session; + return tunerSession; } public ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes, @@ -183,4 +198,12 @@ public class BroadcastRadioService { } return aggregator; } + + private void closeTunerSessionLocked(int moduleId) { + TunerSession tunerSession = mTunerSessions.remove(moduleId); + if (tunerSession != null) { + Slog.d(TAG, "Closing previous TunerSession"); + tunerSession.close(RadioTuner.ERROR_HARDWARE_FAILURE); + } + } } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java index 9833507f8fce..05ca144ed3b9 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java @@ -17,6 +17,7 @@ package com.android.server.broadcastradio.hal2; import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Bitmap; import android.hardware.broadcastradio.V2_0.ConfigFlag; import android.hardware.broadcastradio.V2_0.ITunerSession; @@ -58,8 +59,22 @@ class TunerSession extends ITuner.Stub { @Override public void close() { + close(null); + } + + /** + * Closes the TunerSession. If error is non-null, the client's onError() callback is invoked + * first with the specified error, see {@link + * android.hardware.radio.RadioTuner.Callback#onError}. + * + * @param error Optional error to send to client before session is closed. + */ + public void close(@Nullable Integer error) { synchronized (mLock) { if (mIsClosed) return; + if (error != null) { + TunerCallback.dispatch(() -> mCallback.mClientCb.onError(error)); + } mIsClosed = true; } } diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java index d8bb635f2ce8..1913635f80e2 100644 --- a/services/core/java/com/android/server/connectivity/DnsManager.java +++ b/services/core/java/com/android/server/connectivity/DnsManager.java @@ -30,13 +30,15 @@ import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.net.IDnsResolver; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkUtils; import android.net.Uri; import android.net.shared.PrivateDnsConfig; import android.os.Binder; -import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.ServiceSpecificException; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; @@ -229,7 +231,7 @@ public class DnsManager { private final Context mContext; private final ContentResolver mContentResolver; - private final INetworkManagementService mNMS; + private final IDnsResolver mDnsResolver; private final MockableSystemProperties mSystemProperties; // TODO: Replace these Maps with SparseArrays. private final Map<Integer, PrivateDnsConfig> mPrivateDnsMap; @@ -243,10 +245,10 @@ public class DnsManager { private String mPrivateDnsMode; private String mPrivateDnsSpecifier; - public DnsManager(Context ctx, INetworkManagementService nms, MockableSystemProperties sp) { + public DnsManager(Context ctx, IDnsResolver dnsResolver, MockableSystemProperties sp) { mContext = ctx; mContentResolver = mContext.getContentResolver(); - mNMS = nms; + mDnsResolver = dnsResolver; mSystemProperties = sp; mPrivateDnsMap = new HashMap<>(); mPrivateDnsValidationMap = new HashMap<>(); @@ -260,6 +262,12 @@ public class DnsManager { } public void removeNetwork(Network network) { + try { + mDnsResolver.clearResolverConfiguration(network.netId); + } catch (RemoteException | ServiceSpecificException e) { + Slog.e(TAG, "Error clearing DNS configuration: " + e); + return; + } mPrivateDnsMap.remove(network.netId); mPrivateDnsValidationMap.remove(network.netId); } @@ -344,10 +352,12 @@ public class DnsManager { Slog.d(TAG, String.format("setDnsConfigurationForNetwork(%d, %s, %s, %s, %s, %s)", netId, Arrays.toString(assignedServers), Arrays.toString(domainStrs), Arrays.toString(params), tlsHostname, Arrays.toString(tlsServers))); + final String[] tlsFingerprints = new String[0]; try { - mNMS.setDnsConfigurationForNetwork( - netId, assignedServers, domainStrs, params, tlsHostname, tlsServers); - } catch (Exception e) { + mDnsResolver.setResolverConfiguration( + netId, assignedServers, domainStrs, params, + tlsHostname, tlsServers, tlsFingerprints); + } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Error setting DNS configuration: " + e); return; } diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java index ce887eb4f0fe..d7a57b992eef 100644 --- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -154,12 +154,19 @@ public class KeepaliveTracker { // keepalives are sent cannot be reused by another app even if the fd gets closed by // the user. A null is acceptable here for backward compatibility of PacketKeepalive // API. - // TODO: don't accept null fd after legacy packetKeepalive API is removed. try { if (fd != null) { mFd = Os.dup(fd); } else { - Log.d(TAG, "uid/pid " + mUid + "/" + mPid + " calls with null fd"); + Log.d(TAG, toString() + " calls with null fd"); + if (!mPrivileged) { + throw new SecurityException( + "null fd is not allowed for unprivileged access."); + } + if (mType == TYPE_TCP) { + throw new IllegalArgumentException( + "null fd is not allowed for tcp socket keepalives."); + } mFd = null; } } catch (ErrnoException e) { @@ -480,7 +487,6 @@ public class KeepaliveTracker { } } else { // Keepalive successfully stopped, or error. - ki.mStartedState = KeepaliveInfo.NOT_STARTED; if (reason == SUCCESS) { // The message indicated success stopping : don't call handleStopKeepalive. if (DBG) Log.d(TAG, "Successfully stopped keepalive " + slot + " on " + nai.name()); @@ -490,6 +496,7 @@ public class KeepaliveTracker { handleStopKeepalive(nai, slot, reason); if (DBG) Log.d(TAG, "Keepalive " + slot + " on " + nai.name() + " error " + reason); } + ki.mStartedState = KeepaliveInfo.NOT_STARTED; } } @@ -531,7 +538,8 @@ public class KeepaliveTracker { try { ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds, KeepaliveInfo.TYPE_NATT, fd); - } catch (InvalidSocketException e) { + } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) { + Log.e(TAG, "Fail to construct keepalive", e); notifyErrorCallback(cb, ERROR_INVALID_SOCKET); return; } @@ -570,7 +578,8 @@ public class KeepaliveTracker { try { ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds, KeepaliveInfo.TYPE_TCP, fd); - } catch (InvalidSocketException e) { + } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) { + Log.e(TAG, "Fail to construct keepalive e=" + e); notifyErrorCallback(cb, ERROR_INVALID_SOCKET); return; } diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index 262ba7a475bb..66bd27c1a76b 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -17,6 +17,7 @@ package com.android.server.connectivity; import android.net.ConnectivityManager; +import android.net.IDnsResolver; import android.net.INetd; import android.net.InetAddresses; import android.net.InterfaceConfiguration; @@ -65,6 +66,7 @@ public class Nat464Xlat extends BaseNetworkObserver { NetworkInfo.State.SUSPENDED, }; + private final IDnsResolver mDnsResolver; private final INetd mNetd; private final INetworkManagementService mNMService; @@ -84,7 +86,9 @@ public class Nat464Xlat extends BaseNetworkObserver { private Inet6Address mIPv6Address; private State mState = State.IDLE; - public Nat464Xlat(NetworkAgentInfo nai, INetd netd, INetworkManagementService nmService) { + public Nat464Xlat(NetworkAgentInfo nai, INetd netd, IDnsResolver dnsResolver, + INetworkManagementService nmService) { + mDnsResolver = dnsResolver; mNetd = netd; mNMService = nmService; mNetwork = nai; @@ -269,7 +273,7 @@ public class Nat464Xlat extends BaseNetworkObserver { private void startPrefixDiscovery() { try { - mNetd.resolverStartPrefix64Discovery(getNetId()); + mDnsResolver.startPrefix64Discovery(getNetId()); mState = State.DISCOVERING; } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Error starting prefix discovery on netId " + getNetId() + ": " + e); @@ -278,7 +282,7 @@ public class Nat464Xlat extends BaseNetworkObserver { private void stopPrefixDiscovery() { try { - mNetd.resolverStopPrefix64Discovery(getNetId()); + mDnsResolver.stopPrefix64Discovery(getNetId()); } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Error stopping prefix discovery on netId " + getNetId() + ": " + e); } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 8f2825ca72d8..cfa91314f490 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -17,6 +17,7 @@ package com.android.server.connectivity; import android.content.Context; +import android.net.IDnsResolver; import android.net.INetd; import android.net.INetworkMonitor; import android.net.LinkProperties; @@ -29,6 +30,7 @@ import android.net.NetworkState; import android.os.Handler; import android.os.INetworkManagementService; import android.os.Messenger; +import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; @@ -120,7 +122,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // This Network object is always valid. public final Network network; public LinkProperties linkProperties; - // This should only be modified via ConnectivityService.updateCapabilities(). + // This should only be modified by ConnectivityService, via setNetworkCapabilities(). + // TODO: make this private with a getter. public NetworkCapabilities networkCapabilities; public final NetworkMisc networkMisc; // Indicates if netd has been told to create this Network. From this point on the appropriate @@ -255,7 +258,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, NetworkMisc misc, ConnectivityService connService, INetd netd, - INetworkManagementService nms, int factorySerialNumber) { + IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber) { this.messenger = messenger; asyncChannel = ac; network = net; @@ -263,7 +266,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { linkProperties = lp; networkCapabilities = nc; currentScore = score; - clatd = new Nat464Xlat(this, netd, nms); + clatd = new Nat464Xlat(this, netd, dnsResolver, nms); mConnService = connService; mContext = context; mHandler = handler; @@ -278,6 +281,25 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { mNetworkMonitor = networkMonitor; } + /** + * Set the NetworkCapabilities on this NetworkAgentInfo. Also attempts to notify NetworkMonitor + * of the new capabilities, if NetworkMonitor has been created. + * + * <p>If {@link NetworkMonitor#notifyNetworkCapabilitiesChanged(NetworkCapabilities)} fails, + * the exception is logged but not reported to callers. + */ + public void setNetworkCapabilities(NetworkCapabilities nc) { + networkCapabilities = nc; + final INetworkMonitor nm = mNetworkMonitor; + if (nm != null) { + try { + nm.notifyNetworkCapabilitiesChanged(nc); + } catch (RemoteException e) { + Log.e(TAG, "Error notifying NetworkMonitor of updated NetworkCapabilities", e); + } + } + } + public ConnectivityService connService() { return mConnService; } diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 828a1e58868a..ac3d6def6f80 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -19,6 +19,7 @@ package com.android.server.connectivity; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; import android.app.Notification; import android.app.NotificationManager; @@ -26,9 +27,12 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.net.NetworkSpecifier; +import android.net.StringNetworkSpecifier; import android.net.wifi.WifiInfo; import android.os.UserHandle; import android.telephony.AccessNetworkConstants.TransportType; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Slog; @@ -195,7 +199,20 @@ public class NetworkNotificationManager { title = r.getString(R.string.network_available_sign_in, 0); // TODO: Change this to pull from NetworkInfo once a printable // name has been added to it - details = mTelephonyManager.getNetworkOperatorName(); + NetworkSpecifier specifier = nai.networkCapabilities.getNetworkSpecifier(); + int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; + if (specifier instanceof StringNetworkSpecifier) { + try { + subId = Integer.parseInt( + ((StringNetworkSpecifier) specifier).specifier); + } catch (NumberFormatException e) { + Slog.e(TAG, "NumberFormatException on " + + ((StringNetworkSpecifier) specifier).specifier); + } + } + + details = mTelephonyManager.createForSubscriptionId(subId) + .getNetworkOperatorName(); break; default: title = r.getString(R.string.network_available_sign_in, 0); diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 37fe3d094179..b140c1b25320 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -82,7 +82,6 @@ import android.os.Handler; import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; -import android.os.Parcel; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; @@ -230,8 +229,15 @@ public class Tethering extends BaseNetworkObserver { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); - mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM, - mLog, systemProperties); + // EntitlementManager will send EVENT_UPSTREAM_PERMISSION_CHANGED when cellular upstream + // permission is changed according to entitlement check result. + mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM, mLog, + TetherMasterSM.EVENT_UPSTREAM_PERMISSION_CHANGED, systemProperties); + mEntitlementMgr.setOnUiEntitlementFailedListener((int downstream) -> { + mLog.log("OBSERVED UiEnitlementFailed"); + stopTethering(downstream); + }); + mCarrierConfigChange = new VersionedBroadcastListener( "CarrierConfigChangeListener", mContext, mHandler, filter, (Intent ignored) -> { @@ -363,55 +369,28 @@ public class Tethering extends BaseNetworkObserver { } public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) { - mEntitlementMgr.startTethering(type); - if (!mEntitlementMgr.isTetherProvisioningRequired()) { - enableTetheringInternal(type, true, receiver); - return; - } - - final ResultReceiver proxyReceiver = getProxyReceiver(type, receiver); - if (showProvisioningUi) { - mEntitlementMgr.runUiTetherProvisioningAndEnable(type, proxyReceiver); - } else { - mEntitlementMgr.runSilentTetherProvisioningAndEnable(type, proxyReceiver); - } + mEntitlementMgr.startProvisioningIfNeeded(type, showProvisioningUi); + enableTetheringInternal(type, true /* enabled */, receiver); } public void stopTethering(int type) { - enableTetheringInternal(type, false, null); - mEntitlementMgr.stopTethering(type); - if (mEntitlementMgr.isTetherProvisioningRequired()) { - // There are lurking bugs where the notion of "provisioning required" or - // "tethering supported" may change without notifying tethering properly, then - // tethering can't shutdown correctly. - // TODO: cancel re-check all the time - if (mDeps.isTetheringSupported()) { - mEntitlementMgr.cancelTetherProvisioningRechecks(type); - } - } + enableTetheringInternal(type, false /* disabled */, null); + mEntitlementMgr.stopProvisioningIfNeeded(type); } /** - * Enables or disables tethering for the given type. This should only be called once - * provisioning has succeeded or is not necessary. It will also schedule provisioning rechecks - * for the specified interface. + * Enables or disables tethering for the given type. If provisioning is required, it will + * schedule provisioning rechecks for the specified interface. */ private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) { - boolean isProvisioningRequired = enable && mEntitlementMgr.isTetherProvisioningRequired(); int result; switch (type) { case TETHERING_WIFI: result = setWifiTethering(enable); - if (isProvisioningRequired && result == TETHER_ERROR_NO_ERROR) { - mEntitlementMgr.scheduleProvisioningRechecks(type); - } sendTetherResult(receiver, result); break; case TETHERING_USB: result = setUsbTethering(enable); - if (isProvisioningRequired && result == TETHER_ERROR_NO_ERROR) { - mEntitlementMgr.scheduleProvisioningRechecks(type); - } sendTetherResult(receiver, result); break; case TETHERING_BLUETOOTH: @@ -430,21 +409,25 @@ public class Tethering extends BaseNetworkObserver { } private int setWifiTethering(final boolean enable) { - int rval = TETHER_ERROR_MASTER_ERROR; final long ident = Binder.clearCallingIdentity(); try { synchronized (mPublicSync) { - mWifiTetherRequested = enable; final WifiManager mgr = getWifiManager(); + if (mgr == null) { + mLog.e("setWifiTethering: failed to get WifiManager!"); + return TETHER_ERROR_SERVICE_UNAVAIL; + } if ((enable && mgr.startSoftAp(null /* use existing wifi config */)) || (!enable && mgr.stopSoftAp())) { - rval = TETHER_ERROR_NO_ERROR; + mWifiTetherRequested = enable; + return TETHER_ERROR_NO_ERROR; } } } finally { Binder.restoreCallingIdentity(ident); } - return rval; + + return TETHER_ERROR_MASTER_ERROR; } private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) { @@ -469,46 +452,11 @@ public class Tethering extends BaseNetworkObserver { ? TETHER_ERROR_NO_ERROR : TETHER_ERROR_MASTER_ERROR; sendTetherResult(receiver, result); - if (enable && mEntitlementMgr.isTetherProvisioningRequired()) { - mEntitlementMgr.scheduleProvisioningRechecks(TETHERING_BLUETOOTH); - } adapter.closeProfileProxy(BluetoothProfile.PAN, proxy); } }, BluetoothProfile.PAN); } - /** - * Creates a proxy {@link ResultReceiver} which enables tethering if the provisioning result - * is successful before firing back up to the wrapped receiver. - * - * @param type The type of tethering being enabled. - * @param receiver A ResultReceiver which will be called back with an int resultCode. - * @return The proxy receiver. - */ - private ResultReceiver getProxyReceiver(final int type, final ResultReceiver receiver) { - ResultReceiver rr = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - // If provisioning is successful, enable tethering, otherwise just send the error. - if (resultCode == TETHER_ERROR_NO_ERROR) { - enableTetheringInternal(type, true, receiver); - } else { - sendTetherResult(receiver, resultCode); - } - mEntitlementMgr.updateEntitlementCacheValue(type, resultCode); - } - }; - - // The following is necessary to avoid unmarshalling issues when sending the receiver - // across processes. - Parcel parcel = Parcel.obtain(); - rr.writeToParcel(parcel,0); - parcel.setDataPosition(0); - ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel); - parcel.recycle(); - return receiverForSending; - } - public int tether(String iface) { return tether(iface, IpServer.STATE_TETHERED); } @@ -787,6 +735,7 @@ public class Tethering extends BaseNetworkObserver { if (!usbConnected && mRndisEnabled) { // Turn off tethering if it was enabled and there is a disconnect. tetherMatchingInterfaces(IpServer.STATE_AVAILABLE, TETHERING_USB); + mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_USB); } else if (usbConfigured && rndisEnabled) { // Tether if rndis is enabled and usb is configured. tetherMatchingInterfaces(IpServer.STATE_TETHERED, TETHERING_USB); @@ -813,6 +762,7 @@ public class Tethering extends BaseNetworkObserver { case WifiManager.WIFI_AP_STATE_FAILED: default: disableWifiIpServingLocked(ifname, curState); + mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_WIFI); break; } } @@ -996,6 +946,11 @@ public class Tethering extends BaseNetworkObserver { public int setUsbTethering(boolean enable) { if (VDBG) Log.d(TAG, "setUsbTethering(" + enable + ")"); UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE); + if (usbManager == null) { + mLog.e("setUsbTethering: failed to get UsbManager!"); + return TETHER_ERROR_SERVICE_UNAVAIL; + } + synchronized (mPublicSync) { usbManager.setCurrentFunctions(enable ? UsbManager.FUNCTION_RNDIS : UsbManager.FUNCTION_NONE); @@ -1090,6 +1045,8 @@ public class Tethering extends BaseNetworkObserver { // we treated the error and want now to clear it static final int CMD_CLEAR_ERROR = BASE_MASTER + 6; static final int EVENT_IFACE_UPDATE_LINKPROPERTIES = BASE_MASTER + 7; + // Events from EntitlementManager to choose upstream again. + static final int EVENT_UPSTREAM_PERMISSION_CHANGED = BASE_MASTER + 8; private final State mInitialState; private final State mTetherModeAliveState; @@ -1504,6 +1461,7 @@ public class Tethering extends BaseNetworkObserver { } break; } + case EVENT_UPSTREAM_PERMISSION_CHANGED: case CMD_UPSTREAM_CHANGED: updateUpstreamWanted(); if (!mUpstreamWanted) break; @@ -1694,7 +1652,8 @@ public class Tethering extends BaseNetworkObserver { } public void systemReady() { - mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest()); + mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest(), + mEntitlementMgr); } /** Get the latest value of the tethering entitlement check. */ @@ -1755,6 +1714,11 @@ public class Tethering extends BaseNetworkObserver { cfg.dump(pw); pw.decreaseIndent(); + pw.println("Entitlement:"); + pw.increaseIndent(); + mEntitlementMgr.dump(pw); + pw.decreaseIndent(); + synchronized (mPublicSync) { pw.println("Tether state:"); pw.increaseIndent(); diff --git a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java index 70ab38983446..764a6ebc2d98 100644 --- a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java +++ b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java @@ -18,9 +18,11 @@ package com.android.server.connectivity.tethering; import static android.net.ConnectivityManager.EXTRA_ADD_TETHER_TYPE; import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK; -import static android.net.ConnectivityManager.EXTRA_REM_TETHER_TYPE; import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION; -import static android.net.ConnectivityManager.EXTRA_SET_ALARM; +import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; +import static android.net.ConnectivityManager.TETHERING_INVALID; +import static android.net.ConnectivityManager.TETHERING_USB; +import static android.net.ConnectivityManager.TETHERING_WIFI; import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; @@ -28,17 +30,24 @@ import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; import static com.android.internal.R.string.config_wifi_tether_enable; import android.annotation.Nullable; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Resources; import android.net.util.SharedLog; import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.Parcel; import android.os.PersistableBundle; import android.os.ResultReceiver; +import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.telephony.CarrierConfigManager; @@ -46,48 +55,93 @@ import android.util.ArraySet; import android.util.Log; import android.util.SparseIntArray; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.StateMachine; import com.android.server.connectivity.MockableSystemProperties; +import java.io.PrintWriter; + /** - * This class encapsulates entitlement/provisioning mechanics - * provisioning check only applies to the use of the mobile network as an upstream + * Re-check tethering provisioning for enabled downstream tether types. + * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. * + * All methods of this class must be accessed from the thread of tethering + * state machine. * @hide */ public class EntitlementManager { private static final String TAG = EntitlementManager.class.getSimpleName(); private static final boolean DBG = false; + @VisibleForTesting + protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning"; + private static final String ACTION_PROVISIONING_ALARM = + "com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM"; + // {@link ComponentName} of the Service used to run tether provisioning. private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString( Resources.getSystem().getString(config_wifi_tether_enable)); - protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning"; + private static final int MS_PER_HOUR = 60 * 60 * 1000; + private static final int EVENT_START_PROVISIONING = 0; + private static final int EVENT_STOP_PROVISIONING = 1; + private static final int EVENT_UPSTREAM_CHANGED = 2; + private static final int EVENT_MAYBE_RUN_PROVISIONING = 3; + private static final int EVENT_GET_ENTITLEMENT_VALUE = 4; + // The ArraySet contains enabled downstream types, ex: // {@link ConnectivityManager.TETHERING_WIFI} // {@link ConnectivityManager.TETHERING_USB} // {@link ConnectivityManager.TETHERING_BLUETOOTH} - @GuardedBy("mCurrentTethers") private final ArraySet<Integer> mCurrentTethers; private final Context mContext; + private final int mPermissionChangeMessageCode; private final MockableSystemProperties mSystemProperties; private final SharedLog mLog; - private final Handler mMasterHandler; private final SparseIntArray mEntitlementCacheValue; - @Nullable - private TetheringConfiguration mConfig; + private final EntitlementHandler mHandler; + private @Nullable TetheringConfiguration mConfig; + private final StateMachine mTetherMasterSM; + // Key: ConnectivityManager.TETHERING_*(downstream). + // Value: ConnectivityManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result). + private final SparseIntArray mCellularPermitted; + private PendingIntent mProvisioningRecheckAlarm; + private boolean mCellularUpstreamPermitted = true; + private boolean mUsingCellularAsUpstream = false; + private boolean mNeedReRunProvisioningUi = false; + private OnUiEntitlementFailedListener mListener; public EntitlementManager(Context ctx, StateMachine tetherMasterSM, SharedLog log, - MockableSystemProperties systemProperties) { + int permissionChangeMessageCode, MockableSystemProperties systemProperties) { + mContext = ctx; mLog = log.forSubComponent(TAG); mCurrentTethers = new ArraySet<Integer>(); + mCellularPermitted = new SparseIntArray(); mSystemProperties = systemProperties; mEntitlementCacheValue = new SparseIntArray(); - mMasterHandler = tetherMasterSM.getHandler(); + mTetherMasterSM = tetherMasterSM; + mPermissionChangeMessageCode = permissionChangeMessageCode; + final Handler masterHandler = tetherMasterSM.getHandler(); + // Create entitlement's own handler which is associated with TetherMaster thread + // let all entitlement processes run in the same thread. + mHandler = new EntitlementHandler(masterHandler.getLooper()); + mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM), + null, mHandler); + } + + public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) { + mListener = listener; + } + + /** Callback fired when UI entitlement failed. */ + public interface OnUiEntitlementFailedListener { + /** + * Ui entitlement check fails in |downstream|. + * + * @param downstream tethering type from ConnectivityManager.TETHERING_{@code *}. + */ + void onUiEntitlementFailed(int downstream); } /** @@ -99,24 +153,118 @@ public class EntitlementManager { } /** - * Tell EntitlementManager that a given type of tethering has been enabled + * Check if cellular upstream is permitted. + */ + public boolean isCellularUpstreamPermitted() { + return mCellularUpstreamPermitted; + } + + /** + * This is called when tethering starts. + * Launch provisioning app if upstream is cellular. * - * @param type Tethering type + * @param downstreamType tethering type from ConnectivityManager.TETHERING_{@code *} + * @param showProvisioningUi a boolean indicating whether to show the + * provisioning app UI if there is one. */ - public void startTethering(int type) { - synchronized (mCurrentTethers) { - mCurrentTethers.add(type); + public void startProvisioningIfNeeded(int downstreamType, boolean showProvisioningUi) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_START_PROVISIONING, + downstreamType, encodeBool(showProvisioningUi))); + } + + private void handleStartProvisioningIfNeeded(int type, boolean showProvisioningUi) { + if (!isValidDownstreamType(type)) return; + + if (!mCurrentTethers.contains(type)) mCurrentTethers.add(type); + + if (isTetherProvisioningRequired()) { + // If provisioning is required and the result is not available yet, + // cellular upstream should not be allowed. + if (mCellularPermitted.size() == 0) { + mCellularUpstreamPermitted = false; + } + // If upstream is not cellular, provisioning app would not be launched + // till upstream change to cellular. + if (mUsingCellularAsUpstream) { + if (showProvisioningUi) { + runUiTetherProvisioning(type); + } else { + runSilentTetherProvisioning(type); + } + mNeedReRunProvisioningUi = false; + } else { + mNeedReRunProvisioningUi |= showProvisioningUi; + } + } else { + mCellularUpstreamPermitted = true; } } /** * Tell EntitlementManager that a given type of tethering has been disabled * - * @param type Tethering type + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} */ - public void stopTethering(int type) { - synchronized (mCurrentTethers) { - mCurrentTethers.remove(type); + public void stopProvisioningIfNeeded(int type) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_STOP_PROVISIONING, type, 0)); + } + + private void handleStopProvisioningIfNeeded(int type) { + if (!isValidDownstreamType(type)) return; + + mCurrentTethers.remove(type); + // There are lurking bugs where the notion of "provisioning required" or + // "tethering supported" may change without without tethering being notified properly. + // Remove the mapping all the time no matter provisioning is required or not. + removeDownstreamMapping(type); + } + + /** + * Notify EntitlementManager if upstream is cellular or not. + * + * @param isCellular whether tethering upstream is cellular. + */ + public void notifyUpstream(boolean isCellular) { + mHandler.sendMessage(mHandler.obtainMessage( + EVENT_UPSTREAM_CHANGED, encodeBool(isCellular), 0)); + } + + private void handleNotifyUpstream(boolean isCellular) { + if (DBG) { + Log.d(TAG, "notifyUpstream: " + isCellular + + ", mCellularUpstreamPermitted: " + mCellularUpstreamPermitted + + ", mNeedReRunProvisioningUi: " + mNeedReRunProvisioningUi); + } + mUsingCellularAsUpstream = isCellular; + + if (mUsingCellularAsUpstream) { + handleMaybeRunProvisioning(); + } + } + + /** Run provisioning if needed */ + public void maybeRunProvisioning() { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_MAYBE_RUN_PROVISIONING)); + } + + private void handleMaybeRunProvisioning() { + if (mCurrentTethers.size() == 0 || !isTetherProvisioningRequired()) { + return; + } + + // Whenever any entitlement value changes, all downstreams will re-evaluate whether they + // are allowed. Therefore even if the silent check here ends in a failure and the UI later + // yields success, then the downstream that got a failure will re-evaluate as a result of + // the change and get the new correct value. + for (Integer downstream : mCurrentTethers) { + if (mCellularPermitted.indexOfKey(downstream) < 0) { + if (mNeedReRunProvisioningUi) { + mNeedReRunProvisioningUi = false; + runUiTetherProvisioning(downstream); + } else { + runSilentTetherProvisioning(downstream); + } + } } } @@ -138,23 +286,32 @@ public class EntitlementManager { } /** - * Re-check tethering provisioning for enabled downstream tether types. + * Re-check tethering provisioning for all enabled tether types. * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. + * + * Note: this method is only called from TetherMaster on the handler thread. + * If there are new callers from different threads, the logic should move to + * masterHandler to avoid race conditions. */ public void reevaluateSimCardProvisioning() { - synchronized (mEntitlementCacheValue) { - mEntitlementCacheValue.clear(); - } + if (DBG) Log.d(TAG, "reevaluateSimCardProvisioning"); - if (!mConfig.hasMobileHotspotProvisionApp()) return; - if (carrierConfigAffirmsEntitlementCheckNotRequired()) return; + if (!mHandler.getLooper().isCurrentThread()) { + // Except for test, this log should not appear in normal flow. + mLog.log("reevaluateSimCardProvisioning() don't run in TetherMaster thread"); + } + mEntitlementCacheValue.clear(); + mCellularPermitted.clear(); - final ArraySet<Integer> reevaluateType; - synchronized (mCurrentTethers) { - reevaluateType = new ArraySet<Integer>(mCurrentTethers); + // TODO: refine provisioning check to isTetherProvisioningRequired() ?? + if (!mConfig.hasMobileHotspotProvisionApp() + || carrierConfigAffirmsEntitlementCheckNotRequired()) { + evaluateCellularPermission(); + return; } - for (Integer type : reevaluateType) { - startProvisionIntent(type); + + if (mUsingCellularAsUpstream) { + handleMaybeRunProvisioning(); } } @@ -189,7 +346,16 @@ public class EntitlementManager { return !isEntitlementCheckRequired; } - public void runSilentTetherProvisioningAndEnable(int type, ResultReceiver receiver) { + /** + * Run no UI tethering provisioning check. + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + */ + protected void runSilentTetherProvisioning(int type) { + if (DBG) Log.d(TAG, "runSilentTetherProvisioning: " + type); + // For silent provisioning, settings would stop tethering when entitlement fail. + ResultReceiver receiver = buildProxyReceiver(type, + false/* notifyFail */, null); + Intent intent = new Intent(); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); intent.putExtra(EXTRA_RUN_PROVISION, true); @@ -203,12 +369,21 @@ public class EntitlementManager { } } - public void runUiTetherProvisioningAndEnable(int type, ResultReceiver receiver) { + /** + * Run the UI-enabled tethering provisioning check. + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + */ + @VisibleForTesting + protected void runUiTetherProvisioning(int type) { + ResultReceiver receiver = buildProxyReceiver(type, + true/* notifyFail */, null); runUiTetherProvisioning(type, receiver); } @VisibleForTesting protected void runUiTetherProvisioning(int type, ResultReceiver receiver) { + if (DBG) Log.d(TAG, "runUiTetherProvisioning: " + type); + Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); @@ -221,56 +396,210 @@ public class EntitlementManager { } } - // Used by the SIM card change observation code. - // TODO: De-duplicate with above code, where possible. - private void startProvisionIntent(int tetherType) { - final Intent startProvIntent = new Intent(); - startProvIntent.putExtra(EXTRA_ADD_TETHER_TYPE, tetherType); - startProvIntent.putExtra(EXTRA_RUN_PROVISION, true); - startProvIntent.setComponent(TETHER_SERVICE); - mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT); + // Not needed to check if this don't run on the handler thread because it's private. + private void scheduleProvisioningRechecks() { + if (mProvisioningRecheckAlarm == null) { + final int period = mConfig.provisioningCheckPeriod; + if (period <= 0) return; + + Intent intent = new Intent(ACTION_PROVISIONING_ALARM); + mProvisioningRecheckAlarm = PendingIntent.getBroadcast(mContext, 0, intent, 0); + AlarmManager alarmManager = (AlarmManager) mContext.getSystemService( + Context.ALARM_SERVICE); + long periodMs = period * MS_PER_HOUR; + long firstAlarmTime = SystemClock.elapsedRealtime() + periodMs; + alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, firstAlarmTime, periodMs, + mProvisioningRecheckAlarm); + } } - public void scheduleProvisioningRechecks(int type) { - Intent intent = new Intent(); - intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); - intent.putExtra(EXTRA_SET_ALARM, true); - intent.setComponent(TETHER_SERVICE); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.startServiceAsUser(intent, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(ident); + private void cancelTetherProvisioningRechecks() { + if (mProvisioningRecheckAlarm != null) { + AlarmManager alarmManager = (AlarmManager) mContext.getSystemService( + Context.ALARM_SERVICE); + alarmManager.cancel(mProvisioningRecheckAlarm); + mProvisioningRecheckAlarm = null; } } - public void cancelTetherProvisioningRechecks(int type) { - Intent intent = new Intent(); - intent.putExtra(EXTRA_REM_TETHER_TYPE, type); - intent.setComponent(TETHER_SERVICE); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.startServiceAsUser(intent, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(ident); + private void evaluateCellularPermission() { + final boolean oldPermitted = mCellularUpstreamPermitted; + mCellularUpstreamPermitted = (!isTetherProvisioningRequired() + || mCellularPermitted.indexOfValue(TETHER_ERROR_NO_ERROR) > -1); + + if (DBG) { + Log.d(TAG, "Cellular permission change from " + oldPermitted + + " to " + mCellularUpstreamPermitted); + } + + if (mCellularUpstreamPermitted != oldPermitted) { + mLog.log("Cellular permission change: " + mCellularUpstreamPermitted); + mTetherMasterSM.sendMessage(mPermissionChangeMessageCode); + } + // Only schedule periodic re-check when tether is provisioned + // and the result is ok. + if (mCellularUpstreamPermitted && mCellularPermitted.size() > 0) { + scheduleProvisioningRechecks(); + } else { + cancelTetherProvisioningRechecks(); + } + } + + /** + * Add the mapping between provisioning result and tethering type. + * Notify UpstreamNetworkMonitor if Cellular permission changes. + * + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param resultCode Provisioning result + */ + protected void addDownstreamMapping(int type, int resultCode) { + if (DBG) { + Log.d(TAG, "addDownstreamMapping: " + type + ", result: " + resultCode + + " ,TetherTypeRequested: " + mCurrentTethers.contains(type)); } + if (!mCurrentTethers.contains(type)) return; + + mCellularPermitted.put(type, resultCode); + evaluateCellularPermission(); } - private ResultReceiver buildProxyReceiver(int type, final ResultReceiver receiver) { - ResultReceiver rr = new ResultReceiver(mMasterHandler) { + /** + * Remove the mapping for input tethering type. + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + */ + protected void removeDownstreamMapping(int type) { + if (DBG) Log.d(TAG, "removeDownstreamMapping: " + type); + mCellularPermitted.delete(type); + evaluateCellularPermission(); + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_PROVISIONING_ALARM.equals(intent.getAction())) { + mLog.log("Received provisioning alarm"); + reevaluateSimCardProvisioning(); + } + } + }; + + private class EntitlementHandler extends Handler { + EntitlementHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_START_PROVISIONING: + handleStartProvisioningIfNeeded(msg.arg1, toBool(msg.arg2)); + break; + case EVENT_STOP_PROVISIONING: + handleStopProvisioningIfNeeded(msg.arg1); + break; + case EVENT_UPSTREAM_CHANGED: + handleNotifyUpstream(toBool(msg.arg1)); + break; + case EVENT_MAYBE_RUN_PROVISIONING: + handleMaybeRunProvisioning(); + break; + case EVENT_GET_ENTITLEMENT_VALUE: + handleGetLatestTetheringEntitlementValue(msg.arg1, (ResultReceiver) msg.obj, + toBool(msg.arg2)); + break; + default: + mLog.log("Unknown event: " + msg.what); + break; + } + } + } + + private static boolean toBool(int encodedBoolean) { + return encodedBoolean != 0; + } + + private static int encodeBool(boolean b) { + return b ? 1 : 0; + } + + private static boolean isValidDownstreamType(int type) { + switch (type) { + case TETHERING_BLUETOOTH: + case TETHERING_USB: + case TETHERING_WIFI: + return true; + default: + return false; + } + } + + /** + * Dump the infromation of EntitlementManager. + * @param pw {@link PrintWriter} is used to print formatted + */ + public void dump(PrintWriter pw) { + pw.print("mCellularUpstreamPermitted: "); + pw.println(mCellularUpstreamPermitted); + for (Integer type : mCurrentTethers) { + pw.print("Type: "); + pw.print(typeString(type)); + if (mCellularPermitted.indexOfKey(type) > -1) { + pw.print(", Value: "); + pw.println(errorString(mCellularPermitted.get(type))); + } else { + pw.println(", Value: empty"); + } + } + } + + private static String typeString(int type) { + switch (type) { + case TETHERING_BLUETOOTH: return "TETHERING_BLUETOOTH"; + case TETHERING_INVALID: return "TETHERING_INVALID"; + case TETHERING_USB: return "TETHERING_USB"; + case TETHERING_WIFI: return "TETHERING_WIFI"; + default: + return String.format("TETHERING UNKNOWN TYPE (%d)", type); + } + } + + private static String errorString(int value) { + switch (value) { + case TETHER_ERROR_ENTITLEMENT_UNKONWN: return "TETHER_ERROR_ENTITLEMENT_UNKONWN"; + case TETHER_ERROR_NO_ERROR: return "TETHER_ERROR_NO_ERROR"; + case TETHER_ERROR_PROVISION_FAILED: return "TETHER_ERROR_PROVISION_FAILED"; + default: + return String.format("UNKNOWN ERROR (%d)", value); + } + } + + private ResultReceiver buildProxyReceiver(int type, boolean notifyFail, + final ResultReceiver receiver) { + ResultReceiver rr = new ResultReceiver(mHandler) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { int updatedCacheValue = updateEntitlementCacheValue(type, resultCode); - receiver.send(updatedCacheValue, null); + addDownstreamMapping(type, updatedCacheValue); + if (updatedCacheValue == TETHER_ERROR_PROVISION_FAILED && notifyFail) { + mListener.onUiEntitlementFailed(type); + } + if (receiver != null) receiver.send(updatedCacheValue, null); } }; return writeToParcel(rr); } + // Instances of ResultReceiver need to be public classes for remote processes to be able + // to load them (otherwise, ClassNotFoundException). For private classes, this method + // performs a trick : round-trip parceling any instance of ResultReceiver will return a + // vanilla instance of ResultReceiver sharing the binder token with the original receiver. + // The binder token has a reference to the original instance of the private class and will + // still call its methods, and can be sent over. However it cannot be used for anything + // else than sending over a Binder call. + // While round-trip parceling is not great, there is currently no other way of generating + // a vanilla instance of ResultReceiver because all its fields are private. private ResultReceiver writeToParcel(final ResultReceiver receiver) { - // This is necessary to avoid unmarshalling issues when sending the receiver - // across processes. Parcel parcel = Parcel.obtain(); receiver.writeToParcel(parcel, 0); parcel.setDataPosition(0); @@ -286,38 +615,41 @@ public class EntitlementManager { * @param resultCode last entitlement value * @return the last updated entitlement value */ - public int updateEntitlementCacheValue(int type, int resultCode) { + private int updateEntitlementCacheValue(int type, int resultCode) { if (DBG) { Log.d(TAG, "updateEntitlementCacheValue: " + type + ", result: " + resultCode); } - synchronized (mEntitlementCacheValue) { - if (resultCode == TETHER_ERROR_NO_ERROR) { - mEntitlementCacheValue.put(type, resultCode); - return resultCode; - } else { - mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISION_FAILED); - return TETHER_ERROR_PROVISION_FAILED; - } + if (resultCode == TETHER_ERROR_NO_ERROR) { + mEntitlementCacheValue.put(type, resultCode); + return resultCode; + } else { + mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISION_FAILED); + return TETHER_ERROR_PROVISION_FAILED; } } /** Get the last value of the tethering entitlement check. */ public void getLatestTetheringEntitlementResult(int downstream, ResultReceiver receiver, boolean showEntitlementUi) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_GET_ENTITLEMENT_VALUE, + downstream, encodeBool(showEntitlementUi), receiver)); + + } + + private void handleGetLatestTetheringEntitlementValue(int downstream, ResultReceiver receiver, + boolean showEntitlementUi) { + if (!isTetherProvisioningRequired()) { receiver.send(TETHER_ERROR_NO_ERROR, null); return; } - final int cacheValue; - synchronized (mEntitlementCacheValue) { - cacheValue = mEntitlementCacheValue.get( + final int cacheValue = mEntitlementCacheValue.get( downstream, TETHER_ERROR_ENTITLEMENT_UNKONWN); - } if (cacheValue == TETHER_ERROR_NO_ERROR || !showEntitlementUi) { receiver.send(cacheValue, null); } else { - ResultReceiver proxy = buildProxyReceiver(downstream, receiver); + ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver); runUiTetherProvisioning(downstream, proxy); } } diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java index 935b79546d63..8427b6eceab9 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java @@ -30,6 +30,7 @@ import static com.android.internal.R.array.config_tether_upstream_types; import static com.android.internal.R.array.config_tether_usb_regexs; import static com.android.internal.R.array.config_tether_wifi_regexs; import static com.android.internal.R.bool.config_tether_upstream_automatic; +import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period; import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui; import android.content.ContentResolver; @@ -94,6 +95,7 @@ public class TetheringConfiguration { public final String[] provisioningApp; public final String provisioningAppNoUi; + public final int provisioningCheckPeriod; public final int subId; @@ -121,6 +123,9 @@ public class TetheringConfiguration { provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app); provisioningAppNoUi = getProvisioningAppNoUi(res); + provisioningCheckPeriod = getResourceInteger(res, + config_mobile_hotspot_provision_check_period, + 0 /* No periodic re-check */); configLog.log(toString()); } @@ -311,6 +316,14 @@ public class TetheringConfiguration { } } + private static int getResourceInteger(Resources res, int resId, int defaultValue) { + try { + return res.getInteger(resId); + } catch (Resources.NotFoundException e404) { + return defaultValue; + } + } + private static boolean getEnableLegacyDhcpServer(Context ctx) { final ContentResolver cr = ctx.getContentResolver(); final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java index 173d7860e4ac..a0aad7c50481 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java @@ -83,8 +83,8 @@ public class TetheringDependencies { * Get a reference to the EntitlementManager to be used by tethering. */ public EntitlementManager getEntitlementManager(Context ctx, StateMachine target, - SharedLog log, MockableSystemProperties systemProperties) { - return new EntitlementManager(ctx, target, log, systemProperties); + SharedLog log, int what, MockableSystemProperties systemProperties) { + return new EntitlementManager(ctx, target, log, what, systemProperties); } /** diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index 3ac311b3e13a..3a9e21f943d8 100644 --- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -16,36 +16,32 @@ package com.android.server.connectivity.tethering; -import static android.net.ConnectivityManager.getNetworkTypeName; -import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; +import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.os.Process; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.IpPrefix; -import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.NetworkState; -import android.net.util.NetworkConstants; import android.net.util.PrefixUtils; import android.net.util.SharedLog; +import android.os.Handler; +import android.os.Process; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.StateMachine; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Set; @@ -97,10 +93,13 @@ public class UpstreamNetworkMonitor { private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>(); private HashSet<IpPrefix> mLocalPrefixes; private ConnectivityManager mCM; + private EntitlementManager mEntitlementMgr; private NetworkCallback mListenAllCallback; private NetworkCallback mDefaultNetworkCallback; private NetworkCallback mMobileNetworkCallback; private boolean mDunRequired; + // Whether the current default upstream is mobile or not. + private boolean mIsDefaultCellularUpstream; // The current system default network (not really used yet). private Network mDefaultInternetNetwork; // The current upstream network used for tethering. @@ -113,6 +112,7 @@ public class UpstreamNetworkMonitor { mLog = log.forSubComponent(TAG); mWhat = what; mLocalPrefixes = new HashSet<>(); + mIsDefaultCellularUpstream = false; } @VisibleForTesting @@ -122,7 +122,15 @@ public class UpstreamNetworkMonitor { mCM = cm; } - public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest) { + /** + * Tracking the system default network. This method should be called when system is ready. + * + * @param defaultNetworkRequest should be the same as ConnectivityService default request + * @param entitle a EntitlementManager object to communicate between EntitlementManager and + * UpstreamNetworkMonitor + */ + public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest, + EntitlementManager entitle) { // This is not really a "request", just a way of tracking the system default network. // It's guaranteed not to actually bring up any networks because it's the same request // as the ConnectivityService default request, and thus shares fate with it. We can't @@ -133,6 +141,9 @@ public class UpstreamNetworkMonitor { mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET); cm().requestNetwork(trackDefaultRequest, mDefaultNetworkCallback, mHandler); } + if (mEntitlementMgr == null) { + mEntitlementMgr = entitle; + } } public void startObserveAllNetworks() { @@ -168,11 +179,15 @@ public class UpstreamNetworkMonitor { } public void registerMobileNetworkRequest() { + if (!isCellularUpstreamPermitted()) { + mLog.i("registerMobileNetworkRequest() is not permitted"); + releaseMobileNetworkRequest(); + return; + } if (mMobileNetworkCallback != null) { mLog.e("registerMobileNetworkRequest() already registered"); return; } - // The following use of the legacy type system cannot be removed until // after upstream selection no longer finds networks by legacy type. // See also http://b/34364553 . @@ -206,29 +221,32 @@ public class UpstreamNetworkMonitor { // becomes available and useful we (a) file a request to keep it up as // necessary and (b) change all upstream tracking state accordingly (by // passing LinkProperties up to Tethering). - // - // Next TODO: return NetworkState instead of just the type. public NetworkState selectPreferredUpstreamType(Iterable<Integer> preferredTypes) { final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType( - mNetworkMap.values(), preferredTypes); + mNetworkMap.values(), preferredTypes, isCellularUpstreamPermitted()); mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type)); switch (typeStatePair.type) { case TYPE_MOBILE_DUN: case TYPE_MOBILE_HIPRI: + // Tethering just selected mobile upstream in spite of the default network being + // not mobile. This can happen because of the priority list. + // Notify EntitlementManager to check permission for using mobile upstream. + if (!mIsDefaultCellularUpstream) { + mEntitlementMgr.maybeRunProvisioning(); + } // If we're on DUN, put our own grab on it. registerMobileNetworkRequest(); break; case TYPE_NONE: + // If we found NONE and mobile upstream is permitted we don't want to do this + // as we want any previous requests to keep trying to bring up something we can use. + if (!isCellularUpstreamPermitted()) releaseMobileNetworkRequest(); break; default: - /* If we've found an active upstream connection that's not DUN/HIPRI - * we should stop any outstanding DUN/HIPRI requests. - * - * If we found NONE we don't want to do this as we want any previous - * requests to keep trying to bring up something we can use. - */ + // If we've found an active upstream connection that's not DUN/HIPRI + // we should stop any outstanding DUN/HIPRI requests. releaseMobileNetworkRequest(); break; } @@ -241,10 +259,12 @@ public class UpstreamNetworkMonitor { final NetworkState dfltState = (mDefaultInternetNetwork != null) ? mNetworkMap.get(mDefaultInternetNetwork) : null; - if (!mDunRequired) return dfltState; - if (isNetworkUsableAndNotCellular(dfltState)) return dfltState; + if (!isCellularUpstreamPermitted()) return null; + + if (!mDunRequired) return dfltState; + // Find a DUN network. Note that code in Tethering causes a DUN request // to be filed, but this might be moved into this class in future. return findFirstDunNetwork(mNetworkMap.values()); @@ -258,6 +278,15 @@ public class UpstreamNetworkMonitor { return (Set<IpPrefix>) mLocalPrefixes.clone(); } + private boolean isCellularUpstreamPermitted() { + if (mEntitlementMgr != null) { + return mEntitlementMgr.isCellularUpstreamPermitted(); + } else { + // This flow should only happens in testing. + return true; + } + } + private void handleAvailable(Network network) { if (mNetworkMap.containsKey(network)) return; @@ -388,8 +417,14 @@ public class UpstreamNetworkMonitor { public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) { if (mCallbackType == CALLBACK_DEFAULT_INTERNET) { mDefaultInternetNetwork = network; + final boolean newIsCellular = isCellular(newNc); + if (mIsDefaultCellularUpstream != newIsCellular) { + mIsDefaultCellularUpstream = newIsCellular; + mEntitlementMgr.notifyUpstream(newIsCellular); + } return; } + handleNetCap(network, newNc); } @@ -424,8 +459,11 @@ public class UpstreamNetworkMonitor { public void onLost(Network network) { if (mCallbackType == CALLBACK_DEFAULT_INTERNET) { mDefaultInternetNetwork = null; + mIsDefaultCellularUpstream = false; + mEntitlementMgr.notifyUpstream(false); return; } + handleLost(network); // Any non-LISTEN_ALL callback will necessarily concern a network that will // also match the LISTEN_ALL callback by construction of the LISTEN_ALL callback. @@ -454,7 +492,8 @@ public class UpstreamNetworkMonitor { } private static TypeStatePair findFirstAvailableUpstreamByType( - Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) { + Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes, + boolean isCellularUpstreamPermitted) { final TypeStatePair result = new TypeStatePair(); for (int type : preferredTypes) { @@ -466,6 +505,10 @@ public class UpstreamNetworkMonitor { ConnectivityManager.getNetworkTypeName(type)); continue; } + if (!isCellularUpstreamPermitted && isCellular(nc)) { + continue; + } + nc.setSingleUid(Process.myUid()); for (NetworkState value : netStates) { diff --git a/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java b/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java index fa7d3fca75b5..ad04b7d10433 100644 --- a/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java +++ b/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java @@ -49,6 +49,9 @@ public abstract class ContentCaptureManagerInternal { /** * Gets the content capture options for the given user and package, or {@code null} if the * package is not whitelisted by the service. + * + * <p><b>NOTE: </b>this method is called by the {@code ActivityManager} service and hence cannot + * hold the main service lock. */ @Nullable public abstract ContentCaptureOptions getOptionsForPackage(@UserIdInt int userId, diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java index df0dc77613b1..b4c7dd310dd0 100755 --- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java @@ -335,7 +335,6 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { current.mDeviceType = params[2] & 0xFF; current.mDisplayName = HdmiUtils.getDefaultDeviceName(current.mDeviceType); - // TODO(amyjojo): check if non-TV device needs to update cec switch info. // This is to manager CEC device separately in case they don't have address. if (mIsTvDevice) { tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType, diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 2026957b6c44..9e2fd4e9e91e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -74,7 +74,15 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @GuardedBy("mLock") private boolean mSystemAudioControlFeatureEnabled; - private boolean mTvSystemAudioModeSupport; + /** + * Indicates if the TV that the current device is connected to supports System Audio Mode or not + * + * <p>If the current device has no information on this, keep mTvSystemAudioModeSupport null + * + * <p>The boolean will be reset to null every time when the current device goes to standby + * or loses its physical address. + */ + private Boolean mTvSystemAudioModeSupport = null; // Whether ARC is available or not. "true" means that ARC is established between TV and // AVR as audio receiver. @@ -314,14 +322,14 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { super.disableDevice(initiatedByCec, callback); assertRunOnServiceThread(); mService.unregisterTvInputCallback(mTvInputCallback); - // TODO(amyjojo): check disableDevice and onStandby behaviors per spec + // TODO(b/129088603): check disableDevice and onStandby behaviors per spec } @Override @ServiceThreadOnly protected void onStandby(boolean initiatedByCec, int standbyAction) { assertRunOnServiceThread(); - mTvSystemAudioModeSupport = false; + mTvSystemAudioModeSupport = null; // Record the last state of System Audio Control before going to standby synchronized (mLock) { mService.writeStringSystemProperty( @@ -465,15 +473,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @Override @ServiceThreadOnly - protected boolean handleReportAudioStatus(HdmiCecMessage message) { - assertRunOnServiceThread(); - // TODO(amyjojo): implement report audio status handler - HdmiLogger.debug(TAG + "Stub handleReportAudioStatus"); - return true; - } - - @Override - @ServiceThreadOnly protected boolean handleInitiateArc(HdmiCecMessage message) { assertRunOnServiceThread(); // TODO(amyjojo): implement initiate arc handler @@ -970,7 +969,10 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @ServiceThreadOnly void doManualPortSwitching(int portId, IHdmiControlCallback callback) { assertRunOnServiceThread(); - // TODO: validate port ID + if (!mService.isValidPortId(portId)) { + invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); + return; + } if (portId == getLocalActivePort()) { invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); return; @@ -1035,12 +1037,11 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { * <p>The result of the query may be cached until Audio device type is put in standby or loses * its physical address. */ - // TODO(amyjojo): making mTvSystemAudioModeSupport null originally and fix the logic. void queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback) { - if (!mTvSystemAudioModeSupport) { + if (mTvSystemAudioModeSupport == null) { addAndStartAction(new DetectTvSystemAudioModeSupportAction(this, callback)); } else { - callback.onResult(true); + callback.onResult(mTvSystemAudioModeSupport); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index f5adb0111559..3398d36ffdb9 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1764,7 +1764,7 @@ public class HdmiControlService extends SystemService { } @Override - // TODO(AMYJOJO): add a result callback + // TODO(b/128427908): add a result callback public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) { enforceAccessPermission(); runOnServiceThread(new Runnable() { diff --git a/services/core/java/com/android/server/incident/IncidentCompanionService.java b/services/core/java/com/android/server/incident/IncidentCompanionService.java index 55e054b6c8cd..9989c1a511ad 100644 --- a/services/core/java/com/android/server/incident/IncidentCompanionService.java +++ b/services/core/java/com/android/server/incident/IncidentCompanionService.java @@ -23,7 +23,10 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.os.Binder; +import android.os.Build; +import android.os.IBinder; import android.os.IIncidentAuthListener; import android.os.IIncidentCompanion; import android.os.IIncidentManager; @@ -49,6 +52,12 @@ public class IncidentCompanionService extends SystemService { static final String TAG = "IncidentCompanionService"; /** + * Dump argument for proxying restricted image dumps to the services + * listed in the config. + */ + private static String[] RESTRICTED_IMAGE_DUMP_ARGS = new String[] { "--restricted_image" }; + + /** * The two permissions, for sendBroadcastAsUserMultiplePermissions. */ private static final String[] DUMP_AND_USAGE_STATS_PERMISSIONS = new String[] { @@ -260,7 +269,42 @@ public class IncidentCompanionService extends SystemService { if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) { return; } - mPendingReports.dump(fd, writer, args); + + if (args.length == 1 && "--restricted_image".equals(args[0])) { + // Does NOT clearCallingIdentity + dumpRestrictedImages(fd); + } else { + // Regular dump + mPendingReports.dump(fd, writer, args); + } + } + + /** + * Proxy for the restricted images section. + */ + private void dumpRestrictedImages(FileDescriptor fd) { + // Only supported on eng or userdebug. + if (!(Build.IS_ENG || Build.IS_USERDEBUG)) { + return; + } + + final Resources res = getContext().getResources(); + final String[] services = res.getStringArray( + com.android.internal.R.array.config_restrictedImagesServices); + final int servicesCount = services.length; + for (int i = 0; i < servicesCount; i++) { + final String name = services[i]; + Log.d(TAG, "Looking up service " + name); + final IBinder service = ServiceManager.getService(name); + if (service != null) { + Log.d(TAG, "Calling dump on service: " + name); + try { + service.dump(fd, RESTRICTED_IMAGE_DUMP_ARGS); + } catch (RemoteException ex) { + Log.w(TAG, "dump --restricted_image of " + name + " threw", ex); + } + } + } } /** @@ -300,7 +344,7 @@ public class IncidentCompanionService extends SystemService { android.Manifest.permission.DUMP, null); getContext().enforceCallingOrSelfPermission( android.Manifest.permission.PACKAGE_USAGE_STATS, null); - if (pkg == null) { + if (pkg != null) { enforceCallerIsSameApp(pkg); } } diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java index ed894ee0bee8..098b0e9d4d39 100644 --- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java +++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java @@ -181,9 +181,8 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem mServiceNameResolver = serviceNameResolver; if (mServiceNameResolver != null) { - mServiceNameResolver - .setOnTemporaryServiceNameChangedCallback( - (u, s) -> updateCachedServiceLocked(u)); + mServiceNameResolver.setOnTemporaryServiceNameChangedCallback( + (u, s, t) -> onServiceNameChanged(u, s, t)); } if (disallowProperty == null) { @@ -582,6 +581,23 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem } /** + * Called when the service name changed (typically when using temporary services). + * + * <p>By default, it calls {@link #updateCachedServiceLocked(int)}; subclasses must either call + * that same method, or {@code super.onServiceNameChanged()}. + * + * @param userId user handle. + * @param serviceName the new service name. + * @param isTemporary whether the new service is temporary. + */ + protected void onServiceNameChanged(@UserIdInt int userId, @Nullable String serviceName, + boolean isTemporary) { + synchronized (mLock) { + updateCachedServiceLocked(userId); + } + } + + /** * Visits all services in the cache. */ @GuardedBy("mLock") @@ -600,6 +616,23 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem mServicesCache.clear(); } + /** + * Asserts that the given package name is owned by the UID making this call. + * + * @throws SecurityException when it's not... + */ + protected final void assertCalledByPackageOwner(@NonNull String packageName) { + Preconditions.checkNotNull(packageName); + final int uid = Binder.getCallingUid(); + final String[] packages = getContext().getPackageManager().getPackagesForUid(uid); + if (packages != null) { + for (String candidate : packages) { + if (packageName.equals(candidate)) return; // Found it + } + } + throw new SecurityException("UID " + uid + " does not own " + packageName); + } + // TODO(b/117779333): support proto protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) { boolean realDebug = debug; diff --git a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java index d20481331e56..35d59561fdeb 100644 --- a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java +++ b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java @@ -155,7 +155,8 @@ public final class FrameworkResourcesServiceNameResolver implements ServiceNameR } mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs; mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs); - notifyTemporaryServiceNameChangedLocked(userId, componentName); + notifyTemporaryServiceNameChangedLocked(userId, componentName, + /* isTemporary= */ true); } } @@ -169,7 +170,8 @@ public final class FrameworkResourcesServiceNameResolver implements ServiceNameR mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE); mTemporaryHandler = null; } - notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null); + notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null, + /* isTemporary= */ false); } } @@ -235,9 +237,9 @@ public final class FrameworkResourcesServiceNameResolver implements ServiceNameR } private void notifyTemporaryServiceNameChangedLocked(@UserIdInt int userId, - @Nullable String newTemporaryName) { + @Nullable String newTemporaryName, boolean isTemporary) { if (mOnSetCallback != null) { - mOnSetCallback.onNameResolved(userId, newTemporaryName); + mOnSetCallback.onNameResolved(userId, newTemporaryName, isTemporary); } } } diff --git a/services/core/java/com/android/server/infra/ServiceNameResolver.java b/services/core/java/com/android/server/infra/ServiceNameResolver.java index 8c348ebbfcd4..e20c45992e05 100644 --- a/services/core/java/com/android/server/infra/ServiceNameResolver.java +++ b/services/core/java/com/android/server/infra/ServiceNameResolver.java @@ -39,7 +39,8 @@ public interface ServiceNameResolver { /** * The name change callback. */ - void onNameResolved(@UserIdInt int userId, @Nullable String serviceName); + void onNameResolved(@UserIdInt int userId, @Nullable String serviceName, + boolean isTemporary); } /** diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 622c49e67967..5abc73eb255a 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1809,6 +1809,10 @@ public class InputManagerService extends IInputManager.Stub } // Native callback. + private void onPointerDownOutsideFocus(IBinder touchedToken) { + } + + // Native callback. private int getVirtualKeyQuietTimeMillis() { return mContext.getResources().getInteger( com.android.internal.R.integer.config_virtualKeyQuietTimeMillis); diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java index 5a0b991bc7de..1820acfaf293 100644 --- a/services/core/java/com/android/server/job/controllers/QuotaController.java +++ b/services/core/java/com/android/server/job/controllers/QuotaController.java @@ -769,6 +769,91 @@ public final class QuotaController extends StateController { mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs); } + /** + * Returns the amount of time, in milliseconds, until the package would have reached its + * duration quota, assuming it has a job counting towards its quota the entire time. This takes + * into account any {@link TimingSession}s that may roll out of the window as the job is + * running. + */ + @VisibleForTesting + long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName) { + final long nowElapsed = sElapsedRealtimeClock.millis(); + final int standbyBucket = JobSchedulerService.standbyBucketForPackage( + packageName, userId, nowElapsed); + if (standbyBucket == NEVER_INDEX) { + return 0; + } + List<TimingSession> sessions = mTimingSessions.get(userId, packageName); + if (sessions == null || sessions.size() == 0) { + return mAllowedTimePerPeriodMs; + } + + final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); + final long startWindowElapsed = nowElapsed - stats.windowSizeMs; + final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; + final long allowedTimeRemainingMs = mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs; + final long maxExecutionTimeRemainingMs = + mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs; + + // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can + // essentially run until they reach the maximum limit. + if (stats.windowSizeMs == mAllowedTimePerPeriodMs) { + return calculateTimeUntilQuotaConsumedLocked( + sessions, startMaxElapsed, maxExecutionTimeRemainingMs); + } + + // Need to check both max time and period time in case one is less than the other. + // For example, max time remaining could be less than bucket time remaining, but sessions + // contributing to the max time remaining could phase out enough that we'd want to use the + // bucket value. + return Math.min( + calculateTimeUntilQuotaConsumedLocked( + sessions, startMaxElapsed, maxExecutionTimeRemainingMs), + calculateTimeUntilQuotaConsumedLocked( + sessions, startWindowElapsed, allowedTimeRemainingMs)); + } + + /** + * Calculates how much time it will take, in milliseconds, until the quota is fully consumed. + * + * @param windowStartElapsed The start of the window, in the elapsed realtime timebase. + * @param deadSpaceMs How much time can be allowed to count towards the quota + */ + private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimingSession> sessions, + final long windowStartElapsed, long deadSpaceMs) { + long timeUntilQuotaConsumedMs = 0; + long start = windowStartElapsed; + for (int i = 0; i < sessions.size(); ++i) { + TimingSession session = sessions.get(i); + + if (session.endTimeElapsed < windowStartElapsed) { + // Outside of window. Ignore. + continue; + } else if (session.startTimeElapsed <= windowStartElapsed) { + // Overlapping session. Can extend time by portion of session in window. + timeUntilQuotaConsumedMs += session.endTimeElapsed - windowStartElapsed; + start = session.endTimeElapsed; + } else { + // Completely within the window. Can only consider if there's enough dead space + // to get to the start of the session. + long diff = session.startTimeElapsed - start; + if (diff > deadSpaceMs) { + break; + } + timeUntilQuotaConsumedMs += diff + + (session.endTimeElapsed - session.startTimeElapsed); + deadSpaceMs -= diff; + start = session.endTimeElapsed; + } + } + // Will be non-zero if the loop didn't look at any sessions. + timeUntilQuotaConsumedMs += deadSpaceMs; + if (timeUntilQuotaConsumedMs > mMaxExecutionTimeMs) { + Slog.wtf(TAG, "Calculated quota consumed time too high: " + timeUntilQuotaConsumedMs); + } + return timeUntilQuotaConsumedMs; + } + /** Returns the execution stats of the app in the most recent window. */ @VisibleForTesting @NonNull @@ -1483,7 +1568,7 @@ public final class QuotaController extends StateController { return; } Message msg = mHandler.obtainMessage(MSG_REACHED_QUOTA, mPkg); - final long timeRemainingMs = getRemainingExecutionTimeLocked(mPkg.userId, + final long timeRemainingMs = getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName); if (DEBUG) { Slog.i(TAG, "Job for " + mPkg + " has " + timeRemainingMs + "ms left."); @@ -1642,6 +1727,8 @@ public final class QuotaController extends StateController { // job is currently running. // Reschedule message Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg); + timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId, + pkg.packageName); if (DEBUG) { Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left."); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 75b62cb349af..af58b195a491 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -1335,7 +1335,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { case TYPE_WARNING: { title = res.getText(R.string.data_usage_warning_title); body = res.getString(R.string.data_usage_warning_body, - Formatter.formatFileSize(mContext, totalBytes)); + Formatter.formatFileSize(mContext, totalBytes, Formatter.FLAG_IEC_UNITS)); builder.setSmallIcon(R.drawable.stat_notify_error); @@ -1383,7 +1383,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } final long overBytes = totalBytes - policy.limitBytes; body = res.getString(R.string.data_usage_limit_snoozed_body, - Formatter.formatFileSize(mContext, overBytes)); + Formatter.formatFileSize(mContext, overBytes, Formatter.FLAG_IEC_UNITS)); builder.setOngoing(true); builder.setSmallIcon(R.drawable.stat_notify_error); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 20b898760389..bfab85b5211b 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -17,7 +17,11 @@ package com.android.server.notification; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; +import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY; +import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; +import static android.app.Notification.FLAG_NO_CLEAR; +import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED; @@ -84,6 +88,7 @@ import static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL; import android.Manifest; import android.Manifest.permission; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -107,6 +112,8 @@ import android.app.UriGrantsManager; import android.app.admin.DeviceAdminInfo; import android.app.admin.DevicePolicyManagerInternal; import android.app.backup.BackupManager; +import android.app.role.OnRoleHoldersChangedListener; +import android.app.role.RoleManager; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; @@ -151,6 +158,7 @@ import android.os.ShellCallback; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.UserManager; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.DeviceConfig; @@ -216,7 +224,6 @@ import com.android.server.lights.LightsManager; import com.android.server.notification.ManagedServices.ManagedServiceInfo; import com.android.server.notification.ManagedServices.UserProfiles; import com.android.server.pm.PackageManagerService; -import com.android.server.pm.UserManagerService; import com.android.server.policy.PhoneWindowManager; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; @@ -249,6 +256,7 @@ import java.util.List; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; @@ -300,6 +308,12 @@ public class NotificationManagerService extends SystemService { Adjustment.KEY_TEXT_REPLIES, Adjustment.KEY_USER_SENTIMENT}; + static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] { + RoleManager.ROLE_DIALER, + RoleManager.ROLE_SMS, + RoleManager.ROLE_EMERGENCY + }; + // When #matchesCallFilter is called from the ringer, wait at most // 3s to resolve the contacts. This timeout is required since // ContactsProvider might take a long time to start up. @@ -343,6 +357,8 @@ public class NotificationManagerService extends SystemService { private IDeviceIdleController mDeviceIdleController; private IUriGrantsManager mUgm; private UriGrantsManagerInternal mUgmInternal; + private RoleObserver mRoleObserver; + private UserManager mUm; final IBinder mForegroundToken = new Binder(); private WorkerHandler mHandler; @@ -553,18 +569,13 @@ public class NotificationManagerService extends SystemService { } } - UserManagerService getUserManagerService() { - return UserManagerService.getInstance(); - } - void readPolicyXml(InputStream stream, boolean forRestore, int userId) throws XmlPullParserException, NumberFormatException, IOException { final XmlPullParser parser = Xml.newPullParser(); parser.setInput(stream, StandardCharsets.UTF_8.name()); XmlUtils.beginDocument(parser, TAG_NOTIFICATION_POLICY); boolean migratedManagedServices = false; - boolean ineligibleForManagedServices = forRestore - && getUserManagerService().isManagedProfile(userId); + boolean ineligibleForManagedServices = forRestore && mUm.isManagedProfile(userId); int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { if (ZenModeConfig.ZEN_TAG.equals(parser.getName())) { @@ -612,7 +623,8 @@ public class NotificationManagerService extends SystemService { mAssistants.resetDefaultAssistantsIfNecessary(); } - private void loadPolicyFile() { + @VisibleForTesting + protected void loadPolicyFile() { if (DBG) Slog.d(TAG, "loadPolicyFile"); synchronized (mPolicyFile) { InputStream infile = null; @@ -836,7 +848,7 @@ public class NotificationManagerService extends SystemService { } } cancelNotification(callingUid, callingPid, pkg, tag, id, 0, - Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE, + FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE, true, userId, REASON_CANCEL, nv.rank, nv.count,null); nv.recycle(); } @@ -1530,7 +1542,8 @@ public class NotificationManagerService extends SystemService { NotificationUsageStats usageStats, AtomicFile policyFile, ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am, UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm, - IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps) { + IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, + UserManager userManager) { Resources resources = getContext().getResources(); mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(), Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE, @@ -1552,6 +1565,7 @@ public class NotificationManagerService extends SystemService { mDeviceIdleController = IDeviceIdleController.Stub.asInterface( ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); mDpm = dpm; + mUm = userManager; mHandler = new WorkerHandler(looper); mRankingThread.start(); @@ -1697,14 +1711,16 @@ public class NotificationManagerService extends SystemService { AppGlobals.getPackageManager()), new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()), null, snoozeHelper, new NotificationUsageStats(getContext()), - new AtomicFile(new File(systemDir, "notification_policy.xml"), "notification-policy"), + new AtomicFile(new File( + systemDir, "notification_policy.xml"), "notification-policy"), (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE), getGroupHelper(), ActivityManager.getService(), LocalServices.getService(UsageStatsManagerInternal.class), LocalServices.getService(DevicePolicyManagerInternal.class), UriGrantsManager.getService(), LocalServices.getService(UriGrantsManagerInternal.class), - (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE)); + (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE), + getContext().getSystemService(UserManager.class)); // register for various Intents IntentFilter filter = new IntentFilter(); @@ -1827,6 +1843,9 @@ public class NotificationManagerService extends SystemService { mAudioManagerInternal = getLocalService(AudioManagerInternal.class); mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); mZenModeHelper.onSystemReady(); + mRoleObserver = new RoleObserver(getContext().getSystemService(RoleManager.class), + getContext().getMainExecutor()); + mRoleObserver.init(); } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { // This observer will force an update when observe is called, causing us to // bind to listener services. @@ -2324,7 +2343,7 @@ public class NotificationManagerService extends SystemService { // Don't allow client applications to cancel foreground service notis or autobundled // summaries. final int mustNotHaveFlags = isCallingUidSystem() ? 0 : - (FLAG_FOREGROUND_SERVICE | Notification.FLAG_AUTOGROUP_SUMMARY); + (FLAG_FOREGROUND_SERVICE | FLAG_AUTOGROUP_SUMMARY); cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(), pkg, tag, id, 0, mustNotHaveFlags, false, userId, REASON_APP_CANCEL, null); } @@ -3128,7 +3147,7 @@ public class NotificationManagerService extends SystemService { private void cancelNotificationFromListenerLocked(ManagedServiceInfo info, int callingUid, int callingPid, String pkg, String tag, int id, int userId) { cancelNotification(callingUid, callingPid, pkg, tag, id, 0, - Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE, + FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE | FLAG_BUBBLE, true, userId, REASON_LISTENER_CANCEL, info); } @@ -4219,7 +4238,7 @@ public class NotificationManagerService extends SystemService { .setGroupSummary(true) .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN) .setGroup(GroupHelper.AUTOGROUP_KEY) - .setFlag(Notification.FLAG_AUTOGROUP_SUMMARY, true) + .setFlag(FLAG_AUTOGROUP_SUMMARY, true) .setFlag(Notification.FLAG_GROUP_SUMMARY, true) .setColor(adjustedSbn.getNotification().color) .setLocalOnly(true) @@ -4740,6 +4759,20 @@ public class NotificationManagerService extends SystemService { } } + /** + * Updates the flags for this notification to reflect whether it is a bubble or not. + */ + private void flagNotificationForBubbles(NotificationRecord r, String pkg, int userId) { + Notification notification = r.getNotification(); + boolean canBubble = mPreferencesHelper.areBubblesAllowed(pkg, userId) + && r.getChannel().canBubble(); + if (notification.getBubbleMetadata() != null && canBubble) { + notification.flags |= FLAG_BUBBLE; + } else { + notification.flags &= ~FLAG_BUBBLE; + } + } + private void doChannelWarningToast(CharSequence toastText) { Binder.withCleanCallingIdentity(() -> { final int defaultWarningEnabled = Build.IS_DEBUGGABLE ? 1 : 0; @@ -5095,6 +5128,9 @@ public class NotificationManagerService extends SystemService { final int id = n.getId(); final String tag = n.getTag(); + // We need to fix the notification up a little for bubbles + flagNotificationForBubbles(r, pkg, callingUid); + // Handle grouped notifications and bail out early if we // can to avoid extracting signals. handleGroupedNotificationLocked(r, old, callingUid, callingPid); @@ -5196,8 +5232,8 @@ public class NotificationManagerService extends SystemService { // Ensure if this is a foreground service that the proper additional // flags are set. if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) { - notification.flags |= Notification.FLAG_ONGOING_EVENT - | Notification.FLAG_NO_CLEAR; + notification.flags |= FLAG_ONGOING_EVENT + | FLAG_NO_CLEAR; } mRankingHelper.extractSignals(r); @@ -6654,8 +6690,11 @@ public class NotificationManagerService extends SystemService { null, userId, 0, 0, reason, listenerName); FlagChecker flagChecker = (int flags) -> { - if ((flags & (Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR)) - != 0) { + int flagsToCheck = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR; + if (REASON_LISTENER_CANCEL_ALL == reason) { + flagsToCheck |= FLAG_BUBBLE; + } + if ((flags & flagsToCheck) != 0) { return false; } return true; @@ -8074,6 +8113,98 @@ public class NotificationManagerService extends SystemService { } } + class RoleObserver implements OnRoleHoldersChangedListener { + // Role name : user id : list of approved packages + private ArrayMap<String, ArrayMap<Integer, ArraySet<String>>> mNonBlockableDefaultApps; + + private final RoleManager mRm; + private final Executor mExecutor; + + RoleObserver(@NonNull RoleManager roleManager, + @NonNull @CallbackExecutor Executor executor) { + mRm = roleManager; + mExecutor = executor; + } + + public void init() { + List<UserInfo> users = mUm.getUsers(); + mNonBlockableDefaultApps = new ArrayMap<>(); + for (int i = 0; i < NON_BLOCKABLE_DEFAULT_ROLES.length; i++) { + final ArrayMap<Integer, ArraySet<String>> userToApprovedList = new ArrayMap<>(); + mNonBlockableDefaultApps.put(NON_BLOCKABLE_DEFAULT_ROLES[i], userToApprovedList); + for (int j = 0; j < users.size(); j++) { + Integer userId = users.get(j).getUserHandle().getIdentifier(); + ArraySet<String> approvedForUserId = new ArraySet<>(mRm.getRoleHoldersAsUser( + NON_BLOCKABLE_DEFAULT_ROLES[i], UserHandle.of(userId))); + userToApprovedList.put(userId, approvedForUserId); + mPreferencesHelper.updateDefaultApps(userId, null, approvedForUserId); + } + } + + mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL); + } + + @VisibleForTesting + public boolean isApprovedPackageForRoleForUser(String role, String pkg, int userId) { + return mNonBlockableDefaultApps.get(role).get(userId).contains(pkg); + } + + /** + * Convert the assistant-role holder into settings. The rest of the system uses the + * settings. + * + * @param roleName the name of the role whose holders are changed + * @param user the user for this role holder change + */ + @Override + public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) { + // we only care about a couple of the roles they'll tell us about + boolean relevantChange = false; + for (int i = 0; i < NON_BLOCKABLE_DEFAULT_ROLES.length; i++) { + if (NON_BLOCKABLE_DEFAULT_ROLES[i].equals(roleName)) { + relevantChange = true; + break; + } + } + + if (!relevantChange) { + return; + } + + ArraySet<String> roleHolders = new ArraySet<>(mRm.getRoleHoldersAsUser(roleName, user)); + + // find the diff + ArrayMap<Integer, ArraySet<String>> prevApprovedForRole = + mNonBlockableDefaultApps.getOrDefault(roleName, new ArrayMap<>()); + ArraySet<String> previouslyApproved = + prevApprovedForRole.getOrDefault(user.getIdentifier(), new ArraySet<>()); + + ArraySet<String> toRemove = new ArraySet<>(); + ArraySet<String> toAdd = new ArraySet<>(); + + for (String previous : previouslyApproved) { + if (!roleHolders.contains(previous)) { + toRemove.add(previous); + } + } + for (String nowApproved : roleHolders) { + if (!previouslyApproved.contains(nowApproved)) { + toAdd.add(nowApproved); + } + } + + // store newly approved apps + prevApprovedForRole.put(user.getIdentifier(), roleHolders); + mNonBlockableDefaultApps.put(roleName, prevApprovedForRole); + + // update what apps can be blocked + mPreferencesHelper.updateDefaultApps(user.getIdentifier(), toRemove, toAdd); + + // RoleManager is the source of truth for this data so we don't need to trigger a + // write of the notification policy xml for this change + } + } + public static final class DumpFilter { public boolean filtered = false; public String pkgFilter; diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index de9312041c41..4ed24ec7971b 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -783,7 +783,8 @@ public final class NotificationRecord { // Consider Notification Assistant and system overrides to importance. If both, system wins. if (!getChannel().hasUserSetImportance() && mAssistantImportance != IMPORTANCE_UNSPECIFIED - && !getChannel().isImportanceLockedByOEM()) { + && !getChannel().isImportanceLockedByOEM() + && !getChannel().isImportanceLockedByCriticalDeviceFunction()) { mImportance = mAssistantImportance; mImportanceExplanationCode = MetricsEvent.IMPORTANCE_EXPLANATION_ASST; } @@ -1290,13 +1291,11 @@ public final class NotificationRecord { lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_INITIAL, stats.naturalImportance); } - // Log Assistant override if it was itself overridden by System. Since System can't be - // overridden, it never needs logging. - if (mImportanceExplanationCode == MetricsEvent.IMPORTANCE_EXPLANATION_SYSTEM - && mAssistantImportance != IMPORTANCE_UNSPECIFIED) { - lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_ASST, + } + // Log Assistant override if present, whether or not importance calculation is complete. + if (mAssistantImportance != IMPORTANCE_UNSPECIFIED) { + lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_ASST, mAssistantImportance); - } } return lm; } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 660309cd578d..a3e90dcff83e 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -37,6 +37,8 @@ import android.service.notification.NotificationListenerService; import android.service.notification.RankingHelperProto; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Pair; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.proto.ProtoOutputStream; @@ -100,6 +102,7 @@ public class PreferencesHelper implements RankingConfig { private static final boolean DEFAULT_SHOW_BADGE = true; private static final boolean DEFAULT_ALLOW_BUBBLE = true; private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE = false; + private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE = false; /** * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable @@ -659,6 +662,7 @@ public class PreferencesHelper implements RankingConfig { channel.setImportanceLockedByOEM(true); } } + channel.setImportanceLockedByCriticalDeviceFunction(r.defaultAppLockedImportance); if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { channel.setLockscreenVisibility( NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE); @@ -707,6 +711,10 @@ public class PreferencesHelper implements RankingConfig { if (updatedChannel.isImportanceLockedByOEM()) { updatedChannel.setImportance(channel.getImportance()); } + updatedChannel.setImportanceLockedByCriticalDeviceFunction(r.defaultAppLockedImportance); + if (updatedChannel.isImportanceLockedByCriticalDeviceFunction()) { + updatedChannel.setImportance(channel.getImportance()); + } r.channels.put(updatedChannel.getId(), updatedChannel); @@ -844,6 +852,26 @@ public class PreferencesHelper implements RankingConfig { } } + public void updateDefaultApps(int userId, ArraySet<String> toRemove, ArraySet<String> toAdd) { + synchronized (mPackagePreferences) { + for (PackagePreferences p : mPackagePreferences.values()) { + if (userId == UserHandle.getUserId(p.uid)) { + if (toRemove != null && toRemove.contains(p.pkg)) { + p.defaultAppLockedImportance = false; + for (NotificationChannel channel : p.channels.values()) { + channel.setImportanceLockedByCriticalDeviceFunction(false); + } + } else if (toAdd != null && toAdd.contains(p.pkg)) { + p.defaultAppLockedImportance = true; + for (NotificationChannel channel : p.channels.values()) { + channel.setImportanceLockedByCriticalDeviceFunction(true); + } + } + } + } + } + } + public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg, int uid, String groupId, boolean includeDeleted) { Preconditions.checkNotNull(pkg); @@ -1729,8 +1757,11 @@ public class PreferencesHelper implements RankingConfig { boolean showBadge = DEFAULT_SHOW_BADGE; boolean allowBubble = DEFAULT_ALLOW_BUBBLE; int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS; + // these fields are loaded on boot from a different source of truth and so are not + // written to notification policy xml boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE; List<String> futureOemLockedChannels = new ArrayList<>(); + boolean defaultAppLockedImportance = DEFAULT_APP_LOCKED_IMPORTANCE; Delegate delegate = null; ArrayMap<String, NotificationChannel> channels = new ArrayMap<>(); diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 37dd63a2f745..13ff873b8bb7 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -422,8 +422,6 @@ public final class OverlayManagerService extends SystemService { final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId); if (oi != null) { mImpl.onOverlayPackageUpgrading(packageName, userId); - } else { - mImpl.onTargetPackageUpgrading(packageName, userId); } } } diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 15ed06311758..a3d63806b7e7 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -22,7 +22,6 @@ import static android.content.om.OverlayInfo.STATE_ENABLED_STATIC; import static android.content.om.OverlayInfo.STATE_MISSING_TARGET; import static android.content.om.OverlayInfo.STATE_NO_IDMAP; import static android.content.om.OverlayInfo.STATE_OVERLAY_UPGRADING; -import static android.content.om.OverlayInfo.STATE_TARGET_UPGRADING; import static com.android.server.om.OverlayManagerService.DEBUG; import static com.android.server.om.OverlayManagerService.TAG; @@ -30,12 +29,15 @@ import static com.android.server.om.OverlayManagerService.TAG; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.om.OverlayInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import com.android.internal.util.ArrayUtils; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Iterator; @@ -54,8 +56,14 @@ import java.util.Set; * @see OverlayManagerService */ final class OverlayManagerServiceImpl { - // Flags to use in conjunction with updateState. + + /** + * @deprecated Not used. See {@link android.content.om.OverlayInfo#STATE_TARGET_UPGRADING}. + */ + @Deprecated private static final int FLAG_TARGET_IS_UPGRADING = 1 << 0; + + // Flags to use in conjunction with updateState. private static final int FLAG_OVERLAY_IS_UPGRADING = 1 << 1; private final PackageManagerHelper mPackageManager; @@ -247,9 +255,7 @@ final class OverlayManagerServiceImpl { Slog.d(TAG, "onTargetPackageAdded packageName=" + packageName + " userId=" + userId); } - if (updateAllOverlaysForTarget(packageName, userId, 0)) { - mListener.onOverlaysChanged(packageName, userId); - } + updateAndRefreshOverlaysForTarget(packageName, userId, 0); } void onTargetPackageChanged(@NonNull final String packageName, final int userId) { @@ -257,16 +263,7 @@ final class OverlayManagerServiceImpl { Slog.d(TAG, "onTargetPackageChanged packageName=" + packageName + " userId=" + userId); } - updateAllOverlaysForTarget(packageName, userId, 0); - } - - void onTargetPackageUpgrading(@NonNull final String packageName, final int userId) { - if (DEBUG) { - Slog.d(TAG, "onTargetPackageUpgrading packageName=" + packageName + " userId=" - + userId); - } - - updateAllOverlaysForTarget(packageName, userId, FLAG_TARGET_IS_UPGRADING); + updateAndRefreshOverlaysForTarget(packageName, userId, 0); } void onTargetPackageUpgraded(@NonNull final String packageName, final int userId) { @@ -274,7 +271,7 @@ final class OverlayManagerServiceImpl { Slog.d(TAG, "onTargetPackageUpgraded packageName=" + packageName + " userId=" + userId); } - updateAllOverlaysForTarget(packageName, userId, 0); + updateAndRefreshOverlaysForTarget(packageName, userId, 0); } void onTargetPackageRemoved(@NonNull final String packageName, final int userId) { @@ -282,22 +279,27 @@ final class OverlayManagerServiceImpl { Slog.d(TAG, "onTargetPackageRemoved packageName=" + packageName + " userId=" + userId); } - if (updateAllOverlaysForTarget(packageName, userId, 0)) { - mListener.onOverlaysChanged(packageName, userId); - } + updateAndRefreshOverlaysForTarget(packageName, userId, 0); } /** * Update the state of any overlays for this target. - * - * Returns true if the system should refresh the app's overlay paths (i.e. - * if the settings were modified for this target, or there is at least one - * enabled framework overlay). */ - private boolean updateAllOverlaysForTarget(@NonNull final String targetPackageName, + private void updateAndRefreshOverlaysForTarget(@NonNull final String targetPackageName, final int userId, final int flags) { + final List<OverlayInfo> ois = new ArrayList<>(); + + // Framework overlays added first because order matters when resolving a resource + if (!"android".equals(targetPackageName)) { + ois.addAll(mSettings.getOverlaysForTarget("android", userId)); + } + + // Then add the targeted, non-framework overlays which have higher priority + ois.addAll(mSettings.getOverlaysForTarget(targetPackageName, userId)); + + final List<String> enabledBaseCodePaths = new ArrayList<>(ois.size()); + boolean modified = false; - final List<OverlayInfo> ois = mSettings.getOverlaysForTarget(targetPackageName, userId); final int n = ois.size(); for (int i = 0; i < n; i++) { final OverlayInfo oi = ois.get(i); @@ -313,13 +315,35 @@ final class OverlayManagerServiceImpl { Slog.e(TAG, "failed to update settings", e); modified |= mSettings.remove(oi.packageName, userId); } + + if (oi.isEnabled() && overlayPackage.applicationInfo != null) { + enabledBaseCodePaths.add(overlayPackage.applicationInfo.getBaseCodePath()); + } } } - // check for enabled framework overlays - modified = modified || !getEnabledOverlayPackageNames("android", userId).isEmpty(); + if (!modified) { + PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, userId); + ApplicationInfo appInfo = packageInfo == null ? null : packageInfo.applicationInfo; + String[] resourceDirs = appInfo == null ? null : appInfo.resourceDirs; + + // If the lists aren't the same length, the enabled overlays have changed + if (ArrayUtils.size(resourceDirs) != enabledBaseCodePaths.size()) { + modified = true; + } else if (resourceDirs != null) { + // If any element isn't equal, an overlay or the order of overlays has changed + for (int index = 0; index < resourceDirs.length; index++) { + if (!resourceDirs[index].equals(enabledBaseCodePaths.get(index))) { + modified = true; + break; + } + } + } + } - return modified; + if (modified) { + mListener.onOverlaysChanged(targetPackageName, userId); + } } void onOverlayPackageAdded(@NonNull final String packageName, final int userId) { @@ -670,10 +694,6 @@ final class OverlayManagerServiceImpl { @Nullable final PackageInfo overlayPackage, final int userId, final int flags) throws OverlayManagerSettings.BadKeyException { - if ((flags & FLAG_TARGET_IS_UPGRADING) != 0) { - return STATE_TARGET_UPGRADING; - } - if ((flags & FLAG_OVERLAY_IS_UPGRADING) != 0) { return STATE_OVERLAY_UPGRADING; } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index ad17549d7448..e4cb283fe864 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -360,7 +360,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements final long age = System.currentTimeMillis() - session.createdMillis; final long timeSinceUpdate = - System.currentTimeMillis() - session.updatedMillis; + System.currentTimeMillis() - session.getUpdatedMillis(); final boolean valid; if (session.isStaged()) { if (timeSinceUpdate >= MAX_TIME_SINCE_UPDATE_MILLIS @@ -396,6 +396,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } finally { IoUtils.closeQuietly(fis); } + // After all of the sessions were loaded, they are ready to be sealed and validated + for (int i = 0; i < mSessions.size(); ++i) { + PackageInstallerSession session = mSessions.valueAt(i); + session.sealAndValidateIfNecessary(); + } } @GuardedBy("mSessions") @@ -493,10 +498,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } - if (callingUid == Process.SYSTEM_UID) { + if (Build.IS_DEBUGGABLE || isDowngradeAllowedForCaller(callingUid)) { params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE; } else { params.installFlags &= ~PackageManager.INSTALL_ALLOW_DOWNGRADE; + params.installFlags &= ~PackageManager.INSTALL_REQUEST_DOWNGRADE; } boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0; @@ -616,6 +622,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements return sessionId; } + private boolean isDowngradeAllowedForCaller(int callingUid) { + return callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID + || callingUid == Process.SHELL_UID; + } + @Override public void updateSessionAppIcon(int sessionId, Bitmap appIcon) { synchronized (mSessions) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index f1d4524cccac..e45a993b99f5 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -202,7 +202,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** Timestamp of the last time this session changed state */ @GuardedBy("mLock") - long updatedMillis; + private long updatedMillis; /** Uid of the creator of this session. */ private final int mOriginalInstallerUid; @@ -231,6 +231,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private boolean mSealed = false; @GuardedBy("mLock") + private boolean mShouldBeSealed = false; + @GuardedBy("mLock") private boolean mCommitted = false; @GuardedBy("mLock") private boolean mRelinquished = false; @@ -430,6 +432,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { this.updatedMillis = createdMillis; this.stageDir = stageDir; this.stageCid = stageCid; + this.mShouldBeSealed = sealed; if (childSessionIds != null) { for (int childSessionId : childSessionIds) { mChildSessionIds.put(childSessionId, 0); @@ -450,16 +453,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mStagedSessionErrorCode = stagedSessionErrorCode; mStagedSessionErrorMessage = stagedSessionErrorMessage != null ? stagedSessionErrorMessage : ""; - if (sealed) { - synchronized (mLock) { - try { - sealAndValidateLocked(); - } catch (PackageManagerException | IOException e) { - destroyInternal(); - throw new IllegalArgumentException(e); - } - } - } } public SessionInfo generateInfo() { @@ -932,6 +925,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @NonNull IntentSender statusReceiver, boolean forTransfer) { Preconditions.checkNotNull(statusReceiver); + List<PackageInstallerSession> childSessions = getChildSessions(); + final boolean wasSealed; synchronized (mLock) { assertCallerIsOwnerOrRootLocked(); @@ -963,7 +958,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { wasSealed = mSealed; if (!mSealed) { try { - sealAndValidateLocked(); + sealAndValidateLocked(childSessions); } catch (IOException e) { throw new IllegalArgumentException(e); } catch (PackageManagerException e) { @@ -994,21 +989,91 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return true; } + /** Return a list of child sessions or null if the session is not multipackage + * + * <p> This method is handy to prevent potential deadlocks (b/123391593) + */ + private @Nullable List<PackageInstallerSession> getChildSessions() { + List<PackageInstallerSession> childSessions = null; + if (isMultiPackage()) { + final int[] childSessionIds = getChildSessionIds(); + childSessions = new ArrayList<>(childSessionIds.length); + for (int childSessionId : childSessionIds) { + childSessions.add(mSessionProvider.getSession(childSessionId)); + } + } + return childSessions; + } + + /** + * Assert multipackage install has consistent sessions. + * + * @throws PackageManagerException if child sessions don't match parent session + * in respect to staged and enable rollback parameters. + */ + @GuardedBy("mLock") + private void assertMultiPackageConsistencyLocked( + @NonNull List<PackageInstallerSession> childSessions) throws PackageManagerException { + for (PackageInstallerSession childSession : childSessions) { + // It might be that the parent session is loaded before all of it's child sessions are, + // e.g. when reading sessions from XML. Those sessions will be null here, and their + // conformance with the multipackage params will be checked when they're loaded. + if (childSession == null) { + continue; + } + assertConsistencyWithLocked(childSession); + } + } + + /** + * Assert consistency with the given session. + * + * @throws PackageManagerException if other sessions doesn't match this session + * in respect to staged and enable rollback parameters. + */ + @GuardedBy("mLock") + private void assertConsistencyWithLocked(PackageInstallerSession other) + throws PackageManagerException { + // Session groups must be consistent wrt to isStaged parameter. Non-staging session + // cannot be grouped with staging sessions. + if (this.params.isStaged != other.params.isStaged) { + throw new PackageManagerException( + PackageManager.INSTALL_FAILED_MULTIPACKAGE_INCONSISTENCY, + "Multipackage Inconsistency: session " + other.sessionId + + " and session " + sessionId + + " have inconsistent staged settings"); + } + if (this.params.getEnableRollback() != other.params.getEnableRollback()) { + throw new PackageManagerException( + PackageManager.INSTALL_FAILED_MULTIPACKAGE_INCONSISTENCY, + "Multipackage Inconsistency: session " + other.sessionId + + " and session " + sessionId + + " have inconsistent rollback settings"); + } + } + /** * Seal the session to prevent further modification and validate the contents of it. * * <p>The session will be sealed after calling this method even if it failed. * + * @param childSessions the child sessions of a multipackage that will be checked for + * consistency. Can be null if session is not multipackage. * @throws PackageManagerException if the session was sealed but something went wrong. If the * session was sealed this is the only possible exception. */ @GuardedBy("mLock") - private void sealAndValidateLocked() throws PackageManagerException, IOException { + private void sealAndValidateLocked(List<PackageInstallerSession> childSessions) + throws PackageManagerException, IOException { assertNoWriteFileTransfersOpenLocked(); assertPreparedAndNotDestroyedLocked("sealing of session"); mSealed = true; + if (childSessions != null) { + assertMultiPackageConsistencyLocked(childSessions); + } + if (params.isStaged) { final PackageInstallerSession activeSession = mStagingManager.getActiveSession(); final boolean anotherSessionAlreadyInProgress = @@ -1048,6 +1113,38 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * If session should be sealed, then it's sealed to prevent further modification + * and then it's validated. + * + * If the session was sealed but something went wrong then it's destroyed. + * + * <p> This is meant to be called after all of the sessions are loaded and added to + * PackageInstallerService + */ + void sealAndValidateIfNecessary() { + synchronized (mLock) { + if (!mShouldBeSealed) { + return; + } + } + List<PackageInstallerSession> childSessions = getChildSessions(); + synchronized (mLock) { + try { + sealAndValidateLocked(childSessions); + } catch (IOException e) { + throw new IllegalStateException(e); + } catch (PackageManagerException e) { + Slog.e(TAG, "Package not valid", e); + // Session is sealed but could not be verified, we need to destroy it. + destroyInternal(); + // Dispatch message to remove session from PackageInstallerService + dispatchSessionFinished( + e.error, ExceptionUtils.getCompleteMessage(e), null); + } + } + } + /** Update the timestamp of when the staged session last changed state */ public void markUpdated() { synchronized (mLock) { @@ -1076,12 +1173,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new SecurityException("Can only transfer sessions that use public options"); } + List<PackageInstallerSession> childSessions = getChildSessions(); + synchronized (mLock) { assertCallerIsOwnerOrRootLocked(); assertPreparedAndNotSealedLocked("transfer"); try { - sealAndValidateLocked(); + sealAndValidateLocked(childSessions); } catch (IOException e) { throw new IllegalStateException(e); } catch (PackageManagerException e) { @@ -1132,14 +1231,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // outside of the lock, because reading the child // sessions with the lock held could lead to deadlock // (b/123391593). - List<PackageInstallerSession> childSessions = null; - if (isMultiPackage()) { - final int[] childSessionIds = getChildSessionIds(); - childSessions = new ArrayList<>(childSessionIds.length); - for (int childSessionId : childSessionIds) { - childSessions.add(mSessionProvider.getSession(childSessionId)); - } - } + List<PackageInstallerSession> childSessions = getChildSessions(); try { synchronized (mLock) { @@ -1741,6 +1833,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * @return the timestamp of when this session last changed state + */ + public long getUpdatedMillis() { + synchronized (mLock) { + return updatedMillis; + } + } + String getInstallerPackageName() { synchronized (mLock) { return mInstallerPackageName; @@ -1965,15 +2066,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { + " does not exist"), false, true).rethrowAsRuntimeException(); } - // Session groups must be consistent wrt to isStaged parameter. Non-staging session - // cannot be grouped with staging sessions. - if (this.params.isStaged ^ childSession.params.isStaged) { - throw new RemoteException("Unable to add child.", - new PackageManagerException("Child session " + childSessionId - + " and parent session " + this.sessionId + " do not have consistent" - + " staging session settings."), - false, true); - } synchronized (mLock) { assertCallerIsOwnerOrRootLocked(); assertPreparedAndNotSealedLocked("addChildSessionId"); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index eec1e539b2d7..098225f9e820 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1941,8 +1941,13 @@ public class PackageManagerService extends IPackageManager.Stub // Send broadcast package appeared if external for all users if (isExternal(res.pkg)) { if (!update) { + final StorageManager storage = + mContext.getSystemService(StorageManager.class); + VolumeInfo volume = + storage.findVolumeByUuid( + res.pkg.applicationInfo.storageUuid.toString()); int packageExternalStorageType = - getPackageExternalStorageType(res.pkg); + getPackageExternalStorageType(volume, isExternal(res.pkg)); // If the package was installed externally, log it. if (packageExternalStorageType != StorageEnums.UNKNOWN) { StatsLog.write(StatsLog.APP_INSTALL_ON_EXTERNAL_STORAGE_REPORTED, @@ -2039,15 +2044,16 @@ public class PackageManagerService extends IPackageManager.Stub /** * Gets the type of the external storage a package is installed on. - * @param pkg The package for which to get the external storage type. - * @return {@link StorageEnum#TYPE_UNKNOWN} if it is not stored externally or the corresponding - * {@link StorageEnum} storage type value if it is. + * @param packageVolume The storage volume of the package. + * @param packageIsExternal true if the package is currently installed on + * external/removable/unprotected storage. + * @return {@link StorageEnum#TYPE_UNKNOWN} if the package is not stored externally or the + * corresponding {@link StorageEnum} storage type value if it is. */ - private int getPackageExternalStorageType(PackageParser.Package pkg) { - final StorageManager storage = mContext.getSystemService(StorageManager.class); - VolumeInfo volume = storage.findVolumeByUuid(pkg.applicationInfo.storageUuid.toString()); - if (volume != null) { - DiskInfo disk = volume.getDisk(); + private static int getPackageExternalStorageType(VolumeInfo packageVolume, + boolean packageIsExternal) { + if (packageVolume != null) { + DiskInfo disk = packageVolume.getDisk(); if (disk != null) { if (disk.isSd()) { return StorageEnums.SD_CARD; @@ -2055,7 +2061,7 @@ public class PackageManagerService extends IPackageManager.Stub if (disk.isUsb()) { return StorageEnums.USB; } - if (isExternal(pkg)) { + if (packageIsExternal) { return StorageEnums.OTHER; } } @@ -2965,7 +2971,7 @@ public class PackageManagerService extends IPackageManager.Stub // Now that we know all of the shared libraries, update all clients to have // the correct library paths. - updateAllSharedLibrariesLPw(null); + updateAllSharedLibrariesLocked(null, Collections.unmodifiableMap(mPackages)); for (SharedUserSetting setting : mSettings.getAllSharedUsersLPw()) { // NOTE: We ignore potential failures here during a system scan (like @@ -3496,7 +3502,7 @@ public class PackageManagerService extends IPackageManager.Stub private @NonNull String getRequiredInstallerLPr() { final Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE); intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE); + intent.setDataAndType(Uri.parse("content://com.example/foo.apk"), PACKAGE_MIME_TYPE); final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, @@ -10016,11 +10022,11 @@ public class PackageManagerService extends IPackageManager.Stub } @GuardedBy("mPackages") - private void updateSharedLibrariesLPr(PackageParser.Package pkg, - PackageParser.Package changingLib) throws PackageManagerException { + private void updateSharedLibrariesLocked(PackageParser.Package pkg, + PackageParser.Package changingLib, Map<String, PackageParser.Package> availablePackages) + throws PackageManagerException { final ArrayList<SharedLibraryInfo> sharedLibraryInfos = - collectSharedLibraryInfos(pkg, Collections.unmodifiableMap(mPackages), - mSharedLibraries, null); + collectSharedLibraryInfos(pkg, availablePackages, mSharedLibraries, null); executeSharedLibrariesUpdateLPr(pkg, changingLib, sharedLibraryInfos); } @@ -10112,7 +10118,6 @@ public class PackageManagerService extends IPackageManager.Stub + " library " + libName + " version " + libraryInfo.getLongVersion() + "; failing!"); } - PackageParser.Package libPkg = availablePackages.get(libraryInfo.getPackageName()); if (libPkg == null) { @@ -10120,12 +10125,8 @@ public class PackageManagerService extends IPackageManager.Stub "Package " + packageName + " requires unavailable static shared" + " library; failing!"); } - final String[] expectedCertDigests = requiredCertDigests[i]; - - if (expectedCertDigests.length > 1) { - // For apps targeting O MR1 we require explicit enumeration of all certs. final String[] libCertDigests = (targetSdk >= Build.VERSION_CODES.O_MR1) ? PackageUtils.computeSignaturesSha256Digests( @@ -10157,7 +10158,6 @@ public class PackageManagerService extends IPackageManager.Stub } } } else { - // lib signing cert could have rotated beyond the one expected, check to see // if the new one has been blessed by the old if (!libPkg.mSigningDetails.hasSha256Certificate( @@ -10169,7 +10169,6 @@ public class PackageManagerService extends IPackageManager.Stub } } } - if (outUsedLibraries == null) { outUsedLibraries = new ArrayList<>(); } @@ -10180,7 +10179,7 @@ public class PackageManagerService extends IPackageManager.Stub } private static boolean hasString(List<String> list, List<String> which) { - if (list == null) { + if (list == null || which == null) { return false; } for (int i=list.size()-1; i>=0; i--) { @@ -10194,39 +10193,63 @@ public class PackageManagerService extends IPackageManager.Stub } @GuardedBy("mPackages") - private ArrayList<PackageParser.Package> updateAllSharedLibrariesLPw( - PackageParser.Package changingPkg) { - ArrayList<PackageParser.Package> res = null; - for (PackageParser.Package pkg : mPackages.values()) { - if (changingPkg != null - && !hasString(pkg.usesLibraries, changingPkg.libraryNames) - && !hasString(pkg.usesOptionalLibraries, changingPkg.libraryNames) - && !ArrayUtils.contains(pkg.usesStaticLibraries, - changingPkg.staticSharedLibName)) { - return null; - } - if (res == null) { - res = new ArrayList<>(); - } - res.add(pkg); - try { - updateSharedLibrariesLPr(pkg, changingPkg); - } catch (PackageManagerException e) { - // If a system app update or an app and a required lib missing we - // delete the package and for updated system apps keep the data as - // it is better for the user to reinstall than to be in an limbo - // state. Also libs disappearing under an app should never happen - // - just in case. - if (!pkg.isSystem() || pkg.isUpdatedSystemApp()) { - final int flags = pkg.isUpdatedSystemApp() - ? PackageManager.DELETE_KEEP_DATA : 0; - deletePackageLIF(pkg.packageName, null, true, sUserManager.getUserIds(), - flags , null, true, null); + private ArrayList<PackageParser.Package> updateAllSharedLibrariesLocked( + PackageParser.Package updatedPkg, + Map<String, PackageParser.Package> availablePackages) { + ArrayList<PackageParser.Package> resultList = null; + // Set of all descendants of a library; used to eliminate cycles + ArraySet<String> descendants = null; + // The current list of packages that need updating + ArrayList<PackageParser.Package> needsUpdating = null; + if (updatedPkg != null) { + needsUpdating = new ArrayList<>(1); + needsUpdating.add(updatedPkg); + } + do { + final PackageParser.Package changingPkg = + (needsUpdating == null) ? null : needsUpdating.remove(0); + for (int i = mPackages.size() - 1; i >= 0; --i) { + final PackageParser.Package pkg = mPackages.valueAt(i); + if (changingPkg != null + && !hasString(pkg.usesLibraries, changingPkg.libraryNames) + && !hasString(pkg.usesOptionalLibraries, changingPkg.libraryNames) + && !ArrayUtils.contains(pkg.usesStaticLibraries, + changingPkg.staticSharedLibName)) { + continue; + } + if (resultList == null) { + resultList = new ArrayList<>(); + } + resultList.add(pkg); + // if we're updating a shared library, all of its descendants must be updated + if (changingPkg != null) { + if (descendants == null) { + descendants = new ArraySet<>(); + } + if (!descendants.contains(pkg.packageName)) { + descendants.add(pkg.packageName); + needsUpdating.add(pkg); + } + } + try { + updateSharedLibrariesLocked(pkg, changingPkg, availablePackages); + } catch (PackageManagerException e) { + // If a system app update or an app and a required lib missing we + // delete the package and for updated system apps keep the data as + // it is better for the user to reinstall than to be in an limbo + // state. Also libs disappearing under an app should never happen + // - just in case. + if (!pkg.isSystem() || pkg.isUpdatedSystemApp()) { + final int flags = pkg.isUpdatedSystemApp() + ? PackageManager.DELETE_KEEP_DATA : 0; + deletePackageLIF(pkg.packageName, null, true, sUserManager.getUserIds(), + flags , null, true, null); + } + Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage()); } - Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage()); } - } - return res; + } while (needsUpdating != null && needsUpdating.size() > 0); + return resultList; } @GuardedBy({"mInstallLock", "mPackages"}) @@ -11648,19 +11671,19 @@ public class PackageManagerService extends IPackageManager.Stub for (SharedLibraryInfo info : reconciledPkg.allowedSharedLibraryInfos) { commitSharedLibraryInfoLocked(info); } + final Map<String, PackageParser.Package> combinedPackages = + reconciledPkg.getCombinedPackages(); try { // Shared libraries for the package need to be updated. - updateSharedLibrariesLPr(pkg, null); + updateSharedLibrariesLocked(pkg, null, combinedPackages); } catch (PackageManagerException e) { Slog.e(TAG, "updateSharedLibrariesLPr failed: ", e); } - } - - if (reconciledPkg.hasDynamicSharedLibraries() && (scanFlags & SCAN_BOOTING) == 0) { - // If we are not booting, we need to update any applications - // that are clients of our shared library. If we are booting, - // this will all be done once the scan is complete. - clientLibPkgs = updateAllSharedLibrariesLPw(pkg); + // Update all applications that use this library. Skip when booting + // since this will be done after all packages are scaned. + if ((scanFlags & SCAN_BOOTING) == 0) { + clientLibPkgs = updateAllSharedLibrariesLocked(pkg, combinedPackages); + } } } @@ -15749,6 +15772,7 @@ public class PackageManagerService extends IPackageManager.Stub * TODO: move most of the data contained her into a PackageSetting for commit. */ private static class ReconciledPackage { + public final ReconcileRequest request; public final PackageSetting pkgSetting; public final ScanResult scanResult; // TODO: Remove install-specific details from the reconcile result @@ -15762,14 +15786,18 @@ public class PackageManagerService extends IPackageManager.Stub public ArrayList<SharedLibraryInfo> collectedSharedLibraryInfos; public final boolean removeAppKeySetData; - private ReconciledPackage(InstallArgs installArgs, PackageSetting pkgSetting, + private ReconciledPackage(ReconcileRequest request, + InstallArgs installArgs, + PackageSetting pkgSetting, PackageInstalledInfo installResult, - PrepareResult prepareResult, ScanResult scanResult, + PrepareResult prepareResult, + ScanResult scanResult, DeletePackageAction deletePackageAction, List<SharedLibraryInfo> allowedSharedLibraryInfos, SigningDetails signingDetails, boolean sharedUserSignaturesChanged, boolean removeAppKeySetData) { + this.request = request; this.installArgs = installArgs; this.pkgSetting = pkgSetting; this.installResult = installResult; @@ -15782,9 +15810,20 @@ public class PackageManagerService extends IPackageManager.Stub this.removeAppKeySetData = removeAppKeySetData; } - public boolean hasDynamicSharedLibraries() { - return !ArrayUtils.isEmpty(allowedSharedLibraryInfos) - && allowedSharedLibraryInfos.get(0).getType() != SharedLibraryInfo.TYPE_STATIC; + /** + * Returns a combined set of packages containing the packages already installed combined + * with the package(s) currently being installed. The to-be installed packages take + * precedence and may shadow already installed packages. + */ + private Map<String, PackageParser.Package> getCombinedPackages() { + final ArrayMap<String, PackageParser.Package> combinedPackages = + new ArrayMap<>(request.allPackages.size() + request.scannedPackages.size()); + + combinedPackages.putAll(request.allPackages); + for (ScanResult scanResult : request.scannedPackages.values()) { + combinedPackages.put(scanResult.pkgSetting.name, scanResult.request.pkg); + } + return combinedPackages; } } @@ -15974,7 +16013,7 @@ public class PackageManagerService extends IPackageManager.Stub } result.put(installPackageName, - new ReconciledPackage(installArgs, scanResult.pkgSetting, + new ReconciledPackage(request, installArgs, scanResult.pkgSetting, res, request.preparedPackages.get(installPackageName), scanResult, deletePackageAction, allowedSharedLibInfos, signingDetails, sharedUserSignaturesChanged, removeAppKeySetData)); @@ -18409,7 +18448,7 @@ public class PackageManagerService extends IPackageManager.Stub try { // update shared libraries for the newly re-installed system package - updateSharedLibrariesLPr(pkg, null); + updateSharedLibrariesLocked(pkg, null, Collections.unmodifiableMap(mPackages)); } catch (PackageManagerException e) { Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage()); } @@ -20233,6 +20272,11 @@ public class PackageManagerService extends IPackageManager.Stub return mContext.getString(R.string.config_defaultTextClassifierPackage); } + @Override + public String getAttentionServicePackageName() { + return mContext.getString(R.string.config_defaultAttentionService); + } + private @Nullable String getDocumenterPackageName() { final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); @@ -20483,7 +20527,7 @@ public class PackageManagerService extends IPackageManager.Stub prepareAppDataAfterInstallLIF(pkg); synchronized (mPackages) { try { - updateSharedLibrariesLPr(pkg, null); + updateSharedLibrariesLocked(pkg, null, mPackages); } catch (PackageManagerException e) { Slog.e(TAG, "updateAllSharedLibrariesLPw failed: ", e); } @@ -22494,6 +22538,7 @@ public class PackageManagerService extends IPackageManager.Stub final int targetSdkVersion; final PackageFreezer freezer; final int[] installedUserIds; + final boolean isCurrentLocationExternal; // reader synchronized (mPackages) { @@ -22540,6 +22585,7 @@ public class PackageManagerService extends IPackageManager.Stub "Failed to move already frozen package"); } + isCurrentLocationExternal = isExternal(pkg); codeFile = new File(pkg.codePath); installerPackageName = ps.installerPackageName; packageAbiOverride = ps.cpuAbiOverrideString; @@ -22642,6 +22688,7 @@ public class PackageManagerService extends IPackageManager.Stub case PackageInstaller.STATUS_SUCCESS: mMoveCallbacks.notifyStatusChanged(moveId, PackageManager.MOVE_SUCCEEDED); + logAppMovedStorage(packageName, isCurrentLocationExternal); break; case PackageInstaller.STATUS_FAILURE_STORAGE: mMoveCallbacks.notifyStatusChanged(moveId, @@ -22700,6 +22747,36 @@ public class PackageManagerService extends IPackageManager.Stub mHandler.sendMessage(msg); } + /** + * Logs that an app has been moved from internal to external storage and vice versa. + * @param packageName The package that was moved. + */ + private void logAppMovedStorage(String packageName, boolean isPreviousLocationExternal) { + final PackageParser.Package pkg; + synchronized (mPackages) { + pkg = mPackages.get(packageName); + } + if (pkg == null) { + return; + } + + final StorageManager storage = mContext.getSystemService(StorageManager.class); + VolumeInfo volume = storage.findVolumeByUuid(pkg.applicationInfo.storageUuid.toString()); + int packageExternalStorageType = getPackageExternalStorageType(volume, isExternal(pkg)); + + if (!isPreviousLocationExternal && isExternal(pkg)) { + // Move from internal to external storage. + StatsLog.write(StatsLog.APP_MOVED_STORAGE_REPORTED, packageExternalStorageType, + StatsLog.APP_MOVED_STORAGE_REPORTED__MOVE_TYPE__TO_EXTERNAL, + packageName); + } else if (isPreviousLocationExternal && !isExternal(pkg)) { + // Move from external to internal storage. + StatsLog.write(StatsLog.APP_MOVED_STORAGE_REPORTED, packageExternalStorageType, + StatsLog.APP_MOVED_STORAGE_REPORTED__MOVE_TYPE__TO_INTERNAL, + packageName); + } + } + @Override public int movePrimaryStorage(String volumeUuid) throws RemoteException { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null); @@ -23294,6 +23371,23 @@ public class PackageManagerService extends IPackageManager.Stub } return results; } + + @Override + public int getLocationFlags(String packageName) throws RemoteException { + int callingUser = UserHandle.getUserId(Binder.getCallingUid()); + ApplicationInfo appInfo = getApplicationInfo(packageName, + /*flags*/ 0, + /*userId*/ callingUser); + if (appInfo == null) { + throw new RemoteException( + "Couldn't get ApplicationInfo for package " + packageName); + } + return ((appInfo.isSystemApp() ? IPackageManagerNative.LOCATION_SYSTEM : 0) + | (appInfo.isVendor() ? IPackageManagerNative.LOCATION_VENDOR : 0) + | (appInfo.isProduct() ? IPackageManagerNative.LOCATION_PRODUCT : 0) + | (appInfo.isProductServices() + ? IPackageManagerNative.LOCATION_PRODUCT_SERVICES : 0)); + } } private class PackageManagerInternalImpl extends PackageManagerInternal { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 096335e884ad..6a1f223917b6 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4383,6 +4383,7 @@ public final class Settings { ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION, "PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION", ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE, "PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE", ApplicationInfo.PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE, "ALLOW_AUDIO_PLAYBACK_CAPTURE", + ApplicationInfo.PRIVATE_FLAG_ALLOW_EXTERNAL_STORAGE_SANDBOX, "ALLOW_EXTERNAL_STORAGE_SANDBOX", ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND, "BACKUP_IN_FOREGROUND", ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE", ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE, "DEFAULT_TO_DEVICE_PROTECTED_STORAGE", diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 39c731c92135..190610cd337a 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -44,11 +44,13 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.storage.IStorageManager; import android.util.Slog; import android.util.SparseArray; import android.util.apk.ApkSignatureVerifier; import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.PackageHelper; import com.android.internal.os.BackgroundThread; import java.io.File; @@ -253,6 +255,21 @@ public class StagingManager { } } + // Make sure we start a filesystem checkpoint on the next boot. + try { + IStorageManager storageManager = PackageHelper.getStorageManager(); + if (storageManager.supportsCheckpoint()) { + storageManager.startCheckpoint(1 /* numRetries */); + } + } catch (RemoteException e) { + // While StorageManager lives in the same process, the native implementation + // it calls through lives in 'vold'; so, this call can fail if 'vold' isn't + // reachable. + // Since we can live without filesystem checkpointing, just warn in this case + // and continue. + Slog.w(TAG, "Could not start filesystem checkpoint."); + } + session.setStagedSessionReady(); if (sessionContainsApex(session) && !mApexManager.markStagedSessionReady(session.sessionId)) { 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 e6e2c76de829..18332001e268 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -740,6 +740,14 @@ public final class DefaultPermissionGrantPolicy { LOCATION_PERMISSIONS, CONTACTS_PERMISSIONS); } + // Atthention Service + String attentionServicePackageName = + mContext.getPackageManager().getAttentionServicePackageName(); + if (!TextUtils.isEmpty(attentionServicePackageName)) { + grantPermissionsToSystemPackage(attentionServicePackageName, userId, + CAMERA_PERMISSIONS); + } + // There is no real "marker" interface to identify the shared storage backup, it is // hardcoded in BackupManagerService.SHARED_BACKUP_AGENT_PACKAGE. grantSystemFixedPermissionsToSystemPackage("com.android.sharedstoragebackup", userId, diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 7c87462cd63f..88109d32e55a 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -206,7 +206,6 @@ import com.android.internal.policy.IShortcutService; import com.android.internal.policy.PhoneWindow; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.ScreenshotHelper; import com.android.server.ExtconStateObserver; import com.android.server.ExtconUEventObserver; import com.android.server.GestureLauncherService; @@ -377,7 +376,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { BurnInProtectionHelper mBurnInProtectionHelper; private DisplayFoldController mDisplayFoldController; AppOpsManager mAppOpsManager; - private ScreenshotHelper mScreenshotHelper; private boolean mHasFeatureWatch; private boolean mHasFeatureLeanback; private boolean mHasFeatureHdmiCec; @@ -1923,7 +1921,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowManagerFuncs.onKeyguardShowingAndNotOccludedChanged(); } }); - mScreenshotHelper = new ScreenshotHelper(mContext); } /** @@ -2226,6 +2223,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public boolean canBeHiddenByKeyguardLw(WindowState win) { + + // Keyguard visibility of window from activities are determined over activity visibility. + if (win.getAppToken() != null) { + return false; + } switch (win.getAttrs().type) { case TYPE_STATUS_BAR: case TYPE_NAVIGATION_BAR: @@ -2239,19 +2241,30 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private boolean shouldBeHiddenByKeyguard(WindowState win, WindowState imeTarget) { + final LayoutParams attrs = win.getAttrs(); - // Keyguard visibility of window from activities are determined over activity visibility. - if (win.getAppToken() != null) { - return false; + boolean hideDockDivider = attrs.type == TYPE_DOCK_DIVIDER + && !mWindowManagerInternal.isStackVisibleLw(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + if (hideDockDivider) { + return true; + } + + // If AOD is showing, the IME should be hidden. However, sometimes the AOD is considered + // hidden because it's in the process of hiding, but it's still being shown on screen. + // In that case, we want to continue hiding the IME until the windows have completed + // drawing. This way, we know that the IME can be safely shown since the other windows are + // now shown. + final boolean hideIme = win.isInputMethodWindow() + && (mAodShowing || !mDefaultDisplayPolicy.isWindowManagerDrawComplete()); + if (hideIme) { + return true; } - final LayoutParams attrs = win.getAttrs(); final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisibleLw() && (imeTarget.canShowWhenLocked() || !canBeHiddenByKeyguardLw(imeTarget)); // Show IME over the keyguard if the target allows it - boolean allowWhenLocked = (win.isInputMethodWindow() || imeTarget == this) - && showImeOverKeyguard; + boolean allowWhenLocked = win.isInputMethodWindow() && showImeOverKeyguard; final boolean isKeyguardShowing = mKeyguardDelegate.isShowing(); @@ -2262,17 +2275,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { || (attrs.privateFlags & PRIVATE_FLAG_SYSTEM_ERROR) != 0; } - boolean hideDockDivider = attrs.type == TYPE_DOCK_DIVIDER - && !mWindowManagerInternal.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - // If AOD is showing, the IME should be hidden. However, sometimes the AOD is considered - // hidden because it's in the process of hiding, but it's still being shown on screen. - // In that case, we want to continue hiding the IME until the windows have completed - // drawing. This way, we know that the IME can be safely shown since the other windows are - // now shown. - final boolean hideIme = win.isInputMethodWindow() - && (mAodShowing || !mDefaultDisplayPolicy.isWindowManagerDrawComplete()); - return (isKeyguardShowing && !allowWhenLocked && win.getDisplayId() == DEFAULT_DISPLAY) - || hideDockDivider || hideIme; + return isKeyguardShowing && !allowWhenLocked && win.getDisplayId() == DEFAULT_DISPLAY; } /** {@inheritDoc} */ diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java index f23b68e28c34..38bdc62c5761 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java @@ -594,7 +594,7 @@ public class BatterySaverPolicy extends ContentObserver { boolean forceBackgroundCheck, int locationMode) { - this.adjustBrightnessFactor = adjustBrightnessFactor; + this.adjustBrightnessFactor = Math.min(1, Math.max(0, adjustBrightnessFactor)); this.advertiseIsEnabled = advertiseIsEnabled; this.deferFullBackup = deferFullBackup; this.deferKeyValueBackup = deferKeyValueBackup; @@ -613,7 +613,14 @@ public class BatterySaverPolicy extends ContentObserver { this.filesForNoninteractive = filesForNoninteractive; this.forceAllAppsStandby = forceAllAppsStandby; this.forceBackgroundCheck = forceBackgroundCheck; - this.locationMode = locationMode; + + if (locationMode < PowerManager.MIN_LOCATION_MODE + || PowerManager.MAX_LOCATION_MODE < locationMode) { + Slog.e(TAG, "Invalid location mode: " + locationMode); + this.locationMode = PowerManager.LOCATION_MODE_NO_CHANGE; + } else { + this.locationMode = locationMode; + } mHashCode = Objects.hash( adjustBrightnessFactor, diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 9348806c0e59..d8f07feb1ddb 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -166,8 +166,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve * This may cause {@code packages} to be rolled back if they crash too freqeuntly. */ public void startObservingHealth(List<String> packages, long durationMs) { - PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs, - false /* withExplicitHealthCheck */); + PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs); } /** Verifies the rollback state after a reboot. */ diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index a5d291f94751..5f00148335a7 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -425,9 +425,12 @@ public final class TextClassificationManagerService extends ITextClassifierServi if (packageName == null) return; try { - final int uid = context.getPackageManager() + final int packageUid = context.getPackageManager() .getPackageUidAsUser(packageName, UserHandle.getCallingUserId()); - Preconditions.checkArgument(Binder.getCallingUid() == uid); + final int callingUid = Binder.getCallingUid(); + Preconditions.checkArgument(callingUid == packageUid + // Trust the system process: + || callingUid == android.os.Process.SYSTEM_UID); } catch (Exception e) { throw new RemoteException( String.format("Invalid package: name=%s, error=%s", packageName, e)); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 5502bb98564f..d52ba169768f 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1719,6 +1719,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final WallpaperData systemWallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM); switchWallpaper(systemWallpaper, null); + notifyCallbacksLocked(systemWallpaper); } // Make sure that the SELinux labeling of all the relevant files is correct. @@ -2669,6 +2670,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } wallpaper.connection.mReply = null; } + try { + // It can be null if user switching happens before service connection. + if (wallpaper.connection.mService != null) { + wallpaper.connection.mService.detach(); + } + } catch (RemoteException e) { + Slog.w(TAG, "Failed detaching wallpaper service ", e); + } mContext.unbindService(wallpaper.connection); wallpaper.connection.forEachDisplayConnector( WallpaperConnection.DisplayConnector::disconnectLocked); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 5bbabfceda47..91ec4a083ed9 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1102,7 +1102,7 @@ final class ActivityRecord extends ConfigurationContainer { ActivityTaskManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L, fullscreen, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, appInfo.targetSdkVersion, - info.screenOrientation, mRotationAnimationHint, info.configChanges, + info.screenOrientation, mRotationAnimationHint, mLaunchTaskBehind, isAlwaysFocusable()); if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) { Slog.v(TAG, "addAppToken: " @@ -1151,11 +1151,11 @@ final class ActivityRecord extends ConfigurationContainer { AppWindowToken createAppWindow(WindowManagerService service, IApplicationToken token, boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation, - int rotationAnimationHint, int configChanges, boolean launchTaskBehind, + int rotationAnimationHint, boolean launchTaskBehind, boolean alwaysFocusable) { return new AppWindowToken(service, token, mActivityComponent, voiceInteraction, dc, inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation, - rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable, + rotationAnimationHint, launchTaskBehind, alwaysFocusable, this); } @@ -2730,30 +2730,40 @@ final class ActivityRecord extends ConfigurationContainer { final int appHeight = resolvedAppBounds.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; } - if (info.hasFixedAspectRatio()) { + // 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); - // Check if the parent still has available space in long side. - if (aspectRatio < parentAspectRatio - && (aspectRatio < info.maxAspectRatio || info.minAspectRatio > 0)) { - return true; + if (parentAspectRatio <= info.minAspectRatio) { + // The long side has reached the parent. + return false; } } - - final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); - // If the width or height is the same as parent, it is already the best fit of the override - // bounds, therefore this condition is considered as not size compatibility mode. Here uses - // right and bottom as width and height of parent because the bounds may contain decor - // insets which has been accounted in override bounds. See {@link #computeBounds}. - return parentAppBounds.right != resolvedBounds.width() - && parentAppBounds.bottom != resolvedBounds.height(); + return true; } /** diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 46c017ec7f4f..6bc9fc8a9f7c 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -4115,9 +4115,16 @@ class ActivityStack extends ConfigurationContainer { if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to FINISHING: " + r); r.setState(FINISHING, "finishCurrentActivityLocked"); + + // Don't destroy activity immediately if the display contains home stack, although there is + // no next activity at the moment but another home activity should be started later. Keep + // this activity alive until next home activity is resumed then user won't see a temporary + // black screen. + final boolean noRunningStack = next == null && display.topRunningActivity() == null + && display.getHomeStack() == null; + final boolean noFocusedStack = r.getActivityStack() != display.getFocusedStack(); final boolean finishingInNonFocusedStackOrNoRunning = mode == FINISH_AFTER_VISIBLE - && prevState == PAUSED && (r.getActivityStack() != display.getFocusedStack() - || (next == null && display.topRunningActivity() == null)); + && prevState == PAUSED && (noFocusedStack || noRunningStack); if (mode == FINISH_IMMEDIATELY || (prevState == PAUSED diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index ea1db406b8df..7c12c1ebc2be 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -114,6 +114,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.service.voice.IVoiceInteractionSession; import android.text.TextUtils; +import android.util.ArraySet; import android.util.EventLog; import android.util.Pools.SynchronizedPool; import android.util.Slog; @@ -607,6 +608,7 @@ class ActivityStarter { boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity, TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup, PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { + mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent); int err = ActivityManager.START_SUCCESS; // Pull the optional Ephemeral Installer-only bundle out of the options early. final Bundle verificationBundle @@ -927,8 +929,10 @@ class ActivityStarter { mService.onStartActivitySetDidAppSwitch(); mController.doPendingActivityLaunches(false); - return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, + final int res = startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true /* doResume */, checkedOptions, inTask, outActivity); + mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(res, outActivity[0]); + return res; } private boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid, @@ -1001,6 +1005,10 @@ class ActivityStarter { if (callerApp.hasActivityInVisibleTask()) { return false; } + // don't abort if the caller is bound by a UID that's currently foreground + if (isBoundByForegroundUid(callerApp)) { + return false; + } } // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission if (mService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid) @@ -1015,6 +1023,11 @@ class ActivityStarter { if (mService.isDeviceOwner(callingPackage)) { return false; } + // don't abort if the callingPackage has companion device + final int callingUserId = UserHandle.getUserId(callingUid); + if (mService.isAssociatedCompanionApp(callingUserId, callingPackage)) { + return false; + } // don't abort if the callingPackage is temporarily whitelisted if (mService.isPackageNameWhitelistedForBgActivityStarts(callingPackage)) { Slog.w(TAG, "Background activity start for " + callingPackage @@ -1045,6 +1058,18 @@ class ActivityStarter { return true; } + private boolean isBoundByForegroundUid(WindowProcessController callerApp) { + final ArraySet<Integer> boundClientUids = callerApp.getBoundClientUids(); + for (int i = boundClientUids.size() - 1; i >= 0; --i) { + final int uid = boundClientUids.valueAt(i); + if (mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid) + || mService.getUidState(uid) == ActivityManager.PROCESS_STATE_TOP) { + return true; + } + } + return false; + } + /** * Creates a launch intent for the given auxiliary resolution data. */ diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index fc7646f8ae4e..b2e5b6ab7a1a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -510,4 +510,7 @@ public abstract class ActivityTaskManagerInternal { * Called by DevicePolicyManagerService to set the package name of the device owner. */ public abstract void setDeviceOwnerPackageName(String deviceOwnerPkg); + + /** Set all associated companion app that belongs to an userId. */ + public abstract void setCompanionAppPackages(int userId, Set<String> companionAppPackages); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 7ea7cf1cd015..9a8824f1b717 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -191,6 +191,7 @@ import android.os.Message; import android.os.PersistableBundle; import android.os.PowerManager; import android.os.PowerManagerInternal; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; @@ -243,6 +244,7 @@ import com.android.internal.util.FastPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.AttributeCache; +import com.android.server.DeviceIdleController; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; @@ -425,6 +427,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private static final long START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT = START_AS_CALLER_TOKEN_TIMEOUT_IMPL + 20 * MINUTE_IN_MILLIS; + // How long to whitelist the Services for when requested. + private static final int SERVICE_LAUNCH_IDLE_WHITELIST_DURATION_MS = 5 * 1000; + // Activity tokens of system activities that are delegating their call to // #startActivityByCaller, keyed by the permissionToken granted to the delegate. final HashMap<IBinder, IBinder> mStartActivitySources = new HashMap<>(); @@ -438,6 +443,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // VoiceInteractionManagerService ComponentName mActiveVoiceInteractionServiceComponent; + // A map userId and all its companion app packages + private final Map<Integer, Set<String>> mCompanionAppPackageMap = new ArrayMap<>(); + VrController mVrController; KeyguardController mKeyguardController; private final ClientLifecycleManager mLifecycleManager; @@ -2967,7 +2975,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (TextUtils.equals(pae.intent.getAction(), android.service.voice.VoiceInteractionService.SERVICE_INTERFACE)) { pae.intent.putExtras(pae.extras); - mContext.startServiceAsUser(pae.intent, new UserHandle(pae.userHandle)); + + startVoiceInteractionServiceAsUser(pae.intent, pae.userHandle, "AssistContext"); } else { pae.intent.replaceExtras(pae.extras); pae.intent.setFlags(FLAG_ACTIVITY_NEW_TASK @@ -2986,6 +2995,34 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + /** + * Workaround for historical API which starts the Assist service with a non-foreground + * {@code startService()} call. + */ + private void startVoiceInteractionServiceAsUser( + Intent intent, int userHandle, String reason) { + // Resolve the intent to find out which package we need to whitelist. + ResolveInfo resolveInfo = + mContext.getPackageManager().resolveServiceAsUser(intent, 0, userHandle); + if (resolveInfo == null || resolveInfo.serviceInfo == null) { + Slog.e(TAG, "VoiceInteractionService intent does not resolve. Not starting."); + return; + } + intent.setPackage(resolveInfo.serviceInfo.packageName); + + // Whitelist background services temporarily. + LocalServices.getService(DeviceIdleController.LocalService.class) + .addPowerSaveTempWhitelistApp(Process.myUid(), intent.getPackage(), + SERVICE_LAUNCH_IDLE_WHITELIST_DURATION_MS, userHandle, false, reason); + + // Finally, try to start the service. + try { + mContext.startServiceAsUser(intent, UserHandle.of(userHandle)); + } catch (RuntimeException e) { + Slog.e(TAG, "VoiceInteractionService failed to start.", e); + } + } + @Override public int addAppTask(IBinder activityToken, Intent intent, ActivityManager.TaskDescription description, Bitmap thumbnail) throws RemoteException { @@ -5875,6 +5912,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + boolean isAssociatedCompanionApp(int userId, String packageName) { + final Set<String> allPackages = mCompanionAppPackageMap.get(userId); + if (allPackages == null) { + return false; + } + return allPackages.contains(packageName); + } + final class H extends Handler { static final int REPORT_TIME_TRACKER_MSG = 1; @@ -7248,5 +7293,17 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { ActivityTaskManagerService.this.setDeviceOwnerPackageName(deviceOwnerPkg); } } + + @Override + public void setCompanionAppPackages(int userId, Set<String> companionAppPackages) { + // Deep copy all content to make sure we do not rely on the source + final Set<String> result = new HashSet<>(); + for (String pkg : companionAppPackages) { + result.add(pkg); + } + synchronized (mGlobalLock) { + mCompanionAppPackageMap.put(userId, result); + } + } } } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 1e1c482bdce3..a53f85daaa25 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -153,7 +153,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree /** @see WindowContainer#fillsParent() */ private boolean mFillsParent; - boolean layoutConfigChanges; boolean mShowForAllUsers; int mTargetSdk; @@ -337,7 +336,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree AppWindowToken(WindowManagerService service, IApplicationToken token, ComponentName activityComponent, boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen, boolean showForAllUsers, - int targetSdk, int orientation, int rotationAnimationHint, int configChanges, + int targetSdk, int orientation, int rotationAnimationHint, boolean launchTaskBehind, boolean alwaysFocusable, ActivityRecord activityRecord) { this(service, token, activityComponent, voiceInteraction, dc, fullscreen); @@ -348,7 +347,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree mShowForAllUsers = showForAllUsers; mTargetSdk = targetSdk; mOrientation = orientation; - layoutConfigChanges = (configChanges & (CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION)) != 0; mLaunchTaskBehind = launchTaskBehind; mAlwaysFocusable = alwaysFocusable; mRotationAnimationHint = rotationAnimationHint; @@ -666,7 +664,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } } - if (isSelfAnimating()) { + if (isReallyAnimating()) { delayed = true; } else { @@ -1976,7 +1974,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree final boolean surfaceReady = w.isDrawnLw() // Regular case || w.mWinAnimator.mSurfaceDestroyDeferred // The preserved surface is still ready. || w.isDragResizeChanged(); // Waiting for relayoutWindow to call preserveSurface. - final boolean needsLetterbox = w.isLetterboxedAppWindow() && fillsParent() && surfaceReady; + final boolean needsLetterbox = surfaceReady && w.isLetterboxedAppWindow() && fillsParent(); if (needsLetterbox) { if (mLetterbox == null) { mLetterbox = new Letterbox(() -> makeChildSurface(null)); @@ -3132,8 +3130,17 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } } + /** Gets the inner bounds of letterbox. The bounds will be empty if there is no letterbox. */ + void getLetterboxInnerBounds(Rect outBounds) { + if (mLetterbox != null) { + outBounds.set(mLetterbox.getInnerFrame()); + } else { + outBounds.setEmpty(); + } + } + /** - * @eturn true if there is a letterbox and any part of that letterbox overlaps with + * @return {@code true} if there is a letterbox and any part of that letterbox overlaps with * the given {@code rect}. */ boolean isLetterboxOverlappingWith(Rect rect) { diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java index 9bc8462c66e3..90bb494232c7 100644 --- a/services/core/java/com/android/server/wm/BarController.java +++ b/services/core/java/com/android/server/wm/BarController.java @@ -51,6 +51,7 @@ public class BarController { private static final int MSG_NAV_BAR_VISIBILITY_CHANGED = 1; protected final String mTag; + protected final int mDisplayId; private final int mTransientFlag; private final int mUnhideFlag; private final int mTranslucentFlag; @@ -74,9 +75,10 @@ public class BarController { private OnBarVisibilityChangedListener mVisibilityChangeListener; - BarController(String tag, int transientFlag, int unhideFlag, int translucentFlag, + BarController(String tag, int displayId, int transientFlag, int unhideFlag, int translucentFlag, int statusBarManagerId, int translucentWmFlag, int transparentFlag) { mTag = "BarController." + tag; + mDisplayId = displayId; mTransientFlag = transientFlag; mUnhideFlag = unhideFlag; mTranslucentFlag = translucentFlag; @@ -173,8 +175,9 @@ public class BarController { } final boolean wasVis = mWin.isVisibleLw(); final boolean wasAnim = mWin.isAnimatingLw(); - final boolean change = show ? mWin.showLw(!mNoAnimationOnNextShow && !skipAnimation()) - : mWin.hideLw(!mNoAnimationOnNextShow && !skipAnimation()); + final boolean skipAnim = skipAnimation(); + final boolean change = show ? mWin.showLw(!mNoAnimationOnNextShow && !skipAnim) + : mWin.hideLw(!mNoAnimationOnNextShow && !skipAnim); mNoAnimationOnNextShow = false; final int state = computeStateLw(wasVis, wasAnim, mWin, change); final boolean stateChanged = updateStateLw(state); @@ -229,7 +232,7 @@ public class BarController { public void run() { StatusBarManagerInternal statusbar = getStatusBarInternal(); if (statusbar != null) { - statusbar.setWindowState(mWin.getDisplayId(), mStatusBarManagerId, state); + statusbar.setWindowState(mDisplayId, mStatusBarManagerId, state); } } }); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index bec72f5686df..77055c1095d1 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -28,6 +28,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; 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.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_PRIVATE; import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; @@ -655,7 +656,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (DEBUG_LAYOUT && !w.mLayoutAttached) { Slog.v(TAG, "1ST PASS " + w + ": gone=" + gone + " mHaveFrame=" + w.mHaveFrame + " mLayoutAttached=" + w.mLayoutAttached - + " screen changed=" + w.isConfigChanged()); + + " config reported=" + w.isLastConfigReportedToClient()); final AppWindowToken atoken = w.mAppToken; if (gone) Slog.v(TAG, " GONE: mViewVisibility=" + w.mViewVisibility + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden() @@ -670,42 +671,34 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // If this view is GONE, then skip it -- keep the current frame, and let the caller know // so they can ignore it if they want. (We do the normal layout for INVISIBLE windows, // since that means "perform layout as normal, just don't display"). - if (!gone || !w.mHaveFrame || w.mLayoutNeeded - || ((w.isConfigChanged() || w.setReportResizeHints()) - && !w.isGoneForLayoutLw() && - ((w.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 || - (w.mHasSurface && w.mAppToken != null && - w.mAppToken.layoutConfigChanges)))) { - if (!w.mLayoutAttached) { - if (mTmpInitial) { - //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial"); - w.resetContentChanged(); - } - if (w.mAttrs.type == TYPE_DREAM) { - // Don't layout windows behind a dream, so that if it does stuff like hide - // the status bar we won't get a bad transition when it goes away. - mTmpWindow = w; - } - w.mLayoutNeeded = false; - w.prelayout(); - final boolean firstLayout = !w.isLaidOut(); - getDisplayPolicy().layoutWindowLw(w, null, mDisplayFrames); - w.mLayoutSeq = mLayoutSeq; - - // If this is the first layout, we need to initialize the last inset values as - // otherwise we'd immediately cause an unnecessary resize. - if (firstLayout) { - w.updateLastInsetValues(); - } + if ((!gone || !w.mHaveFrame || w.mLayoutNeeded) && !w.mLayoutAttached) { + if (mTmpInitial) { + w.resetContentChanged(); + } + if (w.mAttrs.type == TYPE_DREAM) { + // Don't layout windows behind a dream, so that if it does stuff like hide + // the status bar we won't get a bad transition when it goes away. + mTmpWindow = w; + } + w.mLayoutNeeded = false; + w.prelayout(); + final boolean firstLayout = !w.isLaidOut(); + getDisplayPolicy().layoutWindowLw(w, null, mDisplayFrames); + w.mLayoutSeq = mLayoutSeq; - if (w.mAppToken != null) { - w.mAppToken.layoutLetterbox(w); - } + // If this is the first layout, we need to initialize the last inset values as + // otherwise we'd immediately cause an unnecessary resize. + if (firstLayout) { + w.updateLastInsetValues(); + } - if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrameLw() - + " mContainingFrame=" + w.getContainingFrame() - + " mDisplayFrame=" + w.getDisplayFrameLw()); + if (w.mAppToken != null) { + w.mAppToken.layoutLetterbox(w); } + + if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrameLw() + + " mContainingFrame=" + w.getContainingFrame() + + " mDisplayFrame=" + w.getDisplayFrameLw()); } }; @@ -1525,7 +1518,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / mBaseDisplayDensity; final int longSizeDp = longSize * DisplayMetrics.DENSITY_DEFAULT / mBaseDisplayDensity; - mDisplayPolicy.configure(width, height, shortSizeDp); + mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors(); mDisplayRotation.configure(width, height, shortSizeDp, longSizeDp); mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo, @@ -1734,7 +1727,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mWmService.mH.sendEmptyMessage(REPORT_HARD_KEYBOARD_STATUS_CHANGE); } - mDisplayPolicy.updateConfigurationDependentBehaviors(); + mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors(); // Let the policy update hidden states. config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; @@ -3094,7 +3087,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** Updates the layer assignment of windows on this display. */ void assignWindowLayers(boolean setLayoutNeeded) { - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "assignWindowLayers"); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "assignWindowLayers"); assignChildLayers(getPendingTransaction()); if (setLayoutNeeded) { setLayoutNeeded(); @@ -3105,7 +3098,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // prepareSurfaces. This allows us to synchronize Z-ordering changes with // the hiding and showing of surfaces. scheduleAnimation(); - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } // TODO: This should probably be called any time a visual change is made to the hierarchy like @@ -3659,9 +3652,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // FIRST AND ONE HALF LOOP: Make WindowManagerPolicy think it is animating. pendingLayoutChanges = 0; - mDisplayPolicy.beginPostLayoutPolicyLw(); - forAllWindows(mApplyPostLayoutPolicy, true /* traverseTopToBottom */); - pendingLayoutChanges |= mDisplayPolicy.finishPostLayoutPolicyLw(); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyPostLayoutPolicy"); + try { + mDisplayPolicy.beginPostLayoutPolicyLw(); + forAllWindows(mApplyPostLayoutPolicy, true /* traverseTopToBottom */); + pendingLayoutChanges |= mDisplayPolicy.finishPostLayoutPolicyLw(); + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats( "after finishPostLayoutPolicyLw", pendingLayoutChanges); mInsetsStateController.onPostLayout(); @@ -3670,7 +3668,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mTmpApplySurfaceChangesTransactionState.reset(); mTmpRecoveringMemory = recoveringMemory; - forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */); + + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyWindowSurfaceChanges"); + try { + forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */); + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } prepareSurfaces(); mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent; @@ -3720,11 +3724,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo out.set(left, top, left + width, top + height); } - @Override - public void getBounds(Rect out) { - calculateBounds(mDisplayInfo, out); - } - private void getBounds(Rect out, int orientation) { getBounds(out); @@ -3746,6 +3745,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } void performLayout(boolean initial, boolean updateInputWindows) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performLayout"); + try { + performLayoutNoTrace(initial, updateInputWindows); + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } + } + + private void performLayoutNoTrace(boolean initial, boolean updateInputWindows) { if (!isLayoutNeeded()) { return; } @@ -3755,13 +3763,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final int dh = mDisplayInfo.logicalHeight; if (DEBUG_LAYOUT) { Slog.v(TAG, "-------------------------------------"); - Slog.v(TAG, "performLayout: needed=" + isLayoutNeeded() + " dw=" + dw + " dh=" + dh); + Slog.v(TAG, "performLayout: needed=" + isLayoutNeeded() + " dw=" + dw + + " dh=" + dh); } mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation)); - // TODO: Not sure if we really need to set the rotation here since we are updating from the - // display info above... + // TODO: Not sure if we really need to set the rotation here since we are updating from + // the display info above... mDisplayFrames.mRotation = mRotation; mDisplayPolicy.beginLayoutLw(mDisplayFrames, getConfiguration().uiMode); @@ -4801,20 +4810,25 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @Override void prepareSurfaces() { - final ScreenRotationAnimation screenRotationAnimation = - mWmService.mAnimator.getScreenRotationAnimationLocked(mDisplayId); - if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) { - screenRotationAnimation.getEnterTransformation().getMatrix().getValues(mTmpFloats); - mPendingTransaction.setMatrix(mWindowingLayer, - mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], - mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); - mPendingTransaction.setPosition(mWindowingLayer, - mTmpFloats[Matrix.MTRANS_X], mTmpFloats[Matrix.MTRANS_Y]); - mPendingTransaction.setAlpha(mWindowingLayer, - screenRotationAnimation.getEnterTransformation().getAlpha()); - } - - super.prepareSurfaces(); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "prepareSurfaces"); + try { + final ScreenRotationAnimation screenRotationAnimation = + mWmService.mAnimator.getScreenRotationAnimationLocked(mDisplayId); + if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) { + screenRotationAnimation.getEnterTransformation().getMatrix().getValues(mTmpFloats); + mPendingTransaction.setMatrix(mWindowingLayer, + mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], + mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); + mPendingTransaction.setPosition(mWindowingLayer, + mTmpFloats[Matrix.MTRANS_X], mTmpFloats[Matrix.MTRANS_Y]); + mPendingTransaction.setAlpha(mWindowingLayer, + screenRotationAnimation.getEnterTransformation().getAlpha()); + } + + super.prepareSurfaces(); + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } } void assignStackOrdering() { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 6605f3c605ed..bbb857f4c24c 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -26,6 +26,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.res.Configuration.UI_MODE_TYPE_CAR; import static android.content.res.Configuration.UI_MODE_TYPE_MASK; import static android.view.InsetsState.TYPE_TOP_BAR; +import static android.view.InsetsState.TYPE_TOP_GESTURES; +import static android.view.InsetsState.TYPE_TOP_TAPPABLE_ELEMENT; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION; @@ -40,6 +42,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; @@ -103,13 +106,16 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.localLOGV; +import android.Manifest.permission; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.Px; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.StatusBarManager; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.PixelFormat; @@ -149,6 +155,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ScreenShapeHelper; import com.android.internal.util.ScreenshotHelper; +import com.android.internal.util.function.TriConsumer; import com.android.internal.widget.PointerLocationView; import com.android.server.LocalServices; import com.android.server.UiThread; @@ -212,6 +219,12 @@ public class DisplayPolicy { private final Object mServiceAcquireLock = new Object(); private StatusBarManagerInternal mStatusBarManagerInternal; + @Px + private int mBottomGestureAdditionalInset; + @Px + private int mSideGestureInset; + private boolean mNavigationBarLetsThroughTaps; + private StatusBarManagerInternal getStatusBarManagerInternal() { synchronized (mServiceAcquireLock) { if (mStatusBarManagerInternal == null) { @@ -255,15 +268,12 @@ public class DisplayPolicy { private int[] mNavigationBarHeightForRotationInCarMode = new int[4]; private int[] mNavigationBarWidthForRotationInCarMode = new int[4]; - private final StatusBarController mStatusBarController = new StatusBarController(); + /** Cached value of {@link ScreenShapeHelper#getWindowOutsetBottomPx} */ + @Px private int mWindowOutsetBottom; + + private final StatusBarController mStatusBarController; - private final BarController mNavigationBarController = new BarController("NavigationBar", - View.NAVIGATION_BAR_TRANSIENT, - View.NAVIGATION_BAR_UNHIDE, - View.NAVIGATION_BAR_TRANSLUCENT, - StatusBarManager.WINDOW_NAVIGATION_BAR, - FLAG_TRANSLUCENT_NAVIGATION, - View.NAVIGATION_BAR_TRANSPARENT); + private final BarController mNavigationBarController; private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener = new BarController.OnBarVisibilityChangedListener() { @@ -410,12 +420,22 @@ public class DisplayPolicy { mDisplayContent = displayContent; mLock = service.getWindowManagerLock(); + final int displayId = displayContent.getDisplayId(); + mStatusBarController = new StatusBarController(displayId); + mNavigationBarController = new BarController("NavigationBar", + displayId, + View.NAVIGATION_BAR_TRANSIENT, + View.NAVIGATION_BAR_UNHIDE, + View.NAVIGATION_BAR_TRANSLUCENT, + StatusBarManager.WINDOW_NAVIGATION_BAR, + FLAG_TRANSLUCENT_NAVIGATION, + View.NAVIGATION_BAR_TRANSPARENT); + final Resources r = mContext.getResources(); mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer); mDeskDockEnablesAccelerometer = r.getBoolean(R.bool.config_deskDockEnablesAccelerometer); mTranslucentDecorEnabled = r.getBoolean(R.bool.config_enableTranslucentDecor); mForceShowSystemBarsFromExternal = r.getBoolean(R.bool.config_forceShowSystemBars); - updateConfigurationDependentBehaviors(); mAccessibilityManager = (AccessibilityManager) mContext.getSystemService( Context.ACCESSIBILITY_SERVICE); @@ -522,7 +542,6 @@ public class DisplayPolicy { if (mWindowSleepToken != null) { return; } - final int displayId = displayContent.getDisplayId(); mWindowSleepToken = service.mAtmInternal.acquireSleepToken( "WindowSleepTokenOnDisplay" + displayId, displayId); }; @@ -567,15 +586,6 @@ public class DisplayPolicy { return mDisplayContent.getDisplayId(); } - void configure(int width, int height, int shortSizeDp) { - // Allow the navigation bar to move on non-square small devices (phones). - mNavigationBarCanMove = width != height && shortSizeDp < 600; - } - - void updateConfigurationDependentBehaviors() { - mNavBarOpacityMode = mContext.getResources().getInteger(R.integer.config_navBarOpacityMode); - } - public void setHdmiPlugged(boolean plugged) { setHdmiPlugged(plugged, false /* force */); } @@ -741,6 +751,11 @@ public class DisplayPolicy { return true; } + private boolean hasStatusBarServicePermission(int pid, int uid) { + return mContext.checkPermission(permission.STATUS_BAR_SERVICE, pid, uid) + == PackageManager.PERMISSION_GRANTED; + } + /** * Sanitize the layout parameters coming from a client. Allows the policy * to do things like ensure that windows of a specific type can't take @@ -750,7 +765,7 @@ public class DisplayPolicy { * are modified in-place. */ public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs, - boolean hasStatusBarServicePermission) { + int callingPid, int callingUid) { final boolean isScreenDecor = (attrs.privateFlags & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0; if (mScreenDecorWindows.contains(win)) { @@ -758,7 +773,7 @@ public class DisplayPolicy { // No longer has the flag set, so remove from the set. mScreenDecorWindows.remove(win); } - } else if (isScreenDecor && hasStatusBarServicePermission) { + } else if (isScreenDecor && hasStatusBarServicePermission(callingPid, callingUid)) { mScreenDecorWindows.add(win); } @@ -856,11 +871,14 @@ public class DisplayPolicy { if (mDisplayContent.isDefaultDisplay) { mService.mPolicy.setKeyguardCandidateLw(win); } - mDisplayContent.setInsetProvider(TYPE_TOP_BAR, win, + final TriConsumer<DisplayFrames, WindowState, Rect> frameProvider = (displayFrames, windowState, rect) -> { rect.top = 0; rect.bottom = getStatusBarHeight(displayFrames); - }); + }; + mDisplayContent.setInsetProvider(TYPE_TOP_BAR, win, frameProvider); + mDisplayContent.setInsetProvider(TYPE_TOP_GESTURES, win, frameProvider); + mDisplayContent.setInsetProvider(TYPE_TOP_TAPPABLE_ELEMENT, win, frameProvider); break; case TYPE_NAVIGATION_BAR: mContext.enforceCallingOrSelfPermission( @@ -877,6 +895,31 @@ public class DisplayPolicy { mNavBarVisibilityListener, true); mDisplayContent.setInsetProvider(InsetsState.TYPE_NAVIGATION_BAR, win, null /* frameProvider */); + mDisplayContent.setInsetProvider(InsetsState.TYPE_BOTTOM_GESTURES, win, + (displayFrames, windowState, inOutFrame) -> { + inOutFrame.top -= mBottomGestureAdditionalInset; + }); + mDisplayContent.setInsetProvider(InsetsState.TYPE_LEFT_GESTURES, win, + (displayFrames, windowState, inOutFrame) -> { + inOutFrame.left = 0; + inOutFrame.top = 0; + inOutFrame.bottom = displayFrames.mDisplayHeight; + inOutFrame.right = displayFrames.mUnrestricted.left + mSideGestureInset; + }); + mDisplayContent.setInsetProvider(InsetsState.TYPE_RIGHT_GESTURES, win, + (displayFrames, windowState, inOutFrame) -> { + inOutFrame.left = displayFrames.mUnrestricted.right - mSideGestureInset; + inOutFrame.top = 0; + inOutFrame.bottom = displayFrames.mDisplayHeight; + inOutFrame.right = displayFrames.mDisplayWidth; + }); + mDisplayContent.setInsetProvider(InsetsState.TYPE_BOTTOM_TAPPABLE_ELEMENT, win, + (displayFrames, windowState, inOutFrame) -> { + if ((windowState.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0 + || mNavigationBarLetsThroughTaps) { + inOutFrame.setEmpty(); + } + }); if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar); break; case TYPE_NAVIGATION_BAR_PANEL: @@ -1169,7 +1212,7 @@ public class DisplayPolicy { final boolean useOutsets = outOutsets != null && shouldUseOutsets(attrs, fl); if (useOutsets) { - int outset = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources()); + int outset = mWindowOutsetBottom; if (outset > 0) { if (displayRotation == Surface.ROTATION_0) { outOutsets.bottom += outset; @@ -1489,12 +1532,13 @@ public class DisplayPolicy { } // apply any navigation bar insets sTmpRect.setEmpty(); - mStatusBar.getWindowFrames().setFrames(displayFrames.mUnrestricted /* parentFrame */, + final WindowFrames windowFrames = mStatusBar.getWindowFrames(); + windowFrames.setFrames(displayFrames.mUnrestricted /* parentFrame */, displayFrames.mUnrestricted /* displayFrame */, displayFrames.mStable /* overscanFrame */, displayFrames.mStable /* contentFrame */, displayFrames.mStable /* visibleFrame */, sTmpRect /* decorFrame */, displayFrames.mStable /* stableFrame */, displayFrames.mStable /* outsetFrame */); - mStatusBar.getWindowFrames().setDisplayCutout(displayFrames.mDisplayCutout); + windowFrames.setDisplayCutout(displayFrames.mDisplayCutout); // Let the status bar determine its size. mStatusBar.computeFrameLw(); @@ -1534,8 +1578,9 @@ public class DisplayPolicy { "dock=%s content=%s cur=%s", dockFrame.toString(), displayFrames.mContent.toString(), displayFrames.mCurrent.toString())); - if (!mStatusBar.isAnimatingLw() && !statusBarTranslucent - && !mStatusBarController.wasRecentlyTranslucent()) { + if (!statusBarTranslucent && !mStatusBarController.wasRecentlyTranslucent() + && !mStatusBar.isAnimatingLw()) { + // If the opaque status bar is currently requested to be visible, and not in the // process of animating on or off, then we can tell the app that it is covered by // it. @@ -2190,7 +2235,7 @@ public class DisplayPolicy { final Rect osf = windowFrames.mOutsetFrame; osf.set(cf.left, cf.top, cf.right, cf.bottom); windowFrames.setHasOutsets(true); - int outset = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources()); + int outset = mWindowOutsetBottom; if (outset > 0) { int rotation = displayFrames.mRotation; if (rotation == Surface.ROTATION_0) { @@ -2346,7 +2391,7 @@ public class DisplayPolicy { } // Voice interaction overrides both top fullscreen and top docked. - if (affectsSystemUi && win.getAttrs().type == TYPE_VOICE_INTERACTION) { + if (affectsSystemUi && attrs.type == TYPE_VOICE_INTERACTION) { if (mTopFullscreenOpaqueWindowState == null) { mTopFullscreenOpaqueWindowState = win; if (mTopFullscreenOpaqueOrDimmingWindowState == null) { @@ -2541,6 +2586,7 @@ public class DisplayPolicy { */ public void onOverlayChangedLw() { onConfigurationChanged(); + mSystemGestures.onConfigurationChanged(); } /** @@ -2602,9 +2648,31 @@ public class DisplayPolicy { res.getDimensionPixelSize(R.dimen.navigation_bar_width_car_mode); } + mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode); + mSideGestureInset = res.getDimensionPixelSize(R.dimen.config_backGestureInset); + mNavigationBarLetsThroughTaps = res.getBoolean(R.bool.config_navBarTapThrough); + // EXPERIMENT TODO(b/113952590): Remove once experiment in bug is completed mExperiments.onConfigurationChanged(uiContext); // EXPERIMENT END + + // EXPERIMENT: TODO(b/113952590): Replace with real code after experiment. + // This should calculate how much above the frame we accept gestures. Currently, + // we extend the frame to capture the gestures, so this is 0. + mBottomGestureAdditionalInset = mExperiments.getNavigationBarFrameHeight() + - mExperiments.getNavigationBarFrameHeight(); + // EXPERIMENT END + + updateConfigurationAndScreenSizeDependentBehaviors(); + mWindowOutsetBottom = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources()); + } + + void updateConfigurationAndScreenSizeDependentBehaviors() { + final Context uiContext = getSystemUiContext(); + final Resources res = uiContext.getResources(); + mNavigationBarCanMove = + mDisplayContent.mBaseDisplayWidth != mDisplayContent.mBaseDisplayHeight + && res.getBoolean(R.bool.config_navBarCanMove); } @VisibleForTesting @@ -2960,6 +3028,8 @@ public class DisplayPolicy { mLastDockedStackSysUiFlags = dockedVisibility; mLastFocusNeedsMenu = needsMenu; mFocusedApp = win.getAppToken(); + mLastNonDockedStackBounds.set(mNonDockedStackBounds); + mLastDockedStackBounds.set(mDockedStackBounds); final Rect fullscreenStackBounds = new Rect(mNonDockedStackBounds); final Rect dockedStackBounds = new Rect(mDockedStackBounds); mHandler.post(() -> { @@ -3411,6 +3481,10 @@ public class DisplayPolicy { } if (mNavigationBar != null) { pw.print(prefix); pw.print("mNavigationBar="); pw.println(mNavigationBar); + pw.print(prefix); pw.print("mNavBarOpacityMode="); pw.println(mNavBarOpacityMode); + pw.print(prefix); pw.print("mNavigationBarCanMove="); pw.println(mNavigationBarCanMove); + pw.print(prefix); pw.print("mNavigationBarPosition="); + pw.println(mNavigationBarPosition); } if (mFocusedWindow != null) { pw.print(prefix); pw.print("mFocusedWindow="); pw.println(mFocusedWindow); diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java index ab95e4b52dc6..8dae0163b612 100644 --- a/services/core/java/com/android/server/wm/InputConsumerImpl.java +++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; @@ -43,6 +44,9 @@ class InputConsumerImpl implements IBinder.DeathRecipient { final SurfaceControl mInputSurface; Rect mTmpClipRect = new Rect(); + private final Rect mTmpRect = new Rect(); + private final Point mOldPosition = new Point(); + private final Rect mOldWindowCrop = new Rect(); InputConsumerImpl(WindowManagerService service, IBinder token, String name, InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId) { @@ -112,16 +116,22 @@ class InputConsumerImpl implements IBinder.DeathRecipient { } void layout(SurfaceControl.Transaction t, int dw, int dh) { - t.setPosition(mInputSurface, 0, 0); - - mTmpClipRect.set(0, 0, dw, dh); - t.setWindowCrop(mInputSurface, mTmpClipRect); + mTmpRect.set(0, 0, dw, dh); + layout(t, mTmpRect); } void layout(SurfaceControl.Transaction t, Rect r) { - t.setPosition(mInputSurface, r.left, r.top); mTmpClipRect.set(0, 0, r.width(), r.height()); + + if (mOldPosition.equals(r.left, r.top) && mOldWindowCrop.equals(mTmpClipRect)) { + return; + } + + t.setPosition(mInputSurface, r.left, r.top); t.setWindowCrop(mInputSurface, mTmpClipRect); + + mOldPosition.set(r.left, r.top); + mOldWindowCrop.set(mTmpClipRect); } void hide(SurfaceControl.Transaction t) { diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 56694519b7fc..835b9b1885a3 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION; import static android.view.WindowManager.INPUT_CONSUMER_PIP; import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; @@ -190,8 +191,13 @@ final class InputMonitor { } void layoutInputConsumers(int dw, int dh) { - for (int i = mInputConsumers.size() - 1; i >= 0; i--) { - mInputConsumers.valueAt(i).layout(mInputTransaction, dw, dh); + try { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "layoutInputConsumer"); + for (int i = mInputConsumers.size() - 1; i >= 0; i--) { + mInputConsumers.valueAt(i).layout(mInputTransaction, dw, dh); + } + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } @@ -401,7 +407,7 @@ final class InputMonitor { final InputWindowHandle mInvalidInputWindow = new InputWindowHandle(null, null, mDisplayId); private void updateInputWindows(boolean inDrag) { - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "updateInputWindows"); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows"); navInputConsumer = getInputConsumer(INPUT_CONSUMER_NAVIGATION); pipInputConsumer = getInputConsumer(INPUT_CONSUMER_PIP); @@ -429,7 +435,7 @@ final class InputMonitor { mDisplayContent.scheduleAnimation(); - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @Override diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java index 3110fb9a40fe..c3ea72f4c2fe 100644 --- a/services/core/java/com/android/server/wm/Letterbox.java +++ b/services/core/java/com/android/server/wm/Letterbox.java @@ -92,6 +92,11 @@ public class Letterbox { mBottom.getHeight()); } + /** @return The frame that used to place the content. */ + Rect getInnerFrame() { + return mInner; + } + /** * Returns true if any part of the letterbox overlaps with the given {@code rect}. */ @@ -162,6 +167,7 @@ public class Letterbox { final InputWindowHandle mWindowHandle; final InputEventReceiver mInputEventReceiver; final WindowManagerService mWmService; + final Binder mToken = new Binder(); InputInterceptor(String namePrefix, WindowState win) { mWmService = win.mWmService; @@ -171,13 +177,12 @@ public class Letterbox { mClientChannel = channels[1]; mInputEventReceiver = new SimpleInputReceiver(mClientChannel); - final Binder token = new Binder(); - mWmService.mInputManager.registerInputChannel(mServerChannel, token); + mWmService.mInputManager.registerInputChannel(mServerChannel, mToken); mWindowHandle = new InputWindowHandle(null /* inputApplicationHandle */, null /* clientWindow */, win.getDisplayId()); mWindowHandle.name = name; - mWindowHandle.token = token; + mWindowHandle.token = mToken; mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH @@ -192,6 +197,14 @@ public class Letterbox { } void updateTouchableRegion(Rect frame) { + if (frame.isEmpty()) { + // Use null token to indicate the surface doesn't need to receive input event (see + // the usage of Layer.hasInput in SurfaceFlinger), so InputDispatcher won't keep the + // unnecessary records. + mWindowHandle.token = null; + return; + } + mWindowHandle.token = mToken; mWindowHandle.touchableRegion.set(frame); mWindowHandle.touchableRegion.translate(-frame.left, -frame.top); } @@ -289,14 +302,14 @@ public class Letterbox { t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top); t.setWindowCrop(mSurface, mSurfaceFrameRelative.width(), mSurfaceFrameRelative.height()); - if (mInputInterceptor != null) { - mInputInterceptor.updateTouchableRegion(mSurfaceFrameRelative); - t.setInputWindowInfo(mSurface, mInputInterceptor.mWindowHandle); - } t.show(mSurface); } else if (mSurface != null) { t.hide(mSurface); } + if (mSurface != null && mInputInterceptor != null) { + mInputInterceptor.updateTouchableRegion(mSurfaceFrameRelative); + t.setInputWindowInfo(mSurface, mInputInterceptor.mWindowHandle); + } } public boolean needsApplySurfaceChanges() { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index ed5f6658197b..1ca31f127b0d 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; @@ -65,6 +66,7 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; +import android.os.Trace; import android.os.UserHandle; import android.util.ArraySet; import android.util.EventLog; @@ -551,18 +553,26 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return leakedSurface || killedApps; } + void performSurfacePlacement(boolean recoveringMemory) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performSurfacePlacement"); + try { + performSurfacePlacementNoTrace(recoveringMemory); + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } + } + // "Something has changed! Let's make it correct now." // TODO: Super crazy long method that should be broken down... - void performSurfacePlacement(boolean recoveringMemory) { + void performSurfacePlacementNoTrace(boolean recoveringMemory) { if (DEBUG_WINDOW_TRACE) Slog.v(TAG, "performSurfacePlacementInner: entry. Called by " + Debug.getCallers(3)); int i; - boolean updateInputWindowsNeeded = false; if (mWmService.mFocusMayChange) { mWmService.mFocusMayChange = false; - updateInputWindowsNeeded = mWmService.updateFocusedWindowLocked( + mWmService.updateFocusedWindowLocked( UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/); } @@ -586,6 +596,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces"); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges"); mWmService.openSurfaceTransaction(); try { applySurfaceChangesTransaction(recoveringMemory); @@ -593,6 +604,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> Slog.wtf(TAG, "Unhandled exception in Window Manager", e); } finally { mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces"); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces"); } @@ -621,10 +633,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (mWmService.mFocusMayChange) { mWmService.mFocusMayChange = false; - if (mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, - false /*updateInputWindows*/)) { - updateInputWindowsNeeded = true; - } + mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, + false /*updateInputWindows*/); } if (isLayoutNeeded()) { @@ -679,12 +689,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } - // Finally update all input windows now that the window changes have stabilized. - forAllDisplays(dc -> { - dc.getInputMonitor().updateInputWindowsLw(true /*force*/); - dc.updateSystemGestureExclusion(); - }); - mWmService.setHoldScreenLocked(mHoldScreen); if (!mWmService.mDisplayFrozen) { final int brightness = mScreenBrightness < 0 || mScreenBrightness > 1.0f @@ -710,7 +714,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (mWmService.mWaitingForDrawnCallback != null || (mOrientationChangeComplete && !isLayoutNeeded() - && !mUpdateRotation)) { + && !mUpdateRotation)) { mWmService.checkDrawnWindowsLocked(); } @@ -742,12 +746,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mChildren.get(displayNdx).checkCompleteDeferredRemoval(); } - if (updateInputWindowsNeeded) { - forAllDisplays(dc -> { - dc.getInputMonitor().updateInputWindowsLw(false /*force*/); - }); - } - forAllDisplays(DisplayContent::updateTouchExcludeRegion); + forAllDisplays(dc -> { + dc.getInputMonitor().updateInputWindowsLw(true /*force*/); + dc.updateSystemGestureExclusion(); + dc.updateTouchExcludeRegion(); + }); // Check to see if we are now in a state where the screen should // be enabled, because the window obscured flags have changed. @@ -776,7 +779,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } - if (!curDisplay.isAppAnimating() && curDisplay.mAppTransition.isRunning()) { + if (curDisplay.mAppTransition.isRunning() && !curDisplay.isAppAnimating()) { // We have finished the animation of an app transition. To do this, we have // delayed a lot of operations like showing and hiding apps, moving apps in // Z-order, etc. diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 9b634f959fca..22b030dd1620 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -426,11 +426,10 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } @Override - public void updateTapExcludeRegion(IWindow window, int regionId, int left, int top, int width, - int height) { + public void updateTapExcludeRegion(IWindow window, int regionId, Region region) { final long identity = Binder.clearCallingIdentity(); try { - mService.updateTapExcludeRegion(window, regionId, left, top, width, height); + mService.updateTapExcludeRegion(window, regionId, region); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/wm/StatusBarController.java b/services/core/java/com/android/server/wm/StatusBarController.java index 6db606d2a30b..f4260d32a77d 100644 --- a/services/core/java/com/android/server/wm/StatusBarController.java +++ b/services/core/java/com/android/server/wm/StatusBarController.java @@ -36,22 +36,22 @@ public class StatusBarController extends BarController { private Runnable mAppTransitionPending = () -> { StatusBarManagerInternal statusBar = getStatusBarInternal(); - if (statusBar != null && mWin != null) { - statusBar.appTransitionPending(mWin.getDisplayId()); + if (statusBar != null) { + statusBar.appTransitionPending(mDisplayId); } }; private Runnable mAppTransitionCancelled = () -> { StatusBarManagerInternal statusBar = getStatusBarInternal(); - if (statusBar != null && mWin != null) { - statusBar.appTransitionCancelled(mWin.getDisplayId()); + if (statusBar != null) { + statusBar.appTransitionCancelled(mDisplayId); } }; private Runnable mAppTransitionFinished = () -> { StatusBarManagerInternal statusBar = getStatusBarInternal(); - if (statusBar != null && mWin != null) { - statusBar.appTransitionFinished(mWin.getDisplayId()); + if (statusBar != null) { + statusBar.appTransitionFinished(mDisplayId); } }; @@ -65,8 +65,8 @@ public class StatusBarController extends BarController { long statusBarAnimationStartTime, long statusBarAnimationDuration) { mHandler.post(() -> { StatusBarManagerInternal statusBar = getStatusBarInternal(); - if (statusBar != null && mWin != null) { - statusBar.appTransitionStarting(mWin.getDisplayId(), + if (statusBar != null) { + statusBar.appTransitionStarting(mDisplayId, statusBarAnimationStartTime, statusBarAnimationDuration); } }); @@ -84,8 +84,9 @@ public class StatusBarController extends BarController { } }; - StatusBarController() { + StatusBarController(int displayId) { super("StatusBar", + displayId, View.STATUS_BAR_TRANSIENT, View.STATUS_BAR_UNHIDE, View.STATUS_BAR_TRANSLUCENT, diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 35b8641371be..67686a586a65 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -82,6 +82,11 @@ class SurfaceAnimator { return; } final Runnable resetAndInvokeFinish = () -> { + // We need to check again if the animation has been replaced with a new + // animation because the animatable may defer to finish. + if (anim != mAnimation) { + return; + } reset(mAnimatable.getPendingTransaction(), true /* destroyLeash */); if (animationFinishedCallback != null) { animationFinishedCallback.run(); diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java index bd4e542033c5..35afaedb0b43 100644 --- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java +++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java @@ -17,9 +17,13 @@ package com.android.server.wm; import android.content.Context; +import android.graphics.Rect; +import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; import android.os.SystemClock; import android.util.Slog; +import android.view.Display; +import android.view.DisplayCutout; import android.view.GestureDetector; import android.view.InputDevice; import android.view.MotionEvent; @@ -46,8 +50,9 @@ class SystemGesturesPointerEventListener implements PointerEventListener { private final Context mContext; private final Handler mHandler; - private final int mSwipeStartThreshold; - private final int mSwipeDistanceThreshold; + private int mDisplayCutoutTouchableRegionSize; + private int mSwipeStartThreshold; + private int mSwipeDistanceThreshold; private final Callbacks mCallbacks; private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS]; private final float[] mDownX = new float[MAX_TRACKED_POINTERS]; @@ -65,14 +70,33 @@ class SystemGesturesPointerEventListener implements PointerEventListener { private long mLastFlingTime; SystemGesturesPointerEventListener(Context context, Handler handler, Callbacks callbacks) { - mContext = context; + mContext = checkNull("context", context); mHandler = handler; mCallbacks = checkNull("callbacks", callbacks); - mSwipeStartThreshold = checkNull("context", context).getResources() + + onConfigurationChanged(); + } + + void onConfigurationChanged() { + mSwipeStartThreshold = mContext.getResources() .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); + + final Display display = DisplayManagerGlobal.getInstance() + .getRealDisplay(Display.DEFAULT_DISPLAY); + final DisplayCutout displayCutout = display.getCutout(); + if (displayCutout != null) { + final Rect bounds = displayCutout.getBoundingRectTop(); + if (!bounds.isEmpty()) { + // Expand swipe start threshold such that we can catch touches that just start below + // the notch area + mDisplayCutoutTouchableRegionSize = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.display_cutout_touchable_region_size); + mSwipeStartThreshold += mDisplayCutoutTouchableRegionSize; + } + } mSwipeDistanceThreshold = mSwipeStartThreshold; if (DEBUG) Slog.d(TAG, "mSwipeStartThreshold=" + mSwipeStartThreshold - + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold); + + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold); } private static <T> T checkNull(String name, T arg) { diff --git a/services/core/java/com/android/server/wm/TapExcludeRegionHolder.java b/services/core/java/com/android/server/wm/TapExcludeRegionHolder.java index 0a4ab67f033d..22f529b28b8e 100644 --- a/services/core/java/com/android/server/wm/TapExcludeRegionHolder.java +++ b/services/core/java/com/android/server/wm/TapExcludeRegionHolder.java @@ -21,38 +21,35 @@ import android.graphics.Region; import android.util.SparseArray; /** - * A holder that contains a collection of rectangular areas identified by int id. Each individual - * region can be updated separately. + * A holder that contains a collection of regions identified by int id. Each individual region can + * be updated separately. */ class TapExcludeRegionHolder { - private SparseArray<Rect> mTapExcludeRects = new SparseArray<>(); + private SparseArray<Region> mTapExcludeRegions = new SparseArray<>(); /** Update the specified region with provided position and size. */ - void updateRegion(int regionId, int left, int top, int width, int height) { - if (width <= 0 || height <= 0) { - // A region became empty - remove it. - mTapExcludeRects.remove(regionId); + void updateRegion(int regionId, Region region) { + // Remove the previous one because there is a new one incoming. + mTapExcludeRegions.remove(regionId); + + if (region == null || region.isEmpty()) { + // The incoming region is invalid. Don't use it. return; } - Rect region = mTapExcludeRects.get(regionId); - if (region == null) { - region = new Rect(); - } - region.set(left, top, left + width, top + height); - mTapExcludeRects.put(regionId, region); + mTapExcludeRegions.put(regionId, region); } /** * Union the provided region with current region formed by this container. */ - void amendRegion(Region region, Rect boundingRegion) { - for (int i = mTapExcludeRects.size() - 1; i>= 0 ; --i) { - final Rect rect = mTapExcludeRects.valueAt(i); - if (boundingRegion != null) { - rect.intersect(boundingRegion); + void amendRegion(Region region, Rect bounds) { + for (int i = mTapExcludeRegions.size() - 1; i >= 0; --i) { + final Region r = mTapExcludeRegions.valueAt(i); + if (bounds != null) { + r.op(bounds, Region.Op.INTERSECT); } - region.union(rect); + region.op(r, Region.Op.UNION); } } } diff --git a/services/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java index 20a874b7d0e8..9fe47604d704 100644 --- a/services/core/java/com/android/server/wm/WindowFrames.java +++ b/services/core/java/com/android/server/wm/WindowFrames.java @@ -244,8 +244,6 @@ public class WindowFrames { void calculateOutsets() { if (mHasOutsets) { InsetUtils.insetsBetweenFrames(mOutsetFrame, mContentFrame, mOutsets); - } else { - mOutsets.setEmpty(); } } @@ -373,7 +371,13 @@ public class WindowFrames { * Sets whether the frame has outsets. */ public void setHasOutsets(boolean hasOutsets) { + if (mHasOutsets == hasOutsets) { + return; + } mHasOutsets = hasOutsets; + if (!hasOutsets) { + mOutsets.setEmpty(); + } } /** diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index d3f3711db0d8..9d80425435ef 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -414,7 +414,7 @@ public abstract class WindowManagerInternal { OnHardKeyboardStatusChangeListener listener); /** Returns true if a stack in the windowing mode is currently visible. */ - public abstract boolean isStackVisible(int windowingMode); + public abstract boolean isStackVisibleLw(int windowingMode); /** * Requests the window manager to resend the windows for accessibility. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4aa844ff4f49..20d02ee5355e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1392,11 +1392,9 @@ public class WindowManagerService extends IWindowManager.Stub return WindowManagerGlobal.ADD_INVALID_DISPLAY; } - final boolean hasStatusBarServicePermission = - mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE) - == PackageManager.PERMISSION_GRANTED; final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); - displayPolicy.adjustWindowParamsLw(win, win.mAttrs, hasStatusBarServicePermission); + displayPolicy.adjustWindowParamsLw(win, win.mAttrs, Binder.getCallingPid(), + Binder.getCallingUid()); win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs)); res = displayPolicy.prepareAddWindowLw(win, attrs); @@ -1932,6 +1930,11 @@ public class WindowManagerService extends IWindowManager.Stub } } + private boolean hasStatusBarPermission(int pid, int uid) { + return mContext.checkPermission(permission.STATUS_BAR, pid, uid) + == PackageManager.PERMISSION_GRANTED; + } + public int relayoutWindow(Session session, IWindow client, int seq, LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, int flags, long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, @@ -1940,13 +1943,8 @@ public class WindowManagerService extends IWindowManager.Stub SurfaceControl outSurfaceControl, InsetsState outInsetsState) { int result = 0; boolean configChanged; - final boolean hasStatusBarPermission = - mContext.checkCallingOrSelfPermission(permission.STATUS_BAR) - == PackageManager.PERMISSION_GRANTED; - final boolean hasStatusBarServicePermission = - mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE) - == PackageManager.PERMISSION_GRANTED; - + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); long origId = Binder.clearCallingIdentity(); final int displayId; synchronized (mGlobalLock) { @@ -1973,13 +1971,13 @@ public class WindowManagerService extends IWindowManager.Stub int attrChanges = 0; int flagChanges = 0; if (attrs != null) { - displayPolicy.adjustWindowParamsLw(win, attrs, hasStatusBarServicePermission); + displayPolicy.adjustWindowParamsLw(win, attrs, pid, uid); // if they don't have the permission, mask out the status bar bits if (seq == win.mSeq) { int systemUiVisibility = attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility; if ((systemUiVisibility & DISABLE_MASK) != 0) { - if (!hasStatusBarPermission) { + if (!hasStatusBarPermission(pid, uid)) { systemUiVisibility &= ~DISABLE_MASK; } } @@ -2050,7 +2048,6 @@ public class WindowManagerService extends IWindowManager.Stub && viewVisibility == View.VISIBLE; boolean imMayMove = (flagChanges & (FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE)) != 0 || becameVisible; - final boolean isDefaultDisplay = win.isDefaultDisplay(); boolean focusMayChange = win.mViewVisibility != viewVisibility || ((flagChanges & FLAG_NOT_FOCUSABLE) != 0) || (!win.mRelayoutCalled); @@ -6696,24 +6693,23 @@ public class WindowManagerService extends IWindowManager.Stub } /** - * Update a tap exclude region with a rectangular area in the window identified by the provided - * id. Touches down on this region will not: + * Update a tap exclude region in the window identified by the provided id. Touches down on this + * region will not: * <ol> * <li>Switch focus to this window.</li> * <li>Move the display of this window to top.</li> * <li>Send the touch events to this window.</li> * </ol> - * Passing an empty rect will remove the area from the exclude region of this window. + * Passing an invalid region will remove the area from the exclude region of this window. */ - void updateTapExcludeRegion(IWindow client, int regionId, int left, int top, int width, - int height) { + void updateTapExcludeRegion(IWindow client, int regionId, Region region) { synchronized (mGlobalLock) { final WindowState callingWin = windowForClientLocked(null, client, false); if (callingWin == null) { Slog.w(TAG_WM, "Bad requesting window " + client); return; } - callingWin.updateTapExcludeRegion(regionId, left, top, width, height); + callingWin.updateTapExcludeRegion(regionId, region); } } @@ -7216,11 +7212,9 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public boolean isStackVisible(int windowingMode) { - synchronized (mGlobalLock) { - final DisplayContent dc = getDefaultDisplayContentLocked(); - return dc.isStackVisible(windowingMode); - } + public boolean isStackVisibleLw(int windowingMode) { + final DisplayContent dc = getDefaultDisplayContentLocked(); + return dc.isStackVisible(windowingMode); } @Override diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 1b4aa26f7fb1..33561d3a41d6 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -149,6 +149,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // Set to true if this process is currently temporarily whitelisted to start activities even if // it's not in the foreground private volatile boolean mAllowBackgroundActivityStarts; + // Set of UIDs of clients currently bound to this process + private volatile ArraySet<Integer> mBoundClientUids = new ArraySet<Integer>(); // Thread currently set for VR scheduling int mVrThreadTid; @@ -297,6 +299,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return mPendingUiClean; } + /** @return {@code true} if the process registered to a display as a config listener. */ + boolean registeredForDisplayConfigChanges() { + return mDisplayId != INVALID_DISPLAY; + } + void postPendingUiCleanMsg(boolean pendingUiClean) { if (mListener == null) return; // Posting on handler so WM lock isn't held when we call into AM. @@ -368,6 +375,14 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return mAllowBackgroundActivityStarts; } + public void setBoundClientUids(ArraySet<Integer> boundClientUids) { + mBoundClientUids = boundClientUids; + } + + public ArraySet<Integer> getBoundClientUids() { + return mBoundClientUids; + } + public void setInstrumenting(boolean instrumenting, boolean hasBackgroundActivityStartPrivileges) { mInstrumenting = instrumenting; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 6a21327d8bb9..11288d27fce6 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -72,6 +72,7 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFO import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; +import static com.android.server.am.ActivityManagerService.MY_PID; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT; @@ -307,6 +308,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ private final MergedConfiguration mLastReportedConfiguration = new MergedConfiguration(); + /** @see #isLastConfigReportedToClient() */ + private boolean mLastConfigReportedToClient; + private final Configuration mTempConfiguration = new Configuration(); /** @@ -1200,7 +1204,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } boolean didFrameInsetsChange = setReportResizeHints(); - boolean configChanged = isConfigChanged(); + boolean configChanged = !isLastConfigReportedToClient(); if (DEBUG_CONFIGURATION && configChanged) { Slog.v(TAG_WM, "Win " + this + " config changed: " + getConfiguration()); } @@ -1780,9 +1784,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return getDisplayContent().getBounds().equals(getBounds()); } - /** Returns true if last applied config was not yet requested by client. */ - boolean isConfigChanged() { - return !getLastReportedConfiguration().equals(getConfiguration()); + /** + * @return {@code true} if last applied config was reported to the client already, {@code false} + * otherwise. + */ + boolean isLastConfigReportedToClient() { + return mLastConfigReportedToClient; + } + + @Override + void onMergedOverrideConfigurationChanged() { + super.onMergedOverrideConfigurationChanged(); + mLastConfigReportedToClient = false; } void onWindowReplacementTimeout() { @@ -2209,16 +2222,22 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (modal && mAppToken != null) { // Limit the outer touch to the activity stack region. flags |= FLAG_NOT_TOUCH_MODAL; - // If this is a modal window we need to dismiss it if it's not full screen and the - // touch happens outside of the frame that displays the content. This means we - // need to intercept touches outside of that window. The dim layer user - // associated with the window (task or stack) will give us the good bounds, as - // they would be used to display the dim layer. - final Task task = getTask(); - if (task != null) { - task.getDimBounds(mTmpRect); - } else { - getStack().getDimBounds(mTmpRect); + // If the inner bounds of letterbox is available, then it will be used as the touchable + // region so it won't cover the touchable letterbox and the touch events can slip to + // activity from letterbox. + mAppToken.getLetterboxInnerBounds(mTmpRect); + if (mTmpRect.isEmpty()) { + // If this is a modal window we need to dismiss it if it's not full screen and the + // touch happens outside of the frame that displays the content. This means we need + // to intercept touches outside of that window. The dim layer user associated with + // the window (task or stack) will give us the good bounds, as they would be used to + // display the dim layer. + final Task task = getTask(); + if (task != null) { + task.getDimBounds(mTmpRect); + } else { + getStack().getDimBounds(mTmpRect); + } } if (inFreeformWindowingMode()) { // For freeform windows we the touch region to include the whole surface for the @@ -2292,11 +2311,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void prepareWindowToDisplayDuringRelayout(boolean wasVisible) { // We need to turn on screen regardless of visibility. boolean hasTurnScreenOnFlag = (mAttrs.flags & FLAG_TURN_SCREEN_ON) != 0; - boolean allowTheaterMode = - mWmService.mAllowTheaterModeWakeFromLayout || Settings.Global.getInt( - mWmService.mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0) - == 0; - boolean canTurnScreenOn = mAppToken == null || mAppToken.canTurnScreenOn(); // The screen will turn on if the following conditions are met // 1. The window has the flag FLAG_TURN_SCREEN_ON @@ -2310,6 +2324,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // be occurring while turning off the screen. This would lead to the screen incorrectly // turning back on. if (hasTurnScreenOnFlag) { + boolean allowTheaterMode = mWmService.mAllowTheaterModeWakeFromLayout + || Settings.Global.getInt(mWmService.mContext.getContentResolver(), + Settings.Global.THEATER_MODE_ON, 0) == 0; + boolean canTurnScreenOn = mAppToken == null || mAppToken.canTurnScreenOn(); + if (allowTheaterMode && canTurnScreenOn && !mPowerManagerWrapper.isInteractive()) { if (DEBUG_VISIBILITY || DEBUG_POWER) { Slog.v(TAG, "Relayout window turning screen on: " + this); @@ -2343,12 +2362,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private Configuration getProcessGlobalConfiguration() { // For child windows we want to use the pid for the parent window in case the the child // window was added from another process. - final int pid = getParentWindow() != null ? getParentWindow().mSession.mPid : mSession.mPid; + final WindowState parentWindow = getParentWindow(); + final int pid = parentWindow != null ? parentWindow.mSession.mPid : mSession.mPid; final Configuration processConfig = mWmService.mAtmService.getGlobalConfigurationForPid(pid); - mTempConfiguration.setTo(processConfig == null - ? mWmService.mRoot.getConfiguration() : processConfig); - return mTempConfiguration; + return processConfig; } void getMergedConfiguration(MergedConfiguration outConfiguration) { @@ -2359,6 +2377,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void setLastReportedMergedConfiguration(MergedConfiguration config) { mLastReportedConfiguration.setTo(config); + mLastConfigReportedToClient = true; } void getLastReportedMergedConfiguration(MergedConfiguration config) { @@ -2506,6 +2525,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } boolean showLw(boolean doAnimation, boolean requestAnim) { + if (mPolicyVisibility && mPolicyVisibilityAfterAnim) { + // Already showing. + return false; + } if (isHiddenFromUserLocked()) { return false; } @@ -2526,10 +2549,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // This is an alert window that is currently force hidden. return false; } - if (mPolicyVisibility && mPolicyVisibilityAfterAnim) { - // Already showing. - return false; - } if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility true: " + this); if (doAnimation) { if (DEBUG_VISIBILITY) Slog.v(TAG, "doAnimation: mPolicyVisibility=" @@ -2983,11 +3002,29 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return mAppToken.mFrozenMergedConfig.peek(); } + // If the process has not registered to any display to listen to the configuration change, + // we can simply return the mFullConfiguration as default. + if (!registeredForDisplayConfigChanges()) { + return super.getConfiguration(); + } + // We use the process config this window is associated with as the based global config since - // the process can override it config, but isn't part of the window hierarchy. - final Configuration config = getProcessGlobalConfiguration(); - config.updateFrom(getMergedOverrideConfiguration()); - return config; + // the process can override its config, but isn't part of the window hierarchy. + mTempConfiguration.setTo(getProcessGlobalConfiguration()); + mTempConfiguration.updateFrom(getMergedOverrideConfiguration()); + return mTempConfiguration; + } + + /** @return {@code true} if the process registered to a display as a config listener. */ + private boolean registeredForDisplayConfigChanges() { + final WindowState parentWindow = getParentWindow(); + final Session session = parentWindow != null ? parentWindow.mSession : mSession; + // System process or invalid process cannot register to display config change. + if (session.mPid == MY_PID || session.mPid < 0) return false; + WindowProcessController app = + mWmService.mAtmService.getProcessController(session.mPid, session.mUid); + if (app == null || !app.registeredForDisplayConfigChanges()) return false; + return true; } void reportResized() { @@ -3505,7 +3542,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } void transformClipRectFromScreenToSurfaceSpace(Rect clipRect) { - if (mHScale >= 0) { + if (mHScale == 1 && mVScale == 1) { + return; + } + if (mHScale >= 0) { clipRect.left = (int) (clipRect.left / mHScale); clipRect.right = (int) Math.ceil(clipRect.right / mHScale); } @@ -4361,7 +4401,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // scale function because we want to round things to make the crop // always round to a larger rect to ensure we don't crop too // much and hide part of the window that should be seen. - if (inSizeCompatMode() && mInvGlobalScale != 1.0f) { + if (mInvGlobalScale != 1.0f && inSizeCompatMode()) { final float scale = mInvGlobalScale; systemDecorRect.left = (int) (systemDecorRect.left * scale - 0.5f); systemDecorRect.top = (int) (systemDecorRect.top * scale - 0.5f); @@ -4416,7 +4456,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWinAnimator.mEnteringAnimation = true; - prepareWindowToDisplayDuringRelayout(wasVisible); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "prepareToDisplay"); + try { + prepareWindowToDisplayDuringRelayout(wasVisible); + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } if ((attrChanges & FORMAT_CHANGED) != 0) { // If the format can't be changed in place, preserve the old surface until the app draws @@ -4831,10 +4876,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } /** - * Update a tap exclude region with a rectangular area identified by provided id. The requested - * area will be clipped to the window bounds. + * Update a tap exclude region identified by provided id. The requested area will be clipped to + * the window bounds. */ - void updateTapExcludeRegion(int regionId, int left, int top, int width, int height) { + void updateTapExcludeRegion(int regionId, Region region) { final DisplayContent currentDisplay = getDisplayContent(); if (currentDisplay == null) { throw new IllegalStateException("Trying to update window not attached to any display."); @@ -4848,7 +4893,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP currentDisplay.mTapExcludeProvidingWindows.add(this); } - mTapExcludeRegionHolder.updateRegion(regionId, left, top, width, height); + mTapExcludeRegionHolder.updateRegion(regionId, region); // Trigger touch exclude region update on current display. currentDisplay.updateTouchExcludeRegion(); // Trigger touchable region update for this window. diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index acb98237b5d1..bef0f81e4dc1 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -56,6 +56,7 @@ class WindowSurfaceController { private float mSurfaceY = 0; private int mSurfaceW = 0; private int mSurfaceH = 0; + private Rect mSurfaceCrop = new Rect(0, 0, -1, -1); // Initialize to the identity matrix. private float mLastDsdx = 1; @@ -171,26 +172,15 @@ class WindowSurfaceController { } } - void disconnectInTransaction() { - if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { - Slog.i(TAG, "Disconnecting client: " + this); - } - - try { - if (mSurfaceControl != null) { - mSurfaceControl.disconnect(); - } - } catch (RuntimeException e) { - Slog.w(TAG, "Error disconnecting surface in: " + this, e); - } - } - void setCropInTransaction(Rect clipRect, boolean recoveringMemory) { if (SHOW_TRANSACTIONS) logSurface( "CROP " + clipRect.toShortString(), null); try { if (clipRect.width() > 0 && clipRect.height() > 0) { - mSurfaceControl.setWindowCrop(clipRect); + if (!clipRect.equals(mSurfaceCrop)) { + mSurfaceControl.setWindowCrop(clipRect); + mSurfaceCrop.set(clipRect); + } mHiddenForCrop = false; updateVisibility(); } else { @@ -212,7 +202,11 @@ class WindowSurfaceController { "CLEAR CROP", null); try { Rect clipRect = new Rect(0, 0, -1, -1); + if (mSurfaceCrop.equals(clipRect)) { + return; + } mSurfaceControl.setWindowCrop(clipRect); + mSurfaceCrop.set(clipRect); } catch (RuntimeException e) { Slog.w(TAG, "Error setting clearing crop of " + this, e); if (!recoveringMemory) { @@ -221,12 +215,6 @@ class WindowSurfaceController { } } - void setLayerStackInTransaction(int layerStack) { - if (mSurfaceControl != null) { - mSurfaceControl.setLayerStack(layerStack); - } - } - void setPositionInTransaction(float left, float top, boolean recoveringMemory) { setPosition(null, left, top, recoveringMemory); } diff --git a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java index 98bad93e09e2..3be5d3176df5 100644 --- a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java +++ b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java @@ -81,11 +81,24 @@ public class WmDisplayCutout { * @hide */ public WmDisplayCutout calculateRelativeTo(Rect frame) { + if (mFrameSize == null) { + return this; + } + final int insetRight = mFrameSize.getWidth() - frame.right; + final int insetBottom = mFrameSize.getHeight() - frame.bottom; + if (frame.left == 0 && frame.top == 0 && insetRight == 0 && insetBottom == 0) { + return this; + } + if (frame.left >= mInner.getSafeInsetLeft() + && frame.top >= mInner.getSafeInsetTop() + && insetRight >= mInner.getSafeInsetRight() + && insetBottom >= mInner.getSafeInsetBottom()) { + return NO_CUTOUT; + } if (mInner.isEmpty()) { return this; } - return inset(frame.left, frame.top, - mFrameSize.getWidth() - frame.right, mFrameSize.getHeight() - frame.bottom); + return inset(frame.left, frame.top, insetRight, insetBottom); } /** diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 3d84bd40fb5a..3d6c8684e6d8 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -96,6 +96,7 @@ static struct { jmethodID interceptKeyBeforeDispatching; jmethodID dispatchUnhandledKey; jmethodID checkInjectEventsPermission; + jmethodID onPointerDownOutsideFocus; jmethodID getVirtualKeyQuietTimeMillis; jmethodID getExcludedDeviceNames; jmethodID getInputPortAssociations; @@ -259,6 +260,7 @@ public: virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType); virtual bool checkInjectEventsPermissionNonReentrant( int32_t injectorPid, int32_t injectorUid); + virtual void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken); /* --- PointerControllerPolicyInterface implementation --- */ @@ -1205,6 +1207,15 @@ bool NativeInputManager::checkInjectEventsPermissionNonReentrant( return result; } +void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) { + ATRACE_CALL(); + JNIEnv* env = jniEnv(); + + jobject touchedTokenObj = javaObjectForIBinder(env, touchedToken); + env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDownOutsideFocus, touchedTokenObj); + checkAndClearExceptionFromCallback(env, "onPointerDownOutsideFocus"); +} + void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) { ATRACE_CALL(); JNIEnv* env = jniEnv(); @@ -1809,6 +1820,9 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz, "checkInjectEventsPermission", "(II)Z"); + GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz, + "onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V"); + GET_METHOD_ID(gServiceClassInfo.getVirtualKeyQuietTimeMillis, clazz, "getVirtualKeyQuietTimeMillis", "()I"); diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 7df7ef3bae87..0b47b29e8d44 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -1700,8 +1700,12 @@ static void android_location_GnssLocationProvider_init_once(JNIEnv* env, jclass gnssNavigationMessageIface = gnssNavigationMessage; } - if (gnssHal_V2_0 != nullptr) { - // TODO: getExtensionGnssMeasurement_1_1 from gnssHal_V2_0 + // Allow all causal combinations between IGnss.hal and IGnssMeasurement.hal. That means, + // 2.0@IGnss can be paired with {1.0, 1,1, 2.0}@IGnssMeasurement + // 1.1@IGnss can be paired {1.0, 1.1}@IGnssMeasurement + // 1.0@IGnss is paired with 1.0@IGnssMeasurement + gnssMeasurementIface = nullptr; + if (gnssHal_V2_0 != nullptr && gnssMeasurementIface == nullptr) { auto gnssMeasurement = gnssHal_V2_0->getExtensionGnssMeasurement_2_0(); if (!gnssMeasurement.isOk()) { ALOGD("Unable to get a handle to GnssMeasurement_V2_0"); @@ -1710,13 +1714,8 @@ static void android_location_GnssLocationProvider_init_once(JNIEnv* env, jclass gnssMeasurementIface_V1_1 = gnssMeasurementIface_V2_0; gnssMeasurementIface = gnssMeasurementIface_V2_0; } - auto gnssCorrections = gnssHal_V2_0->getExtensionMeasurementCorrections(); - if (!gnssCorrections.isOk()) { - ALOGD("Unable to get a handle to GnssMeasurementCorrections interface"); - } else { - gnssCorrectionsIface = gnssCorrections; - } - } else if (gnssHal_V1_1 != nullptr) { + } + if (gnssHal_V1_1 != nullptr && gnssMeasurementIface == nullptr) { auto gnssMeasurement = gnssHal_V1_1->getExtensionGnssMeasurement_1_1(); if (!gnssMeasurement.isOk()) { ALOGD("Unable to get a handle to GnssMeasurement_V1_1"); @@ -1724,16 +1723,26 @@ static void android_location_GnssLocationProvider_init_once(JNIEnv* env, jclass gnssMeasurementIface_V1_1 = gnssMeasurement; gnssMeasurementIface = gnssMeasurementIface_V1_1; } - } else { - auto gnssMeasurement_V1_0 = gnssHal->getExtensionGnssMeasurement(); - if (!gnssMeasurement_V1_0.isOk()) { + } + if (gnssMeasurementIface == nullptr) { + auto gnssMeasurement = gnssHal->getExtensionGnssMeasurement(); + if (!gnssMeasurement.isOk()) { ALOGD("Unable to get a handle to GnssMeasurement"); } else { - gnssMeasurementIface = gnssMeasurement_V1_0; + gnssMeasurementIface = gnssMeasurement; } } if (gnssHal_V2_0 != nullptr) { + auto gnssCorrections = gnssHal_V2_0->getExtensionMeasurementCorrections(); + if (!gnssCorrections.isOk()) { + ALOGD("Unable to get a handle to GnssMeasurementCorrections interface"); + } else { + gnssCorrectionsIface = gnssCorrections; + } + } + + if (gnssHal_V2_0 != nullptr) { auto gnssDebug = gnssHal_V2_0->getExtensionGnssDebug_2_0(); if (!gnssDebug.isOk()) { ALOGD("Unable to get a handle to GnssDebug_V2_0"); diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp index 5e1ea897b86e..98e4343e6e57 100644 --- a/services/core/xsd/Android.bp +++ b/services/core/xsd/Android.bp @@ -2,5 +2,5 @@ xsd_config { name: "default-permissions", srcs: ["default-permissions.xsd"], api_dir: "schema", - package_name: "com.android.server.pm.permission", + package_name: "com.android.server.pm.permission.configfile", } diff --git a/services/core/xsd/default-permissions.xsd b/services/core/xsd/default-permissions.xsd index d800a26cdceb..2e32be0009ba 100644 --- a/services/core/xsd/default-permissions.xsd +++ b/services/core/xsd/default-permissions.xsd @@ -27,7 +27,7 @@ </xs:element> <xs:complexType name="exception"> <xs:sequence> - <xs:element name="permission" type="permission"/> + <xs:element name="permission" type="permission" maxOccurs="unbounded"/> </xs:sequence> <xs:attribute name="package" type="xs:string"/> <xs:attribute name="sha256-cert-digest" type="xs:string"/> diff --git a/services/core/xsd/schema/current.txt b/services/core/xsd/schema/current.txt index 4e67e5c235a0..a2092e3a99dc 100644 --- a/services/core/xsd/schema/current.txt +++ b/services/core/xsd/schema/current.txt @@ -1,21 +1,20 @@ // Signature format: 2.0 -package com.android.server.pm.permission { +package com.android.server.pm.permission.configfile { public class Exception { ctor public Exception(); method public String getBrand(); - method public com.android.server.pm.permission.Permission getPermission(); + method public java.util.List<com.android.server.pm.permission.configfile.Permission> getPermission(); method public String getSha256CertDigest(); method public String get_package(); method public void setBrand(String); - method public void setPermission(com.android.server.pm.permission.Permission); method public void setSha256CertDigest(String); method public void set_package(String); } public class Exceptions { ctor public Exceptions(); - method public java.util.List<com.android.server.pm.permission.Exception> getException(); + method public java.util.List<com.android.server.pm.permission.configfile.Exception> getException(); } public class Permission { @@ -28,7 +27,7 @@ package com.android.server.pm.permission { public class XmlParser { ctor public XmlParser(); - method public static com.android.server.pm.permission.Exceptions read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static com.android.server.pm.permission.configfile.Exceptions read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 2bf6f357bec8..bd28be1faba1 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -75,8 +75,7 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { } @Override - public boolean checkDeviceIdentifierAccess(String packageName, int userHandle, int pid, - int uid) { + public boolean checkDeviceIdentifierAccess(String packageName, int pid, int uid) { return false; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 633367ab2fe8..b5c845a9d012 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -8398,13 +8398,40 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public boolean checkDeviceIdentifierAccess(String packageName, int userHandle, int pid, - int uid) { + public boolean checkDeviceIdentifierAccess(String packageName, int pid, int uid) { // If the caller is not a system app then it should only be able to check its own device // identifier access. - int callingAppId = UserHandle.getAppId(mInjector.binderGetCallingUid()); - if (callingAppId >= Process.FIRST_APPLICATION_UID - && callingAppId != UserHandle.getAppId(uid)) { + int callingUid = mInjector.binderGetCallingUid(); + int callingPid = mInjector.binderGetCallingPid(); + if (UserHandle.getAppId(callingUid) >= Process.FIRST_APPLICATION_UID + && (callingUid != uid || callingPid != pid)) { + String message = String.format( + "Calling uid %d, pid %d cannot check device identifier access for package %s " + + "(uid=%d, pid=%d)", callingUid, callingPid, packageName, uid, pid); + Log.w(LOG_TAG, message); + throw new SecurityException(message); + } + // Verify that the specified packages matches the provided uid. + int userId = UserHandle.getUserId(uid); + try { + ApplicationInfo appInfo = mIPackageManager.getApplicationInfo(packageName, 0, userId); + // Since this call goes directly to PackageManagerService a NameNotFoundException is not + // thrown but null data can be returned; if the appInfo for the specified package cannot + // be found then return false to prevent crashing the app. + if (appInfo == null) { + Log.w(LOG_TAG, + String.format("appInfo could not be found for package %s", packageName)); + return false; + } else if (uid != appInfo.uid) { + String message = String.format("Package %s (uid=%d) does not match provided uid %d", + packageName, appInfo.uid, uid); + Log.w(LOG_TAG, message); + throw new SecurityException(message); + } + } catch (RemoteException e) { + // If an exception is caught obtaining the appInfo just return false to prevent crashing + // apps due to an internal error. + Log.e(LOG_TAG, "Exception caught obtaining appInfo for package " + packageName, e); return false; } // A device or profile owner must also have the READ_PHONE_STATE permission to access device @@ -8421,7 +8448,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return true; } // Allow access to the profile owner for the specified user, or delegate cert installer - ComponentName profileOwner = getProfileOwnerAsUser(userHandle); + ComponentName profileOwner = getProfileOwnerAsUser(userId); if (profileOwner != null && (profileOwner.getPackageName().equals(packageName) || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) { return true; @@ -11180,48 +11207,51 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public Intent createUserRestrictionSupportIntent(int userId, String userRestriction) { - int source; - long ident = mInjector.binderClearCallingIdentity(); + final long ident = mInjector.binderClearCallingIdentity(); try { - source = mUserManager.getUserRestrictionSource(userRestriction, - UserHandle.of(userId)); + final List<UserManager.EnforcingUser> sources = mUserManager + .getUserRestrictionSources(userRestriction, UserHandle.of(userId)); + if (sources == null || sources.isEmpty()) { + // The restriction is not enforced. + return null; + } else if (sources.size() > 1) { + // In this case, we'll show an admin support dialog that does not + // specify the admin. + // TODO(b/128928355): if this restriction is enforced by multiple DPCs, return + // the admin for the calling user. + return DevicePolicyManagerService.this.createShowAdminSupportIntent( + null, userId); + } + final UserManager.EnforcingUser enforcingUser = sources.get(0); + final int sourceType = enforcingUser.getUserRestrictionSource(); + final int enforcingUserId = enforcingUser.getUserHandle().getIdentifier(); + if (sourceType == UserManager.RESTRICTION_SOURCE_PROFILE_OWNER) { + // Restriction was enforced by PO + final ComponentName profileOwner = mOwners.getProfileOwnerComponent( + enforcingUserId); + if (profileOwner != null) { + return DevicePolicyManagerService.this.createShowAdminSupportIntent( + profileOwner, enforcingUserId); + } + } else if (sourceType == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) { + // Restriction was enforced by DO + final Pair<Integer, ComponentName> deviceOwner = + mOwners.getDeviceOwnerUserIdAndComponent(); + if (deviceOwner != null) { + return DevicePolicyManagerService.this.createShowAdminSupportIntent( + deviceOwner.second, deviceOwner.first); + } + } else if (sourceType == UserManager.RESTRICTION_SOURCE_SYSTEM) { + /* + * In this case, the user restriction is enforced by the system. + * So we won't show an admin support intent, even if it is also + * enforced by a profile/device owner. + */ + return null; + } } finally { mInjector.binderRestoreCallingIdentity(ident); } - if ((source & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) { - /* - * In this case, the user restriction is enforced by the system. - * So we won't show an admin support intent, even if it is also - * enforced by a profile/device owner. - */ - return null; - } - boolean enforcedByDo = (source & UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) != 0; - boolean enforcedByPo = (source & UserManager.RESTRICTION_SOURCE_PROFILE_OWNER) != 0; - if (enforcedByDo && enforcedByPo) { - // In this case, we'll show an admin support dialog that does not - // specify the admin. - return DevicePolicyManagerService.this.createShowAdminSupportIntent(null, userId); - } else if (enforcedByPo) { - final ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId); - if (profileOwner != null) { - return DevicePolicyManagerService.this - .createShowAdminSupportIntent(profileOwner, userId); - } - // This could happen if another thread has changed the profile owner since we called - // getUserRestrictionSource - return null; - } else if (enforcedByDo) { - final Pair<Integer, ComponentName> deviceOwner - = mOwners.getDeviceOwnerUserIdAndComponent(); - if (deviceOwner != null) { - return DevicePolicyManagerService.this - .createShowAdminSupportIntent(deviceOwner.second, deviceOwner.first); - } - // This could happen if another thread has changed the device owner since we called - // getUserRestrictionSource - return null; - } return null; } diff --git a/services/net/Android.bp b/services/net/Android.bp index 67fbdc4d95f2..8f48f5b3d292 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -58,6 +58,7 @@ java_library_static { name: "services.net", srcs: ["java/**/*.java"], static_libs: [ + "dnsresolver_aidl_interface-java", "netd_aidl_interface-java", "networkstack-aidl-interfaces-java", ] @@ -69,7 +70,7 @@ java_library_static { srcs: [ ":framework-annotations", "java/android/net/IpMemoryStoreClient.java", - "java/android/net/ipmemorystore/**.java", + "java/android/net/ipmemorystore/**/*.java", ], static_libs: [ "ipmemorystore-aidl-interfaces-java", diff --git a/services/net/java/android/net/INetworkMonitor.aidl b/services/net/java/android/net/INetworkMonitor.aidl index 1b0e1d788ff3..3ed4640525c6 100644 --- a/services/net/java/android/net/INetworkMonitor.aidl +++ b/services/net/java/android/net/INetworkMonitor.aidl @@ -15,6 +15,8 @@ */ package android.net; +import android.net.LinkProperties; +import android.net.NetworkCapabilities; import android.net.PrivateDnsConfigParcel; /** @hide */ @@ -46,8 +48,8 @@ oneway interface INetworkMonitor { void notifyPrivateDnsChanged(in PrivateDnsConfigParcel config); void notifyDnsResponse(int returnCode); void notifySystemReady(); - void notifyNetworkConnected(); + void notifyNetworkConnected(in LinkProperties lp, in NetworkCapabilities nc); void notifyNetworkDisconnected(); - void notifyLinkPropertiesChanged(); - void notifyNetworkCapabilitiesChanged(); + void notifyLinkPropertiesChanged(in LinkProperties lp); + void notifyNetworkCapabilitiesChanged(in NetworkCapabilities nc); }
\ No newline at end of file diff --git a/services/net/java/android/net/ipmemorystore/NetworkAttributes.java b/services/net/java/android/net/ipmemorystore/NetworkAttributes.java index 6a9eae00e3ff..e76976991797 100644 --- a/services/net/java/android/net/ipmemorystore/NetworkAttributes.java +++ b/services/net/java/android/net/ipmemorystore/NetworkAttributes.java @@ -60,6 +60,13 @@ public class NetworkAttributes { public final Inet4Address assignedV4Address; private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f; + // The lease expiry timestamp of v4 address allocated from DHCP server, in milliseconds. + @Nullable + public final Long assignedV4AddressExpiry; + // lease expiry doesn't imply any correlation between "the same lease expiry value" and "the + // same L3 network". + private static final float WEIGHT_ASSIGNEDV4ADDREXPIRY = 0.0f; + // Optionally supplied by the client if it has an opinion on L3 network. For example, this // could be a hash of the SSID + security type on WiFi. @Nullable @@ -81,6 +88,7 @@ public class NetworkAttributes { /** @hide */ @VisibleForTesting public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR + + WEIGHT_ASSIGNEDV4ADDREXPIRY + WEIGHT_GROUPHINT + WEIGHT_DNSADDRESSES + WEIGHT_MTU; @@ -89,11 +97,16 @@ public class NetworkAttributes { @VisibleForTesting public NetworkAttributes( @Nullable final Inet4Address assignedV4Address, + @Nullable final Long assignedV4AddressExpiry, @Nullable final String groupHint, @Nullable final List<InetAddress> dnsAddresses, @Nullable final Integer mtu) { if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative"); + if (assignedV4AddressExpiry != null && assignedV4AddressExpiry <= 0) { + throw new IllegalArgumentException("lease expiry can't be negative or zero"); + } this.assignedV4Address = assignedV4Address; + this.assignedV4AddressExpiry = assignedV4AddressExpiry; this.groupHint = groupHint; this.dnsAddresses = null == dnsAddresses ? null : Collections.unmodifiableList(new ArrayList<>(dnsAddresses)); @@ -105,6 +118,8 @@ public class NetworkAttributes { // The call to the other constructor must be the first statement of this constructor, // so everything has to be inline this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address), + parcelable.assignedV4AddressExpiry > 0 + ? parcelable.assignedV4AddressExpiry : null, parcelable.groupHint, blobArrayToInetAddressList(parcelable.dnsAddresses), parcelable.mtu >= 0 ? parcelable.mtu : null); @@ -150,6 +165,8 @@ public class NetworkAttributes { final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable(); parcelable.assignedV4Address = (null == assignedV4Address) ? null : assignedV4Address.getAddress(); + parcelable.assignedV4AddressExpiry = + (null == assignedV4AddressExpiry) ? 0 : assignedV4AddressExpiry; parcelable.groupHint = groupHint; parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses); parcelable.mtu = (null == mtu) ? -1 : mtu; @@ -168,6 +185,8 @@ public class NetworkAttributes { public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) { final float samenessScore = samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address) + + samenessContribution(WEIGHT_ASSIGNEDV4ADDREXPIRY, assignedV4AddressExpiry, + o.assignedV4AddressExpiry) + samenessContribution(WEIGHT_GROUPHINT, groupHint, o.groupHint) + samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses) + samenessContribution(WEIGHT_MTU, mtu, o.mtu); @@ -189,6 +208,8 @@ public class NetworkAttributes { @Nullable private Inet4Address mAssignedAddress; @Nullable + private Long mAssignedAddressExpiry; + @Nullable private String mGroupHint; @Nullable private List<InetAddress> mDnsAddresses; @@ -206,6 +227,20 @@ public class NetworkAttributes { } /** + * Set the lease expiry timestamp of assigned v4 address. + * @param assignedV4AddressExpiry The lease expiry timestamp of assigned v4 address. + * @return This builder. + */ + public Builder setAssignedV4AddressExpiry( + @Nullable final Long assignedV4AddressExpiry) { + if (null != assignedV4AddressExpiry && assignedV4AddressExpiry <= 0) { + throw new IllegalArgumentException("lease expiry can't be negative or zero"); + } + mAssignedAddressExpiry = assignedV4AddressExpiry; + return this; + } + + /** * Set the group hint. * @param groupHint The group hint. * @return This builder. @@ -248,14 +283,15 @@ public class NetworkAttributes { * @return The built NetworkAttributes object. */ public NetworkAttributes build() { - return new NetworkAttributes(mAssignedAddress, mGroupHint, mDnsAddresses, mMtu); + return new NetworkAttributes(mAssignedAddress, mAssignedAddressExpiry, + mGroupHint, mDnsAddresses, mMtu); } } /** @hide */ public boolean isEmpty() { - return (null == assignedV4Address) && (null == groupHint) - && (null == dnsAddresses) && (null == mtu); + return (null == assignedV4Address) && (null == assignedV4AddressExpiry) + && (null == groupHint) && (null == dnsAddresses) && (null == mtu); } @Override @@ -263,6 +299,7 @@ public class NetworkAttributes { if (!(o instanceof NetworkAttributes)) return false; final NetworkAttributes other = (NetworkAttributes) o; return Objects.equals(assignedV4Address, other.assignedV4Address) + && Objects.equals(assignedV4AddressExpiry, other.assignedV4AddressExpiry) && Objects.equals(groupHint, other.groupHint) && Objects.equals(dnsAddresses, other.dnsAddresses) && Objects.equals(mtu, other.mtu); @@ -270,7 +307,8 @@ public class NetworkAttributes { @Override public int hashCode() { - return Objects.hash(assignedV4Address, groupHint, dnsAddresses, mtu); + return Objects.hash(assignedV4Address, assignedV4AddressExpiry, + groupHint, dnsAddresses, mtu); } /** Pretty print */ @@ -286,6 +324,13 @@ public class NetworkAttributes { nullFields.add("assignedV4Addr"); } + if (null != assignedV4AddressExpiry) { + resultJoiner.add("assignedV4AddressExpiry :"); + resultJoiner.add(assignedV4AddressExpiry.toString()); + } else { + nullFields.add("assignedV4AddressExpiry"); + } + if (null != groupHint) { resultJoiner.add("groupHint :"); resultJoiner.add(groupHint); diff --git a/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl index 0894d7260915..997eb2b5128b 100644 --- a/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl +++ b/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl @@ -30,6 +30,7 @@ import android.net.ipmemorystore.Blob; */ parcelable NetworkAttributesParcelable { byte[] assignedV4Address; + long assignedV4AddressExpiry; String groupHint; Blob[] dnsAddresses; int mtu; diff --git a/services/robotests/backup/Android.mk b/services/robotests/backup/Android.mk index cc59b0c9bb16..bd4ebbd393fa 100644 --- a/services/robotests/backup/Android.mk +++ b/services/robotests/backup/Android.mk @@ -26,7 +26,7 @@ LOCAL_MODULE_TAGS := optional LOCAL_PRIVILEGED_MODULE := true LOCAL_STATIC_JAVA_LIBRARIES := \ - bmgrlib \ + bmgr \ bu \ services.backup \ services.core \ diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java index 7c91b6459fc8..7a40e449aaec 100644 --- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java @@ -26,6 +26,7 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -57,6 +58,8 @@ import static org.mockito.Mockito.atLeastOnce; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.IActivityManager; +import android.app.IAlarmCompleteListener; +import android.app.IAlarmListener; import android.app.IUidObserver; import android.app.PendingIntent; import android.app.usage.UsageStatsManagerInternal; @@ -67,6 +70,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.Settings; @@ -231,7 +235,7 @@ public class AlarmManagerServiceTest { doReturn(Looper.getMainLooper()).when(Looper::myLooper); when(mMockContext.getContentResolver()).thenReturn(mMockResolver); - doReturn("min_futurity=0").when(() -> + doReturn("min_futurity=0,min_interval=0").when(() -> Settings.Global.getString(mMockResolver, Settings.Global.ALARM_MANAGER_CONSTANTS)); mInjector = new Injector(mMockContext); mService = new AlarmManagerService(mMockContext, mInjector); @@ -249,6 +253,7 @@ public class AlarmManagerServiceTest { // Other boot phases don't matter mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); assertEquals(0, mService.mConstants.MIN_FUTURITY); + assertEquals(0, mService.mConstants.MIN_INTERVAL); mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW; ArgumentCaptor<UsageStatsManagerInternal.AppIdleStateChangeListener> captor = ArgumentCaptor.forClass(UsageStatsManagerInternal.AppIdleStateChangeListener.class); @@ -257,15 +262,28 @@ public class AlarmManagerServiceTest { } private void setTestAlarm(int type, long triggerTime, PendingIntent operation) { - setTestAlarm(type, triggerTime, operation, TEST_CALLING_UID); + setTestAlarm(type, triggerTime, operation, 0, TEST_CALLING_UID); } - private void setTestAlarm(int type, long triggerTime, PendingIntent operation, int callingUid) { - mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, 0, + private void setRepeatingTestAlarm(int type, long firstTrigger, long interval, + PendingIntent pi) { + setTestAlarm(type, firstTrigger, pi, interval, TEST_CALLING_UID); + } + + private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval, + int callingUid) { + mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, interval, operation, null, "test", AlarmManager.FLAG_STANDALONE, null, null, callingUid, TEST_CALLING_PACKAGE); } + private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) { + mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, 0, + null, listener, "test", AlarmManager.FLAG_STANDALONE, null, null, + TEST_CALLING_UID, TEST_CALLING_PACKAGE); + } + + private PendingIntent getNewMockPendingIntent() { return getNewMockPendingIntent(TEST_CALLING_UID); } @@ -738,14 +756,14 @@ public class AlarmManagerServiceTest { @Test public void alarmCountKeyedOnCallingUid() { final int mockCreatorUid = 431412; - final PendingIntent pi = getNewMockPendingIntent(mockCreatorUid); - setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 5, pi); + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 5, + getNewMockPendingIntent(mockCreatorUid)); assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); assertEquals(-1, mService.mAlarmsPerUid.get(mockCreatorUid, -1)); } @Test - public void alarmCountOnSet() { + public void alarmCountOnSetPi() { final int numAlarms = 103; final int[] types = {RTC_WAKEUP, RTC, ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME}; for (int i = 1; i <= numAlarms; i++) { @@ -755,7 +773,21 @@ public class AlarmManagerServiceTest { } @Test - public void alarmCountOnExpiration() throws InterruptedException { + public void alarmCountOnSetListener() { + final int numAlarms = 103; + final int[] types = {RTC_WAKEUP, RTC, ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME}; + for (int i = 1; i <= numAlarms; i++) { + setTestAlarmWithListener(types[i % 4], mNowElapsedTest + i, new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { + } + }); + assertEquals(i, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); + } + } + + @Test + public void alarmCountOnExpirationPi() throws InterruptedException { final int numAlarms = 8; // This test is slow for (int i = 0; i < numAlarms; i++) { setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10, getNewMockPendingIntent()); @@ -770,6 +802,86 @@ public class AlarmManagerServiceTest { } @Test + public void alarmCountOnExpirationListener() throws InterruptedException { + final int numAlarms = 8; // This test is slow + for (int i = 0; i < numAlarms; i++) { + setTestAlarmWithListener(ELAPSED_REALTIME, mNowElapsedTest + i + 10, + new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) + throws RemoteException { + } + }); + } + int expired = 0; + while (expired < numAlarms) { + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + expired++; + assertEquals(numAlarms - expired, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); + } + } + + @Test + public void alarmCountOnExceptionWhileSendingPi() throws Exception { + final int numAlarms = 5; // This test is slow + for (int i = 0; i < numAlarms; i++) { + final PendingIntent pi = getNewMockPendingIntent(); + doThrow(PendingIntent.CanceledException.class).when(pi).send(eq(mMockContext), eq(0), + any(), any(), any(), any(), any()); + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10, pi); + } + int expired = 0; + while (expired < numAlarms) { + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + expired++; + assertEquals(numAlarms - expired, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); + } + } + + @Test + public void alarmCountOnExceptionWhileCallingListener() throws Exception { + final int numAlarms = 5; // This test is slow + for (int i = 0; i < numAlarms; i++) { + final IAlarmListener listener = new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { + throw new RemoteException("For testing behavior on exception"); + } + }; + setTestAlarmWithListener(ELAPSED_REALTIME, mNowElapsedTest + i + 10, listener); + } + int expired = 0; + while (expired < numAlarms) { + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + expired++; + assertEquals(numAlarms - expired, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); + } + } + + @Test + public void alarmCountForRepeatingAlarms() throws Exception { + final long interval = 1231; + final long firstTrigger = mNowElapsedTest + 321; + final PendingIntent pi = getNewMockPendingIntent(); + setRepeatingTestAlarm(ELAPSED_REALTIME, firstTrigger, interval, pi); + assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); + + for (int i = 0; i < 5; i++) { + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); + } + doThrow(PendingIntent.CanceledException.class).when(pi).send(eq(mMockContext), eq(0), + any(), any(), any(), any(), any()); + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + assertEquals(-1, mService.mAlarmsPerUid.get(TEST_CALLING_UID, -1)); + } + + @Test public void alarmCountOnUidRemoved() { final int numAlarms = 10; for (int i = 0; i < numAlarms; i++) { @@ -798,7 +910,7 @@ public class AlarmManagerServiceTest { for (int i = 0; i < numAlarms; i++) { int mockUid = UserHandle.getUid(mockUserId, 1234 + i); setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10, - getNewMockPendingIntent(mockUid), mockUid); + getNewMockPendingIntent(mockUid), 0, mockUid); } assertEquals(numAlarms, mService.mAlarmsPerUid.size()); mService.removeUserLocked(mockUserId); @@ -820,6 +932,12 @@ public class AlarmManagerServiceTest { } } + @Test + public void alarmCountOnInvalidSet() { + setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 12345, null); + assertEquals(-1, mService.mAlarmsPerUid.get(TEST_CALLING_UID, -1)); + } + @After public void tearDown() { if (mMockingSession != null) { diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index cad71a26a76b..08f6a372de86 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -722,6 +722,147 @@ public class QuotaControllerTest { assertEquals(expectedStats, newStatsRare); } + /** + * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket + * window. + */ + @Test + public void testGetTimeUntilQuotaConsumedLocked_BucketWindow() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + // Close to RARE boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS), + 30 * SECOND_IN_MILLIS, 5)); + // Far away from FREQUENT boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + // Overlap WORKING_SET boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), + 3 * MINUTE_IN_MILLIS, 5)); + // Close to ACTIVE boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + + setStandbyBucket(RARE_INDEX); + assertEquals(30 * SECOND_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + setStandbyBucket(FREQUENT_INDEX); + assertEquals(MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + setStandbyBucket(WORKING_INDEX); + assertEquals(5 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(7 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the + // max execution time. + setStandbyBucket(ACTIVE_INDEX); + assertEquals(7 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + } + + /** + * Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit. + */ + @Test + public void testGetTimeUntilQuotaConsumedLocked_MaxExecution() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + // Overlap boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession( + now - (24 * HOUR_IN_MILLIS + 8 * MINUTE_IN_MILLIS), 4 * HOUR_IN_MILLIS, 5)); + + setStandbyBucket(WORKING_INDEX); + assertEquals(8 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + // Max time will phase out, so should use bucket limit. + assertEquals(10 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); + // Close to boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (24 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS), + 4 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS, 5)); + + setStandbyBucket(WORKING_INDEX); + assertEquals(5 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(10 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); + // Far from boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession( + now - (20 * HOUR_IN_MILLIS), 4 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS, 5)); + + setStandbyBucket(WORKING_INDEX); + assertEquals(3 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(3 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + } + + /** + * Test getTimeUntilQuotaConsumedLocked when the max execution time and bucket window time + * remaining are equal. + */ + @Test + public void testGetTimeUntilQuotaConsumedLocked_EqualTimeRemaining() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + setStandbyBucket(FREQUENT_INDEX); + + // Overlap boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession( + now - (24 * HOUR_IN_MILLIS + 11 * MINUTE_IN_MILLIS), + 4 * HOUR_IN_MILLIS, + 5)); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession( + now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + + // Both max and bucket time have 8 minutes left. + assertEquals(8 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + // Max time essentially free. Bucket time has 2 min phase out plus original 8 minute + // window time. + assertEquals(10 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + + mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); + // Overlap boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession( + now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession( + now - (20 * HOUR_IN_MILLIS), + 3 * HOUR_IN_MILLIS + 48 * MINUTE_IN_MILLIS, + 5)); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession( + now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + + // Both max and bucket time have 8 minutes left. + assertEquals(8 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + // Max time only has one minute phase out. Bucket time has 2 minute phase out. + assertEquals(9 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); + } + @Test public void testIsWithinQuotaLocked_NeverApp() { assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX)); @@ -1902,7 +2043,10 @@ public class QuotaControllerTest { // window, so as the package "reaches its quota" it will have more to keep running. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - 2 * HOUR_IN_MILLIS, - 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1)); + 10 * SECOND_IN_MILLIS - remainingTimeMs, 1)); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - HOUR_IN_MILLIS, + 9 * MINUTE_IN_MILLIS + 50 * SECOND_IN_MILLIS, 1)); assertEquals(remainingTimeMs, mQuotaController.getRemainingExecutionTimeLocked(jobStatus)); // Start the job. @@ -1919,6 +2063,18 @@ public class QuotaControllerTest { // amount of remaining time left its quota. assertEquals(remainingTimeMs, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); - verify(handler, atLeast(1)).sendMessageDelayed(any(), eq(remainingTimeMs)); + // Handler is told to check when the quota will be consumed, not when the initial + // remaining time is over. + verify(handler, atLeast(1)).sendMessageDelayed(any(), eq(10 * SECOND_IN_MILLIS)); + verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs)); + + // After 10 seconds, the job should finally be out of quota. + advanceElapsedClock(10 * SECOND_IN_MILLIS - remainingTimeMs); + // Wait for some extra time to allow for job processing. + verify(mJobSchedulerService, + timeout(12 * SECOND_IN_MILLIS).times(1)) + .onControllerStateChanged(); + assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + verify(handler, never()).sendMessageDelayed(any(), anyInt()); } } diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 04abeca1192e..f39c71682c61 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -34,7 +34,9 @@ import static com.google.android.collect.Sets.newHashSet; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; @@ -64,6 +66,7 @@ import android.os.IRemoteCallback; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.os.UserHandle; import android.os.UserManagerInternal; import android.platform.test.annotations.Presubmit; import android.util.Log; @@ -321,6 +324,14 @@ public class UserControllerTest { verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(false); } + @Test + public void testExplicitSystenUserStartInBackground() { + setUpUser(UserHandle.USER_SYSTEM, 0); + assertFalse(mUserController.isSystemUserStarted()); + assertTrue(mUserController.startUser(UserHandle.USER_SYSTEM, false, null)); + assertTrue(mUserController.isSystemUserStarted()); + } + private void setUpUser(int userId, int flags) { UserInfo userInfo = new UserInfo(userId, "User" + userId, flags); when(mInjector.mUserManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo); @@ -417,6 +428,12 @@ public class UserControllerTest { @Override void reportCurWakefulnessUsageEvent() { } + + @Override + boolean isRuntimeRestarted() { + // to pass all metrics related calls + return true; + } } private static class TestHandler extends Handler { diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java index 4b3d9cf59487..0792414fef95 100644 --- a/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java +++ b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java @@ -1148,6 +1148,11 @@ public class IPackageManagerStub implements IPackageManager { return null; } + @Override + public String getAttentionServicePackageName() throws RemoteException { + return null; + } + public String getIncidentReportApproverPackageName() throws RemoteException { return null; } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 5b1970004971..cbabb0b350d1 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -100,7 +100,6 @@ import com.android.server.pm.UserRestrictionsUtils; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.File; @@ -242,29 +241,23 @@ public class DevicePolicyManagerTest extends DpmTestBase { final Map<Pair<String, UserHandle>, Bundle> appRestrictions = new HashMap<>(); // UM.setApplicationRestrictions() will save to appRestrictions. - doAnswer(new Answer<Void>() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - String pkg = (String) invocation.getArguments()[0]; - Bundle bundle = (Bundle) invocation.getArguments()[1]; - UserHandle user = (UserHandle) invocation.getArguments()[2]; + doAnswer((Answer<Void>) invocation -> { + String pkg = (String) invocation.getArguments()[0]; + Bundle bundle = (Bundle) invocation.getArguments()[1]; + UserHandle user = (UserHandle) invocation.getArguments()[2]; - appRestrictions.put(Pair.create(pkg, user), bundle); + appRestrictions.put(Pair.create(pkg, user), bundle); - return null; - } + return null; }).when(getServices().userManager).setApplicationRestrictions( anyString(), nullable(Bundle.class), any(UserHandle.class)); // UM.getApplicationRestrictions() will read from appRestrictions. - doAnswer(new Answer<Bundle>() { - @Override - public Bundle answer(InvocationOnMock invocation) throws Throwable { - String pkg = (String) invocation.getArguments()[0]; - UserHandle user = (UserHandle) invocation.getArguments()[1]; - - return appRestrictions.get(Pair.create(pkg, user)); - } + doAnswer((Answer<Bundle>) invocation -> { + String pkg = (String) invocation.getArguments()[0]; + UserHandle user = (UserHandle) invocation.getArguments()[1]; + + return appRestrictions.get(Pair.create(pkg, user)); }).when(getServices().userManager).getApplicationRestrictions( anyString(), any(UserHandle.class)); @@ -2243,11 +2236,13 @@ public class DevicePolicyManagerTest extends DpmTestBase { intent = dpm.createAdminSupportIntent(UserManager.DISALLOW_ADJUST_VOLUME); assertNull(intent); - // Permission that is set by device owner returns correct intent - when(getServices().userManager.getUserRestrictionSource( + // UM.getUserRestrictionSources() will return a list of size 1 with the caller resource. + doAnswer((Answer<List<UserManager.EnforcingUser>>) invocation -> Collections.singletonList( + new UserManager.EnforcingUser( + UserHandle.myUserId(), UserManager.RESTRICTION_SOURCE_DEVICE_OWNER)) + ).when(getServices().userManager).getUserRestrictionSources( eq(UserManager.DISALLOW_ADJUST_VOLUME), - eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) - .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER); + eq(UserHandle.getUserHandleForUid(UserHandle.myUserId()))); intent = dpm.createAdminSupportIntent(UserManager.DISALLOW_ADJUST_VOLUME); assertNotNull(intent); assertEquals(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS, intent.getAction()); diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml index aa3135ff18da..3ff85c8806ce 100644 --- a/services/tests/uiservicestests/AndroidManifest.xml +++ b/services/tests/uiservicestests/AndroidManifest.xml @@ -29,6 +29,7 @@ <uses-permission android:name="android.permission.DEVICE_POWER" /> <uses-permission android:name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" /> <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index a9eb6ec5db1e..6f1bd87f14f7 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.notification; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; +import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.NotificationManager.EXTRA_BLOCKED_STATE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; @@ -79,6 +80,7 @@ import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; @@ -92,6 +94,7 @@ import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.res.Resources; import android.graphics.Color; +import android.graphics.drawable.Icon; import android.media.AudioManager; import android.net.Uri; import android.os.Binder; @@ -101,6 +104,7 @@ import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.provider.DeviceConfig; import android.provider.MediaStore; import android.provider.Settings; @@ -133,7 +137,6 @@ import com.android.server.lights.Light; import com.android.server.lights.LightsManager; import com.android.server.notification.NotificationManagerService.NotificationAssistants; import com.android.server.notification.NotificationManagerService.NotificationListeners; -import com.android.server.pm.UserManagerService; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -149,7 +152,6 @@ import org.mockito.stubbing.Answer; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Arrays; @@ -226,23 +228,21 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock AppOpsManager mAppOpsManager; @Mock - private UserManagerService mUserMangerService; - @Mock private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback; + @Mock + UserManager mUm; // Use a Testable subclass so we can simulate calls from the system without failing. private static class TestableNotificationManagerService extends NotificationManagerService { int countSystemChecks = 0; boolean isSystemUid = true; int countLogSmartSuggestionsVisible = 0; - UserManagerService mUserManagerService; @Nullable NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback; - TestableNotificationManagerService(Context context, UserManagerService userManagerService) { + TestableNotificationManagerService(Context context) { super(context); - mUserManagerService = userManagerService; } @Override @@ -279,11 +279,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Override - UserManagerService getUserManagerService() { - return mUserManagerService; - } - - @Override protected void setNotificationAssistantAccessGrantedForUserInternal( ComponentName assistant, int userId, boolean granted) { if (mNotificationAssistantAccessGrantedCallback != null) { @@ -326,7 +321,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal); - mService = new TestableNotificationManagerService(mContext, mUserMangerService); + mService = new TestableNotificationManagerService(mContext); // Use this testable looper. mTestableLooper = TestableLooper.get(this); @@ -379,7 +374,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal, - mAppOpsManager); + mAppOpsManager, mUm); mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); } catch (SecurityException e) { if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { @@ -507,6 +502,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { false); } + private Notification.BubbleMetadata.Builder getBasicBubbleMetadataBuilder() { + PendingIntent pi = PendingIntent.getActivity(mContext, 0, new Intent(), 0); + return new Notification.BubbleMetadata.Builder() + .setIntent(pi) + .setIcon(Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon)); + } + @Test public void testCreateNotificationChannels_SingleChannel() throws Exception { final NotificationChannel channel = @@ -1920,8 +1922,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testHasCompanionDevice_noService() throws Exception { - mService = new TestableNotificationManagerService(mContext, mUserMangerService); + public void testHasCompanionDevice_noService() { + mService = new TestableNotificationManagerService(mContext); assertFalse(mService.hasCompanionDevice(mListener)); } @@ -2623,7 +2625,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />" + "</dnd_apps>" + "</notification-policy>"; - when(mUserMangerService.isManagedProfile(10)).thenReturn(true); + when(mUm.isManagedProfile(10)).thenReturn(true); mService.readPolicyXml( new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())), true, @@ -2647,7 +2649,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />" + "</dnd_apps>" + "</notification-policy>"; - when(mUserMangerService.isManagedProfile(10)).thenReturn(false); + when(mUm.isManagedProfile(10)).thenReturn(false); mService.readPolicyXml( new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())), true, @@ -4290,7 +4292,209 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .onGranted(eq(xmlConfig), eq(0), eq(true)); } + @Test + public void testFlagBubbleNotifs_flagIfAllowed() throws RemoteException { + // Bubbles are allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // Notif with bubble metadata + Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setBubbleMetadata(data) + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // yes allowed, yes bubble + assertTrue(mService.getNotificationRecord( + sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test + public void testFlagBubbleNotifs_noFlagIfNotAllowed() throws RemoteException { + // Bubbles are NOT allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(false); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // Notif with bubble metadata + Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setBubbleMetadata(data) + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + // Post the notification + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // not allowed, no bubble + assertFalse(mService.getNotificationRecord( + sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test + public void testFlagBubbleNotifs_noFlagIfNotBubble() throws RemoteException { + // Bubbles are allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // Notif WITHOUT bubble metadata + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + // Post the notification + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // no bubble metadata, no bubble + assertFalse(mService.getNotificationRecord( + sbn.getKey()).getNotification().isBubbleNotification()); + } + @Test + public void testFlagBubbleNotifs_noFlagIfChannelNotBubble() throws RemoteException { + // Bubbles are allowed! + mService.setPreferencesHelper(mPreferencesHelper); + when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true); + when(mPreferencesHelper.getNotificationChannel( + anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + mTestNotificationChannel); + when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( + mTestNotificationChannel.getImportance()); + + // But not on this channel! + mTestNotificationChannel.setAllowBubbles(false); + + // Notif with bubble metadata + Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setBubbleMetadata(data) + .setSmallIcon(android.R.drawable.sym_def_app_icon); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + // Post the notification + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // channel not allowed, no bubble + assertFalse(mService.getNotificationRecord( + sbn.getKey()).getNotification().isBubbleNotification()); + } + + @Test + public void testCancelAllNotifications_cancelsBubble() throws Exception { + final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel); + nr.sbn.getNotification().flags |= FLAG_BUBBLE; + mService.addNotification(nr); + + mBinderService.cancelAllNotifications(PKG, nr.sbn.getUserId()); + waitForIdle(); + + StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); + assertEquals(0, notifs.length); + assertEquals(0, mService.getNotificationRecordCount()); + } + + @Test + public void testAppCancelNotifications_cancelsBubbles() throws Exception { + final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel); + nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE; + + // Post the notification + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId()); + waitForIdle(); + + StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); + assertEquals(1, notifs.length); + assertEquals(1, mService.getNotificationRecordCount()); + + mBinderService.cancelNotificationWithTag(PKG, null, nrBubble.sbn.getId(), + nrBubble.sbn.getUserId()); + waitForIdle(); + + StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG); + assertEquals(0, notifs2.length); + assertEquals(0, mService.getNotificationRecordCount()); + } + + @Test + public void testCancelAllNotificationsFromListener_ignoresBubbles() throws Exception { + final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel); + final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel); + nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE; + + mService.addNotification(nrNormal); + mService.addNotification(nrBubble); + + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + + StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); + assertEquals(1, notifs.length); + assertEquals(1, mService.getNotificationRecordCount()); + } + + @Test + public void testCancelNotificationsFromListener_ignoresBubbles() throws Exception { + final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel); + final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel); + nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE; + + mService.addNotification(nrNormal); + mService.addNotification(nrBubble); + + String[] keys = {nrNormal.sbn.getKey(), nrBubble.sbn.getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + + StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); + assertEquals(1, notifs.length); + assertEquals(1, mService.getNotificationRecordCount()); + } public void testGetAllowedAssistantCapabilities() throws Exception { List<String> capabilities = mBinderService.getAllowedAssistantCapabilities(null); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index e375195af5c9..7c2235050caf 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -477,8 +477,9 @@ public class NotificationRecordTest extends UiServiceTestCase { assertEquals(MetricsEvent.IMPORTANCE_EXPLANATION_APP, logMaker.getTaggedData( MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_INITIAL_EXPLANATION)); - // This field is only populated if the assistant was itself overridden by the system. - assertNull(logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_ASST)); + // This field is populated whenever mImportanceExplanationCode is. + assertEquals(IMPORTANCE_LOW, + logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_ASST)); } @Test @@ -952,7 +953,31 @@ public class NotificationRecordTest extends UiServiceTestCase { } @Test - public void testApplyImportanceAdjustmentsForNonOemLockedChannels() { + public void testIgnoreImportanceAdjustmentsForDefaultAppLockedChannels() { + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT); + channel.setImportanceLockedByCriticalDeviceFunction(true); + + StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */, + true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, + false /* lights */, false /* defaultLights */, groupId /* group */); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + + assertEquals(IMPORTANCE_DEFAULT, record.getImportance()); + + Bundle bundle = new Bundle(); + bundle.putInt(KEY_IMPORTANCE, IMPORTANCE_LOW); + Adjustment adjustment = new Adjustment( + PKG_O, record.getKey(), bundle, "", record.getUserId()); + + record.addAdjustment(adjustment); + record.applyAdjustments(); + record.calculateImportance(); + + assertEquals(IMPORTANCE_DEFAULT, record.getImportance()); + } + + @Test + public void testApplyImportanceAdjustmentsForNonOemDefaultAppLockedChannels() { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT); channel.setImportanceLockedByOEM(false); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 39e47ecb1b22..87f10a4e077c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -62,11 +62,9 @@ import android.provider.Settings.Secure; import android.test.suitebuilder.annotation.SmallTest; import android.testing.TestableContentResolver; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Xml; -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - import com.android.internal.util.FastXmlSerializer; import com.android.server.UiServiceTestCase; @@ -91,6 +89,9 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + @SmallTest @RunWith(AndroidJUnit4.class) public class PreferencesHelperTest extends UiServiceTestCase { @@ -2442,4 +2443,153 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(IMPORTANCE_HIGH, mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance()); } + + @Test + public void testUpdateDefaultApps_add_multiUser() { + NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); + NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT); + // different uids, same package + mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); + mHelper.createNotificationChannel(PKG_O, UID_O, b, false, false); + mHelper.createNotificationChannel(PKG_O, UserHandle.PER_USER_RANGE + 1, c, true, true); + + ArraySet<String> toAdd = new ArraySet<>(); + toAdd.add(PKG_O); + mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd); + + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + assertFalse(mHelper.getNotificationChannel( + PKG_O, UserHandle.PER_USER_RANGE + 1, c.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + } + + @Test + public void testUpdateDefaultApps_add_onlyGivenPkg() { + NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); + mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, b, false, false); + + ArraySet<String> toAdd = new ArraySet<>(); + toAdd.add(PKG_O); + mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd); + + + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, b.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + } + + @Test + public void testUpdateDefaultApps_remove() { + NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); + // different uids, same package + mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); + mHelper.createNotificationChannel(PKG_O, UID_O, b, false, false); + + ArraySet<String> toAdd = new ArraySet<>(); + toAdd.add(PKG_O); + mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd); + + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + + ArraySet<String> toRemove = new ArraySet<>(); + toRemove.add(PKG_O); + mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), toRemove, null); + + assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + } + + @Test + public void testUpdateDefaultApps_addAndRemove() { + NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); + mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, b, false, false); + + ArraySet<String> toAdd = new ArraySet<>(); + toAdd.add(PKG_O); + mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd); + + + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, b.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + + // now the default is PKG_N_MR1 + ArraySet<String> toRemove = new ArraySet<>(); + toRemove.add(PKG_O); + toAdd = new ArraySet<>(); + toAdd.add(PKG_N_MR1); + mHelper.updateDefaultApps(USER.getIdentifier(), toRemove, toAdd); + + assertFalse(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + assertTrue(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, b.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + } + + @Test + public void testUpdateDefaultApps_appDoesNotExist_noCrash() { + ArraySet<String> toAdd = new ArraySet<>(); + toAdd.add(PKG_O); + ArraySet<String> toRemove = new ArraySet<>(); + toRemove.add(PKG_N_MR1); + mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), toRemove, toAdd); + } + + @Test + public void testUpdateDefaultApps_channelDoesNotExistYet() { + NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); + mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); + + ArraySet<String> toAdd = new ArraySet<>(); + toAdd.add(PKG_O); + mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd); + + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + + mHelper.createNotificationChannel(PKG_O, UID_O, b, true, false); + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false) + .isImportanceLockedByCriticalDeviceFunction()); + } + + @Test + public void testUpdateNotificationChannel_defaultAppLockedImportance() { + NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); + ArraySet<String> toAdd = new ArraySet<>(); + toAdd.add(PKG_O); + mHelper.updateDefaultApps(UserHandle.getUserId(UID_O), null, toAdd); + + NotificationChannel update = new NotificationChannel("a", "a", IMPORTANCE_NONE); + update.setAllowBubbles(false); + + mHelper.updateNotificationChannel(PKG_O, UID_O, update, true); + + assertEquals(IMPORTANCE_HIGH, + mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance()); + assertEquals(false, + mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).canBubble()); + + mHelper.updateNotificationChannel(PKG_O, UID_O, update, false); + + assertEquals(IMPORTANCE_HIGH, + mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance()); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java new file mode 100644 index 000000000000..91d3e5e4d7f6 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; +import static android.app.Notification.FLAG_FOREGROUND_SERVICE; +import static android.app.NotificationManager.EXTRA_BLOCKED_STATE; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_MAX; +import static android.app.NotificationManager.IMPORTANCE_NONE; +import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; +import static android.app.role.RoleManager.ROLE_DIALER; +import static android.app.role.RoleManager.ROLE_EMERGENCY; +import static android.app.role.RoleManager.ROLE_SMS; +import static android.content.pm.PackageManager.FEATURE_WATCH; +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Build.VERSION_CODES.O_MR1; +import static android.os.Build.VERSION_CODES.P; +import static android.service.notification.Adjustment.KEY_IMPORTANCE; +import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; +import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.app.IActivityManager; +import android.app.INotificationManager; +import android.app.ITransientNotification; +import android.app.IUriGrantsManager; +import android.app.Notification; +import android.app.Notification.MessagingStyle.Message; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.app.admin.DevicePolicyManagerInternal; +import android.app.role.RoleManager; +import android.app.usage.UsageStatsManagerInternal; +import android.companion.ICompanionDeviceManager; +import android.content.ComponentName; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; +import android.content.pm.UserInfo; +import android.graphics.Color; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Looper; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.DeviceConfig; +import android.provider.MediaStore; +import android.provider.Settings; +import android.service.notification.Adjustment; +import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationStats; +import android.service.notification.NotifyingApp; +import android.service.notification.StatusBarNotification; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; +import android.testing.TestablePermissions; +import android.text.Html; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.AtomicFile; + +import com.android.internal.R; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; +import com.android.internal.statusbar.NotificationVisibility; +import com.android.server.LocalServices; +import com.android.server.UiServiceTestCase; +import com.android.server.lights.Light; +import com.android.server.lights.LightsManager; +import com.android.server.notification.NotificationManagerService.NotificationAssistants; +import com.android.server.notification.NotificationManagerService.NotificationListeners; +import com.android.server.uri.UriGrantsManagerInternal; +import com.android.server.wm.WindowManagerInternal; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +import androidx.annotation.Nullable; +import androidx.test.InstrumentationRegistry; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class RoleObserverTest extends UiServiceTestCase { + private TestableNotificationManagerService mService; + private NotificationManagerService.RoleObserver mRoleObserver; + + private TestableContext mContext = spy(getContext()); + + @Mock + private PreferencesHelper mPreferencesHelper; + @Mock + private UserManager mUm; + @Mock + private Executor mExecutor; + @Mock + private RoleManager mRoleManager; + + private List<UserInfo> mUsers; + + private static class TestableNotificationManagerService extends NotificationManagerService { + + TestableNotificationManagerService(Context context) { + super(context); + } + + @Override + protected void handleSavePolicyFile() { + return; + } + + @Override + protected void loadPolicyFile() { + return; + } + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + LocalServices.removeServiceForTest(WindowManagerInternal.class); + LocalServices.addService(WindowManagerInternal.class, mock(WindowManagerInternal.class)); + + mUsers = new ArrayList<>(); + mUsers.add(new UserInfo(0, "system", 0)); + mUsers.add(new UserInfo(10, "second", 0)); + when(mUm.getUsers()).thenReturn(mUsers); + + mService = new TestableNotificationManagerService(mContext); + mRoleObserver = mService.new RoleObserver(mRoleManager, mExecutor); + + try { + mService.init(mock(Looper.class), + mock(IPackageManager.class), mock(PackageManager.class), + mock(LightsManager.class), + mock(NotificationListeners.class), mock(NotificationAssistants.class), + mock(ConditionProviders.class), mock(ICompanionDeviceManager.class), + mock(SnoozeHelper.class), mock(NotificationUsageStats.class), + mock(AtomicFile.class), mock(ActivityManager.class), + mock(GroupHelper.class), mock(IActivityManager.class), + mock(UsageStatsManagerInternal.class), + mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class), + mock(UriGrantsManagerInternal.class), + mock(AppOpsManager.class), mUm); + } catch (SecurityException e) { + if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { + throw e; + } + } + mService.setPreferencesHelper(mPreferencesHelper); + } + + @Test + public void testInit() { + List<String> dialer0 = new ArrayList<>(); + dialer0.add("dialer"); + List<String> emer0 = new ArrayList<>(); + emer0.add("emergency"); + List<String> sms10 = new ArrayList<>(); + sms10.add("sms"); + when(mRoleManager.getRoleHoldersAsUser( + ROLE_DIALER, + mUsers.get(0).getUserHandle())). + thenReturn(dialer0); + when(mRoleManager.getRoleHoldersAsUser( + ROLE_EMERGENCY, + mUsers.get(0).getUserHandle())). + thenReturn(emer0); + when(mRoleManager.getRoleHoldersAsUser( + ROLE_SMS, + mUsers.get(1).getUserHandle())). + thenReturn(sms10); + + mRoleObserver.init(); + + // verify internal records of current state of the world + assertTrue(mRoleObserver.isApprovedPackageForRoleForUser( + ROLE_DIALER, dialer0.get(0), mUsers.get(0).id)); + assertFalse(mRoleObserver.isApprovedPackageForRoleForUser( + ROLE_DIALER, dialer0.get(0), mUsers.get(1).id)); + assertFalse(mRoleObserver.isApprovedPackageForRoleForUser( + ROLE_SMS, dialer0.get(0), mUsers.get(1).id)); + + assertTrue(mRoleObserver.isApprovedPackageForRoleForUser( + ROLE_EMERGENCY, emer0.get(0), mUsers.get(0).id)); + assertFalse(mRoleObserver.isApprovedPackageForRoleForUser( + ROLE_EMERGENCY, emer0.get(0), mUsers.get(1).id)); + + assertFalse(mRoleObserver.isApprovedPackageForRoleForUser( + ROLE_SMS, sms10.get(0), mUsers.get(0).id)); + assertFalse(mRoleObserver.isApprovedPackageForRoleForUser( + ROLE_DIALER, sms10.get(0), mUsers.get(0).id)); + assertTrue(mRoleObserver.isApprovedPackageForRoleForUser( + ROLE_SMS, sms10.get(0), mUsers.get(1).id)); + + // make sure we're listening to updates + verify(mRoleManager, times(1)).addOnRoleHoldersChangedListenerAsUser( + eq(mExecutor), any(), eq(UserHandle.ALL)); + + // make sure we told pref helper about the state of the world + verify(mPreferencesHelper, times(1)).updateDefaultApps(0, null, new ArraySet<>(dialer0)); + verify(mPreferencesHelper, times(1)).updateDefaultApps(0, null, new ArraySet<>(emer0)); + verify(mPreferencesHelper, times(1)).updateDefaultApps(10, null, new ArraySet<>(sms10)); + } + + @Test + public void testSwapDefault() { + List<String> dialer0 = new ArrayList<>(); + dialer0.add("dialer"); + + when(mRoleManager.getRoleHoldersAsUser( + ROLE_DIALER, + mUsers.get(0).getUserHandle())). + thenReturn(dialer0); + + mRoleObserver.init(); + + List<String> newDefault = new ArrayList<>(); + newDefault.add("phone"); + + when(mRoleManager.getRoleHoldersAsUser( + ROLE_DIALER, + mUsers.get(0).getUserHandle())). + thenReturn(newDefault); + + mRoleObserver.onRoleHoldersChanged(ROLE_DIALER, UserHandle.of(0)); + + verify(mPreferencesHelper, times(1)).updateDefaultApps( + 0, new ArraySet<>(dialer0), new ArraySet<>(newDefault)); + } + + @Test + public void testSwapDefault_multipleOverlappingApps() { + List<String> dialer0 = new ArrayList<>(); + dialer0.add("dialer"); + dialer0.add("phone"); + + when(mRoleManager.getRoleHoldersAsUser( + ROLE_DIALER, + mUsers.get(0).getUserHandle())). + thenReturn(dialer0); + + mRoleObserver.init(); + + assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "phone", 0)); + assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "emerPhone", 0)); + assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "dialer", 0)); + + List<String> newDefault = new ArrayList<>(); + newDefault.add("phone"); + newDefault.add("emerPhone"); + + when(mRoleManager.getRoleHoldersAsUser( + ROLE_DIALER, + mUsers.get(0).getUserHandle())). + thenReturn(newDefault); + + mRoleObserver.onRoleHoldersChanged(ROLE_DIALER, UserHandle.of(0)); + + ArraySet<String> expectedRemove = new ArraySet<>(); + expectedRemove.add("dialer"); + ArraySet<String> expectedAdd = new ArraySet<>(); + expectedAdd.add("emerPhone"); + + verify(mPreferencesHelper, times(1)).updateDefaultApps( + 0, expectedRemove, expectedAdd); + + assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "phone", 0)); + assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "emerPhone", 0)); + assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "dialer", 0)); + } + + @Test + public void testSwapDefault_newUser() { + List<String> dialer0 = new ArrayList<>(); + dialer0.add("dialer"); + + when(mRoleManager.getRoleHoldersAsUser( + ROLE_DIALER, + mUsers.get(0).getUserHandle())). + thenReturn(dialer0); + + mRoleObserver.init(); + + List<String> dialer10 = new ArrayList<>(); + dialer10.add("phone"); + + when(mRoleManager.getRoleHoldersAsUser( + ROLE_DIALER, + mUsers.get(1).getUserHandle())). + thenReturn(dialer10); + + mRoleObserver.onRoleHoldersChanged(ROLE_DIALER, UserHandle.of(10)); + + ArraySet<String> expectedRemove = new ArraySet<>(); + ArraySet<String> expectedAdd = new ArraySet<>(); + expectedAdd.add("phone"); + + verify(mPreferencesHelper, times(1)).updateDefaultApps( + 10, expectedRemove, expectedAdd); + + assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "phone", 10)); + assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "dialer", 0)); + } +} 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 d580557638cf..32e96a592207 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -438,8 +438,11 @@ public class ActivityRecordTests extends ActivityTestsBase { doReturn(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) .when(mActivity.mAppWindowToken).getOrientationIgnoreVisibility(); mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; - mActivity.info.maxAspectRatio = 1; + mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1; ensureActivityConfiguration(); + // The parent configuration doesn't change since the first resolved configuration, so the + // activity shouldn't be in the size compatibility mode. + assertFalse(mActivity.inSizeCompatMode()); final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds(); // Ensure the app bounds keep the declared aspect ratio. diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index 822700f48dd5..1e00b307c143 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -31,6 +31,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING; +import static com.android.server.wm.ActivityStack.ActivityState.FINISHING; import static com.android.server.wm.ActivityStack.ActivityState.PAUSED; import static com.android.server.wm.ActivityStack.ActivityState.PAUSING; import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; @@ -929,6 +930,16 @@ public class ActivityStackTests extends ActivityTestsBase { } @Test + public void testWontFinishHomeStackImmediately() { + final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + + // Home stack should not be destroyed immediately. + final ActivityRecord activity1 = finishCurrentActivity(homeStack); + assertEquals(FINISHING, activity1.getState()); + } + + @Test public void testFinishCurrentActivity() { // Create 2 activities on a new display. final ActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java index 85b2f7b41e67..5cef38ded575 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java @@ -38,6 +38,7 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Matrix; import android.graphics.RectF; +import android.os.Binder; import android.os.IBinder; import android.testing.TestableResources; import android.util.Pair; @@ -86,7 +87,6 @@ public class DisplayPolicyTestsBase extends WindowTestsBase { Math.min(DISPLAY_WIDTH, DISPLAY_HEIGHT) * DENSITY_DEFAULT / DISPLAY_DENSITY; mDisplayContent.getDisplayRotation().configure( DISPLAY_WIDTH, DISPLAY_HEIGHT, shortSizeDp, longSizeDp); - mDisplayPolicy.configure(DISPLAY_WIDTH, DISPLAY_HEIGHT, shortSizeDp); mDisplayPolicy.onConfigurationChanged(); mStatusBarWindow.mAttrs.gravity = Gravity.TOP; @@ -99,7 +99,8 @@ public class DisplayPolicyTestsBase extends WindowTestsBase { } void addWindow(WindowState win) { - mDisplayPolicy.adjustWindowParamsLw(win, win.mAttrs, true /* hasStatusBarPermission */); + mDisplayPolicy.adjustWindowParamsLw(win, win.mAttrs, Binder.getCallingPid(), + Binder.getCallingUid()); assertEquals(WindowManagerGlobal.ADD_OKAY, mDisplayPolicy.prepareAddWindowLw(win, win.mAttrs)); win.mHasSurface = true; diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java index 88ac96d74df6..4f03726e7631 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java @@ -181,15 +181,10 @@ public class SurfaceAnimatorTest extends WindowTestsBase { public void testDeferFinish() { // Start animation - mDeferFinishAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, - true /* hidden */); - final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass( - OnAnimationFinishedCallback.class); - assertAnimating(mDeferFinishAnimatable); - verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); + final OnAnimationFinishedCallback onFinishedCallback = startDeferFinishAnimatable(mSpec); // Finish the animation but then make sure we are deferring. - callbackCaptor.getValue().onAnimationFinished(mSpec); + onFinishedCallback.onAnimationFinished(mSpec); assertAnimating(mDeferFinishAnimatable); // Now end defer finishing. @@ -199,6 +194,36 @@ public class SurfaceAnimatorTest extends WindowTestsBase { verify(mTransaction).remove(eq(mDeferFinishAnimatable.mLeash)); } + @Test + public void testDeferFinishDoNotFinishNextAnimation() { + // Start the first animation. + final OnAnimationFinishedCallback onFinishedCallback = startDeferFinishAnimatable(mSpec); + onFinishedCallback.onAnimationFinished(mSpec); + // The callback is the resetAndInvokeFinish in {@link SurfaceAnimator#getFinishedCallback}. + final Runnable firstDeferFinishCallback = mDeferFinishAnimatable.mEndDeferFinishCallback; + + // Start the second animation. + mDeferFinishAnimatable.mSurfaceAnimator.cancelAnimation(); + startDeferFinishAnimatable(mSpec2); + mDeferFinishAnimatable.mFinishedCallbackCalled = false; + + // Simulate the first deferred callback is executed from + // {@link AnimatingAppWindowTokenRegistry#endDeferringFinished}. + firstDeferFinishCallback.run(); + // The second animation should not be finished. + assertFalse(mDeferFinishAnimatable.mFinishedCallbackCalled); + } + + private OnAnimationFinishedCallback startDeferFinishAnimatable(AnimationAdapter anim) { + mDeferFinishAnimatable.mSurfaceAnimator.startAnimation(mTransaction, anim, + true /* hidden */); + final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass( + OnAnimationFinishedCallback.class); + assertAnimating(mDeferFinishAnimatable); + verify(anim).startAnimation(any(), any(), callbackCaptor.capture()); + return callbackCaptor.getValue(); + } + private void assertAnimating(MyAnimatable animatable) { assertTrue(animatable.mSurfaceAnimator.isAnimating()); assertNotNull(animatable.mSurfaceAnimator.getAnimation()); diff --git a/startop/iorap/tests/Android.bp b/startop/iorap/tests/Android.bp index 4359978f5814..3e60ad448cae 100644 --- a/startop/iorap/tests/Android.bp +++ b/startop/iorap/tests/Android.bp @@ -16,32 +16,48 @@ java_library { name: "libiorap-java-test-lib", srcs: ["src/**/*.kt"], - static_libs: [ - // Non-test dependencies - - // library under test - "services.startop.iorap", - // need the system_server code to be on the classpath, - "services.core", - - // Test Dependencies - - // test android dependencies - "platform-test-annotations", - "androidx.test.rules", - // test framework dependencies - "mockito-target-inline-minus-junit4", - // "mockito-target-minus-junit4", + // Non-test dependencies + // library under test + "services.startop.iorap", + // need the system_server code to be on the classpath, + "services.core", + // Test Dependencies + // test android dependencies + "platform-test-annotations", + "androidx.test.rules", + // test framework dependencies + "mockito-target-inline-minus-junit4", + // "mockito-target-minus-junit4", // Mockito also requires JNI (see Android.mk) // and android:debuggable=true (see AndroidManifest.xml) - "truth-prebuilt", + "truth-prebuilt", ], - // sdk_version: "current", // certificate: "platform", - - libs: ["android.test.base", "android.test.runner"], - + libs: [ + "android.test.base", + "android.test.runner", + ], // test_suites: ["device-tests"], } + +android_test { + name: "libiorap-java-tests", + dxflags: ["--multi-dex"], + test_suites: ["device-tests"], + static_libs: ["libiorap-java-test-lib"], + compile_multilib: "both", + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + "libmultiplejvmtiagentsinterferenceagent", + ], + libs: [ + "android.test.base", + "android.test.runner", + ], + // Use private APIs + certificate: "platform", + platform_apis: true, +} diff --git a/startop/iorap/tests/Android.mk b/startop/iorap/tests/Android.mk deleted file mode 100644 index fa8c8b5fba89..000000000000 --- a/startop/iorap/tests/Android.mk +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (C) 2018 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. - -# android_test does not support JNI libraries -# TODO: once b/80095087 is fixed, rewrite this back to android_test -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_JACK_FLAGS := --multi-dex native -LOCAL_DX_FLAGS := --multi-dex - -LOCAL_PACKAGE_NAME := libiorap-java-tests -LOCAL_COMPATIBILITY_SUITE := device-tests - -LOCAL_STATIC_JAVA_LIBRARIES := \ - libiorap-java-test-lib - -LOCAL_MULTILIB := both - -LOCAL_JNI_SHARED_LIBRARIES := \ - libdexmakerjvmtiagent \ - libstaticjvmtiagent \ - libmultiplejvmtiagentsinterferenceagent - -LOCAL_JAVA_LIBRARIES := \ - android.test.base \ - android.test.runner - -# Use private APIs -LOCAL_CERTIFICATE := platform -LOCAL_PRIVATE_PLATFORM_APIS := true - -# Disable presubmit test until it works with disabled iorap by default. -LOCAL_PRESUBMIT_DISABLED := true - -include $(BUILD_PACKAGE) diff --git a/telephony/java/android/telephony/AvailableNetworkInfo.java b/telephony/java/android/telephony/AvailableNetworkInfo.java index a15f959a96bd..a1c5bbefbbe1 100644 --- a/telephony/java/android/telephony/AvailableNetworkInfo.java +++ b/telephony/java/android/telephony/AvailableNetworkInfo.java @@ -27,8 +27,8 @@ import java.util.Objects; /** * Defines available network information which includes corresponding subscription id, - * network plmns and corresponding priority to be used for network selection by Alternative Network - * Service. + * network plmns and corresponding priority to be used for network selection by Opportunistic + * Network Service when passed through {@link TelephonyManager#updateAvailableNetworks} */ public final class AvailableNetworkInfo implements Parcelable { @@ -55,15 +55,19 @@ public final class AvailableNetworkInfo implements Parcelable { /** * Priority for the subscription id. - * Priorities are in the range of 1 to 3 where 1 - * has the highest priority. + * Priorities are in the range of {@link AvailableNetworkInfo#PRIORITY_LOW} to + * {@link AvailableNetworkInfo#PRIORITY_HIGH} + * Among all networks available after network scan, subId with highest priority is chosen + * for network selection. If there are more than one subId with highest priority then the + * network with highest RSRP is chosen. */ private int mPriority; /** * Describes the List of PLMN ids (MCC-MNC) associated with mSubId. - * If this entry is left empty, then the platform software will not scan the network - * to revalidate the input else platform will scan and verify specified PLMNs are available. + * Opportunistic Network Service will scan and verify specified PLMNs are available. + * If this entry is left empty, then the Opportunistic Network Service will not scan the network + * to validate the network availability. */ private ArrayList<String> mMccMncs; @@ -71,8 +75,8 @@ public final class AvailableNetworkInfo implements Parcelable { * Returns the frequency bands associated with the {@link #getMccMncs() MCC/MNCs}. * Opportunistic network service will use these bands to scan. * - * When no specific bands are specified (empty array or null) CBRS band (B48) will be - * used for network scan. + * When no specific bands are specified (empty array or null) CBRS band + * {@link AccessNetworkConstants.EutranBand.BAND_48} will be used for network scan. * * See {@link AccessNetworkConstants} for details. */ @@ -89,8 +93,12 @@ public final class AvailableNetworkInfo implements Parcelable { } /** - * Return priority for the subscription id. Valid value will be within - * [{@link AvailableNetworkInfo#PRIORITY_HIGH}, {@link AvailableNetworkInfo#PRIORITY_LOW}] + * Return priority for the subscription id. + * Priorities are in the range of {@link AvailableNetworkInfo#PRIORITY_LOW} to + * {@link AvailableNetworkInfo#PRIORITY_HIGH} + * Among all networks available after network scan, subId with highest priority is chosen + * for network selection. If there are more than one subId with highest priority then the + * network with highest RSRP is chosen. * @return priority level */ public int getPriority() { @@ -99,8 +107,9 @@ public final class AvailableNetworkInfo implements Parcelable { /** * Return List of PLMN ids (MCC-MNC) associated with the sub ID. - * If this entry is left empty, then the platform software will not scan the network - * to revalidate the input. + * Opportunistic Network Service will scan and verify specified PLMNs are available. + * If this entry is left empty, then the Opportunistic Network Service will not scan the network + * to validate the network availability. * @return list of PLMN ids */ public @NonNull List<String> getMccMncs() { @@ -112,6 +121,9 @@ public final class AvailableNetworkInfo implements Parcelable { * * The returned value is defined in either of {@link AccessNetworkConstants.GeranBand}, * {@link AccessNetworkConstants.UtranBand} and {@link AccessNetworkConstants.EutranBand} + * See {@link AccessNetworkConstants.AccessNetworkType} for details regarding different network + * types. When no specific bands are specified (empty array or null) CBRS band + * {@link AccessNetworkConstants.EutranBand#BAND_48} will be used for network scan. */ public @NonNull List<Integer> getBands() { return (List<Integer>) mBands.clone(); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index fffa935ff3c5..0b44367e1935 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2571,6 +2571,22 @@ public class CarrierConfigManager { "emergency_number_prefix_string_array"; /** + * Smart forwarding config. Smart forwarding is a feature to configure call forwarding to a + * different SIM in the device when one SIM is not reachable. The config here specifies a smart + * forwarding component that will launch UI for changing the configuration. An empty string + * indicates that no smart forwarding component is specified. + * + * Currently, only one non-empty configuration of smart forwarding component within system will + * be used when multiple SIMs are inserted. + * + * Empty string by default. + * + * @hide + */ + public static final String KEY_SMART_FORWARDING_CONFIG_COMPONENT_NAME_STRING = + "smart_forwarding_config_component_name_string"; + + /** * Indicates when a carrier has a primary subscription and an opportunistic subscription active, * and when Internet data is switched to opportunistic network, whether to still show * signal bar of primary network. By default it will be false, meaning whenever data @@ -3130,6 +3146,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_USE_USIM_BOOL, false); sDefaults.putBoolean(KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL, true); sDefaults.putBoolean(KEY_AUTO_CANCEL_CS_REJECT_NOTIFICATION, false); + sDefaults.putString(KEY_SMART_FORWARDING_CONFIG_COMPONENT_NAME_STRING, ""); sDefaults.putBoolean(KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN, false); } diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index 918bf60c9fa7..373c5d27eec8 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -297,8 +297,11 @@ public class PhoneStateListener { * it could be the current active opportunistic subscription in use, or the * subscription user selected as default data subscription in DSDS mode. * - * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE - * READ_PHONE_STATE} + * Requires Permission: No permission is required to listen, but notification requires + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or the calling + * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}) + * on any active subscription. + * * @see #onActiveDataSubscriptionIdChanged */ public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 0x00400000; diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 09046a679c37..63d427a6f3c4 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -951,8 +951,7 @@ public final class SmsManager { * @return associated subscription id */ public int getSubscriptionId() { - final int subId = (mSubId == DEFAULT_SUBSCRIPTION_ID) - ? getDefaultSmsSubscriptionId() : mSubId; + final int subId = getSubIdOrDefault(); boolean isSmsSimPickActivityNeeded = false; final Context context = ActivityThread.currentApplication().getApplicationContext(); try { @@ -985,6 +984,17 @@ public final class SmsManager { } /** + * @return the subscription ID associated with this {@link SmsManager} or the default set by the + * user if this instance was created using {@link SmsManager#getDefault}. + * + * If there is no default set by the user, this method returns + * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}. + */ + private int getSubIdOrDefault() { + return (mSubId == DEFAULT_SUBSCRIPTION_ID) ? getDefaultSmsSubscriptionId() : mSubId; + } + + /** * Returns the ISms service, or throws an UnsupportedOperationException if * the service does not exist. */ @@ -1151,8 +1161,9 @@ public final class SmsManager { try { ISms iSms = getISmsService(); if (iSms != null) { - success = iSms.enableCellBroadcastForSubscriber( - getSubscriptionId(), messageIdentifier, ranType); + // If getSubIdOrDefault() returns INVALID, we will use the default phone internally. + success = iSms.enableCellBroadcastForSubscriber(getSubIdOrDefault(), + messageIdentifier, ranType); } } catch (RemoteException ex) { // ignore it @@ -1187,8 +1198,9 @@ public final class SmsManager { try { ISms iSms = getISmsService(); if (iSms != null) { - success = iSms.disableCellBroadcastForSubscriber( - getSubscriptionId(), messageIdentifier, ranType); + // If getSubIdOrDefault() returns INVALID, we will use the default phone internally. + success = iSms.disableCellBroadcastForSubscriber(getSubIdOrDefault(), + messageIdentifier, ranType); } } catch (RemoteException ex) { // ignore it @@ -1230,7 +1242,8 @@ public final class SmsManager { try { ISms iSms = getISmsService(); if (iSms != null) { - success = iSms.enableCellBroadcastRangeForSubscriber(getSubscriptionId(), + // If getSubIdOrDefault() returns INVALID, we will use the default phone internally. + success = iSms.enableCellBroadcastRangeForSubscriber(getSubIdOrDefault(), startMessageId, endMessageId, ranType); } } catch (RemoteException ex) { @@ -1273,7 +1286,8 @@ public final class SmsManager { try { ISms iSms = getISmsService(); if (iSms != null) { - success = iSms.disableCellBroadcastRangeForSubscriber(getSubscriptionId(), + // If getSubIdOrDefault() returns INVALID, we will use the default phone internally. + success = iSms.disableCellBroadcastRangeForSubscriber(getSubIdOrDefault(), startMessageId, endMessageId, ranType); } } catch (RemoteException ex) { diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 14eac87f1575..a933da753c41 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -2587,8 +2587,7 @@ public class SubscriptionManager { * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED}. * @param executor The executor of where the callback will execute. * @param callback Callback will be triggered once it succeeds or failed. - * See {@link TelephonyManager.SetOpportunisticSubscriptionResult} - * for more details. Pass null if don't care about the result. + * Pass null if don't care about the result. * * @hide * @@ -2596,7 +2595,8 @@ public class SubscriptionManager { @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPreferredDataSubscriptionId(int subId, boolean needValidation, - @Nullable @CallbackExecutor Executor executor, @Nullable Consumer<Integer> callback) { + @Nullable @CallbackExecutor Executor executor, @Nullable + @TelephonyManager.SetOpportunisticSubscriptionResult Consumer<Integer> callback) { if (VDBG) logd("[setPreferredDataSubscriptionId]+ subId:" + subId); try { ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index ffd5b16b8d67..6dd1691bbafc 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -4845,22 +4845,18 @@ public class TelephonyManager { * Registers a listener object to receive notification of changes * in specified telephony states. * <p> - * To register a listener, pass a {@link PhoneStateListener} and specify at least one telephony - * state of interest in the events argument. - * - * At registration, and when a specified telephony state changes, the telephony manager invokes - * the appropriate callback method on the listener object and passes the current (updated) - * values. + * To register a listener, pass a {@link PhoneStateListener} + * and specify at least one telephony state of interest in + * the events argument. + * + * At registration, and when a specified telephony state + * changes, the telephony manager invokes the appropriate + * callback method on the listener object and passes the + * current (updated) values. * <p> - * To un-register a listener, pass the listener object and set the events argument to + * To unregister a listener, pass the listener object and set the + * events argument to * {@link PhoneStateListener#LISTEN_NONE LISTEN_NONE} (0). - * - * If this TelephonyManager object has been created with {@link #createForSubscriptionId}, - * applies to the given subId. Otherwise, applies to - * {@link SubscriptionManager#getDefaultSubscriptionId()}. To listen events for multiple subIds, - * pass a separate listener object to each TelephonyManager object created with - * {@link #createForSubscriptionId}. - * * Note: if you call this method while in the middle of a binder transaction, you <b>must</b> * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A * {@link SecurityException} will be thrown otherwise. @@ -4875,18 +4871,17 @@ public class TelephonyManager { if (mContext == null) return; try { boolean notifyNow = (getITelephony() != null); + // If the listener has not explicitly set the subId (for example, created with the + // default constructor), replace the subId so it will listen to the account the + // telephony manager is created with. + if (listener.mSubId == null) { + listener.mSubId = mSubId; + } + ITelephonyRegistry registry = getTelephonyRegistry(); if (registry != null) { - // listen to the subId the telephony manager is created with. Ignore subId in - // PhoneStateListener. - registry.listenForSubscriber(mSubId, getOpPackageName(), + registry.listenForSubscriber(listener.mSubId, getOpPackageName(), listener.callback, events, notifyNow); - // TODO: remove this once we remove PhoneStateListener constructor with subId. - if (events == PhoneStateListener.LISTEN_NONE) { - listener.mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - } else { - listener.mSubId = mSubId; - } } else { Rlog.w(TAG, "telephony registry not ready."); } @@ -10552,6 +10547,9 @@ public class TelephonyManager { /** * Set preferred opportunistic data subscription id. * + * Switch internet data to preferred opportunistic data subscription id. This api + * can result in lose of internet connectivity for short period of time while internet data + * is handed over. * <p>Requires that the calling app has carrier privileges on both primary and * secondary subscriptions (see * {@link #hasCarrierPrivileges}), or has permission @@ -10630,9 +10628,11 @@ public class TelephonyManager { * * This api should be called to inform OpportunisticNetwork Service about the availability * of a network at the current location. This information will be used by OpportunisticNetwork - * service to decide to attach to the network opportunistically. If an empty list is passed, + * service to enable modem stack and to attach to the network. If an empty list is passed, * it is assumed that no network is available and will result in disabling the modem stack - * to save power. + * to save power. This api do not switch internet data once network attach is completed. + * Use {@link TelephonyManager#setPreferredOpportunisticDataSubscription} + * to switch internet data after network attach is complete. * Requires that the calling app has carrier privileges on both primary and * secondary subscriptions (see {@link #hasCarrierPrivileges}), or has permission * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. @@ -10703,6 +10703,25 @@ public class TelephonyManager { } /** + * It indicates whether modem is enabled or not per slot. + * It's the corresponding status of {@link #enableModemForSlot}. + * + * @param slotIndex which slot it's checking. + * @hide + */ + public boolean isModemEnabledForSlot(int slotIndex) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.isModemEnabledForSlot(slotIndex, mContext.getOpPackageName()); + } + } catch (RemoteException ex) { + Log.e(TAG, "enableModem RemoteException", ex); + } + return false; + } + + /** * Broadcast intent action for network country code changes. * * <p> diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index 8cdf6a235749..cc037e3ea814 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -21,6 +21,7 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.WorkerThread; import android.content.Context; @@ -38,25 +39,36 @@ import android.telephony.ims.stub.ImsRegistrationImplBase; import com.android.internal.telephony.ITelephony; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; /** * Manages IMS provisioning and configuration parameters, as well as callbacks for apps to listen * to changes in these configurations. * - * Note: IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning - * applications and may vary. For compatibility purposes, the first 100 integer values used in - * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys - * previously defined in the Android framework. Some common constants have been defined in this - * class to make integrating with other system apps easier. USE WITH CARE! + * IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning + * applications and may vary. It is up to the carrier and OEM applications to ensure that the + * correct provisioning keys are being used when integrating with a vendor's ImsService. * - * To avoid collisions, please use String based configurations when possible: - * {@link #setProvisioningStringValue(int, String)} and {@link #getProvisioningStringValue(int)}. + * Note: For compatibility purposes, the integer values [0 - 99] used in + * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys + * previously defined in the Android framework. Please do not redefine new provisioning keys in this + * range or it may generate collisions with existing keys. Some common constants have also been + * defined in this class to make integrating with other system apps easier. * @hide */ @SystemApi public class ProvisioningManager { + /**@hide*/ + @StringDef(prefix = "STRING_QUERY_RESULT_ERROR_", value = { + STRING_QUERY_RESULT_ERROR_GENERIC, + STRING_QUERY_RESULT_ERROR_NOT_READY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StringResultError {} + /** * The query from {@link #getProvisioningStringValue(int)} has resulted in an unspecified error. */ @@ -268,14 +280,13 @@ public class ProvisioningManager { * This operation is blocking and should not be performed on the UI thread. * * @param key A String that represents the provisioning key, which is defined by the OEM. - * @return a String value for the provided key, {@code null} if the key doesn't exist, or one - * of the following error codes: {@link #STRING_QUERY_RESULT_ERROR_GENERIC}, - * {@link #STRING_QUERY_RESULT_ERROR_NOT_READY}. + * @return a String value for the provided key, {@code null} if the key doesn't exist, or + * {@link StringResultError} if there was an error getting the value for the provided key. * @throws IllegalArgumentException if the key provided was invalid. */ @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public @Nullable String getProvisioningStringValue(int key) { + public @Nullable @StringResultError String getProvisioningStringValue(int key) { try { return getITelephony().getImsProvisioningString(mSubId, key); } catch (RemoteException e) { diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 9e2d9ee4696b..8332ffe889f3 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1958,5 +1958,7 @@ interface ITelephony { /** * Get the IRadio HAL Version encoded as 100 * MAJOR_VERSION + MINOR_VERSION or -1 if unknown */ - int getRadioHalVersion(); + int getRadioHalVersion(); + + boolean isModemEnabledForSlot(int slotIndex, String callingPackage); } diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java index d93e58254b95..c9b038c7c7d6 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java @@ -164,6 +164,63 @@ public final class TelephonyPermissions { // We have READ_PHONE_STATE permission, so return true as long as the AppOps bit hasn't been // revoked. AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + return appOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, uid, callingPackage) + == AppOpsManager.MODE_ALLOWED; + } + + /** + * Check whether the app with the given pid/uid can read phone state, or has carrier + * privileges on any active subscription. + * + * <p>If the app does not have carrier privilege, this method will return {@code false} instead + * of throwing a SecurityException. Therefore, the callers cannot tell the difference + * between M+ apps which declare the runtime permission but do not have it, and pre-M apps + * which declare the static permission but had access revoked via AppOps. Apps in the former + * category expect SecurityExceptions; apps in the latter don't. So this method is suitable for + * use only if the behavior in both scenarios is meant to be identical. + * + * @return {@code true} if the app can read phone state or has carrier privilege; + * {@code false} otherwise. + */ + public static boolean checkReadPhoneStateOnAnyActiveSub( + Context context, int pid, int uid, String callingPackage, String message) { + return checkReadPhoneStateOnAnyActiveSub(context, TELEPHONY_SUPPLIER, pid, uid, + callingPackage, message); + } + + @VisibleForTesting + public static boolean checkReadPhoneStateOnAnyActiveSub( + Context context, Supplier<ITelephony> telephonySupplier, int pid, int uid, + String callingPackage, String message) { + try { + context.enforcePermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, uid, message); + + // SKIP checking for run-time permission since caller has PRIVILEGED permission + return true; + } catch (SecurityException privilegedPhoneStateException) { + try { + context.enforcePermission( + android.Manifest.permission.READ_PHONE_STATE, pid, uid, message); + } catch (SecurityException phoneStateException) { + SubscriptionManager sm = (SubscriptionManager) context.getSystemService( + Context.TELEPHONY_SUBSCRIPTION_SERVICE); + int[] activeSubIds = sm.getActiveSubscriptionIdList(); + for (int activeSubId : activeSubIds) { + // If we don't have the runtime permission, but do have carrier privileges, that + // suffices for reading phone state. + if (getCarrierPrivilegeStatus(telephonySupplier, activeSubId, uid) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + return true; + } + } + return false; + } + } + + // We have READ_PHONE_STATE permission, so return true as long as the AppOps bit hasn't been + // revoked. + AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); return appOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, uid, callingPackage) == AppOpsManager.MODE_ALLOWED; } @@ -288,8 +345,8 @@ public final class TelephonyPermissions { // Allow access to a device / profile owner app. DevicePolicyManager devicePolicyManager = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); - if (devicePolicyManager != null && devicePolicyManager.checkDeviceIdentifierAccessAsUser( - callingPackage, Binder.getCallingUserHandle().getIdentifier())) { + if (devicePolicyManager != null && devicePolicyManager.checkDeviceIdentifierAccess( + callingPackage, pid, uid)) { return true; } return false; diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml index 41cb74af911c..e36f97656f2a 100644 --- a/tests/FlickerTests/AndroidTest.xml +++ b/tests/FlickerTests/AndroidTest.xml @@ -17,6 +17,7 @@ </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> <option name="package" value="com.android.server.wm.flicker"/> + <option name="exclude-annotation" value="org.junit.Ignore" /> <option name="shell-timeout" value="6600s" /> <option name="test-timeout" value="6000s" /> <option name="hidden-api-checks" value="false" /> diff --git a/packages/PackageInstaller/Android.mk b/tests/GamePerformance/Android.mk index ab5483c8afb8..58654de34029 100644 --- a/packages/PackageInstaller/Android.mk +++ b/tests/GamePerformance/Android.mk @@ -16,17 +16,24 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) +# Don't include this package in any target +LOCAL_MODULE_TAGS := tests + +LOCAL_DEX_PREOPT := false + +LOCAL_PROGUARD_ENABLED := disabled + LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_PACKAGE_NAME := PackageInstaller +LOCAL_STATIC_JAVA_LIBRARIES := android-support-test + +LOCAL_JAVA_LIBRARIES := android.test.base android.test.runner + +LOCAL_PACKAGE_NAME := GamePerformance -LOCAL_CERTIFICATE := platform -LOCAL_PRIVILEGED_MODULE := true LOCAL_PRIVATE_PLATFORM_APIS := true -LOCAL_STATIC_JAVA_LIBRARIES := xz-java -LOCAL_STATIC_ANDROID_LIBRARIES := androidx.leanback_leanback +LOCAL_CERTIFICATE := platform + include $(BUILD_PACKAGE) - -include $(call all-makefiles-under, $(LOCAL_PATH)) diff --git a/tests/GamePerformance/AndroidManifest.xml b/tests/GamePerformance/AndroidManifest.xml new file mode 100644 index 000000000000..b331e2c07e14 --- /dev/null +++ b/tests/GamePerformance/AndroidManifest.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2018 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.gameperformance"> + <uses-sdk android:minSdkVersion="25"/> + <uses-feature android:glEsVersion="0x00020000" android:required="true" /> + + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <application android:theme="@style/noeffects"> + <uses-library android:name="android.test.runner" /> + <activity android:name="android.gameperformance.GamePerformanceActivity" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <uses-library android:name="android.test.runner" /> + </application> + + <!-- self-instrumenting test package. --> + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="android.gameperformance"> + </instrumentation> +</manifest> diff --git a/tests/GamePerformance/res/values/themes.xml b/tests/GamePerformance/res/values/themes.xml new file mode 100644 index 000000000000..63130717fe72 --- /dev/null +++ b/tests/GamePerformance/res/values/themes.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2018 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. + --> +<resources> + <style name="noeffects" parent="@android:style/Theme.Holo.NoActionBar.Fullscreen"> + <item name="android:windowNoTitle">true</item> + <item name="android:windowFullscreen">true</item> + <item name="android:fadingEdge">none</item> + <item name="android:windowContentTransitions">false</item> + <item name="android:windowAnimationStyle">@null</item> + </style> +</resources> diff --git a/tests/GamePerformance/src/android/gameperformance/ATraceRunner.java b/tests/GamePerformance/src/android/gameperformance/ATraceRunner.java new file mode 100644 index 000000000000..25754fd79a72 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/ATraceRunner.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2018 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.gameperformance; + +import java.io.BufferedReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; + +import android.app.Instrumentation; +import android.os.AsyncTask; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +/** + * Helper that runs atrace command for required duration and category. Results are read from + * the output of atrace and serialized to the provided file. We cannot use direct atrace to + * file because atrace is executed in UI automator context and analysis is done in test context. + * In last case output file is not accessible from the both contexts. + */ +public class ATraceRunner extends AsyncTask<Void, Integer, Boolean>{ + private final static String TAG = "ATraceRunner"; + + // Report that atrace is done. + public interface Delegate { + public void onProcessed(boolean success); + } + + private final Instrumentation mInstrumentation; + private final String mOutput; + private final int mTimeInSeconds; + private final String mCategory; + private final Delegate mDelegate; + + public ATraceRunner(Instrumentation instrumentation, + String output, + int timeInSeconds, + String category, + Delegate delegate) { + mInstrumentation = instrumentation; + mOutput = output; + mTimeInSeconds = timeInSeconds; + mCategory = category; + mDelegate = delegate; + } + + @Override + protected Boolean doInBackground(Void... params) { + BufferedReader bufferedReader = null; + FileWriter writer = null; + try { + // Run the command. + final String cmd = "atrace -t " + mTimeInSeconds + " " + mCategory; + Log.i(TAG, "Running atrace... " + cmd); + writer = new FileWriter(mOutput); + final ParcelFileDescriptor fd = + mInstrumentation.getUiAutomation().executeShellCommand(cmd); + bufferedReader = new BufferedReader( + new InputStreamReader(new ParcelFileDescriptor.AutoCloseInputStream(fd))); + String line; + while ((line = bufferedReader.readLine()) != null) { + writer.write(line); + writer.write("\n"); + } + Log.i(TAG, "Running atrace... DONE"); + return true; + } catch (IOException e) { + Log.i(TAG, "atrace failed", e); + return false; + } finally { + Utils.closeQuietly(bufferedReader); + Utils.closeQuietly(writer); + } + } + + @Override + protected void onPostExecute(Boolean result) { + mDelegate.onProcessed(result); + } + +} diff --git a/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java b/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java new file mode 100644 index 000000000000..2b37280ae9b5 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2018 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.gameperformance; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +import android.content.Context; +import android.opengl.GLES20; +import android.opengl.GLSurfaceView; + +public class CustomOpenGLView extends GLSurfaceView { + private Random mRandom; + private List<Long> mFrameTimes; + + public CustomOpenGLView(Context context) { + super(context); + + mRandom = new Random(); + mFrameTimes = new ArrayList<Long>(); + + setEGLContextClientVersion(2); + + setRenderer(new GLSurfaceView.Renderer() { + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f); + gl.glClearDepthf(1.0f); + gl.glEnable(GL10.GL_DEPTH_TEST); + gl.glDepthFunc(GL10.GL_LEQUAL); + + gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, + GL10.GL_NICEST); } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + GLES20.glViewport(0, 0, width, height); + } + + @Override + public void onDrawFrame(GL10 gl) { + GLES20.glClearColor( + mRandom.nextFloat(), mRandom.nextFloat(), mRandom.nextFloat(), 1.0f); + gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); + synchronized (mFrameTimes) { + mFrameTimes.add(System.currentTimeMillis()); + } + } + }); + setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + } + + /** + * Resets frame times in order to calculate fps for different test pass. + */ + public void resetFrameTimes() { + synchronized (mFrameTimes) { + mFrameTimes.clear(); + } + } + + /** + * Returns current fps based on collected frame times. + */ + public double getFps() { + synchronized (mFrameTimes) { + if (mFrameTimes.size() < 2) { + return 0.0f; + } + return 1000.0 * mFrameTimes.size() / + (mFrameTimes.get(mFrameTimes.size() - 1) - mFrameTimes.get(0)); + } + } +} diff --git a/tests/GamePerformance/src/android/gameperformance/CustomSurfaceView.java b/tests/GamePerformance/src/android/gameperformance/CustomSurfaceView.java new file mode 100644 index 000000000000..a46668dd9e24 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/CustomSurfaceView.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2018 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.gameperformance; + +import java.util.ArrayList; +import java.util.List; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Trace; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +/** + * Minimal SurfaceView that sends buffer on request. + */ +public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback { + // Tag for trace when buffer is requested. + public final static String LOCAL_REQUEST_BUFFER = "localRequestBuffer"; + // Tag for trace when buffer is posted. + public final static String LOCAL_POST_BUFFER = "localPostBuffer"; + + private final Object mSurfaceLock = new Object(); + // Keeps frame times. Used to calculate fps. + private List<Long> mFrameTimes; + // Surface to send. + private Surface mSurface; + private Handler mHandler; + + private Runnable mInvalidateSurfaceTask = new Runnable() { + @Override + public void run() { + synchronized (mSurfaceLock) { + if (mSurface == null) { + return; + } + invalidateSurface(true, true); + mHandler.post(this); + } + } + }; + + public CustomSurfaceView(Context context) { + super(context); + mFrameTimes = new ArrayList<Long>(); + getHolder().addCallback(this); + getHolder().setFormat(PixelFormat.OPAQUE); + + HandlerThread thread = new HandlerThread("SurfaceInvalidator"); + thread.start(); + mHandler = new Handler(thread.getLooper()); + } + + /** + * Resets frame times in order to calculate fps for different test pass. + */ + public void resetFrameTimes() { + synchronized (mSurfaceLock) { + mFrameTimes.clear(); + } + } + + /** + * Returns current fps based on collected frame times. + */ + public double getFps() { + synchronized (mSurfaceLock) { + if (mFrameTimes.size() < 2) { + return 0.0f; + } + return 1000.0 * mFrameTimes.size() / + (mFrameTimes.get(mFrameTimes.size() - 1) - mFrameTimes.get(0)); + } + } + + /** + * Invalidates surface. + * @param traceCalls set to true in case we need register trace calls. Not used for warm-up. + * @param drawFps perform drawing current fps on surface to have some payload on surface. + */ + public void invalidateSurface(boolean traceCalls, boolean drawFps) { + synchronized (mSurfaceLock) { + if (mSurface == null) { + throw new IllegalStateException("Surface is not ready"); + } + if (traceCalls) { + Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, LOCAL_REQUEST_BUFFER); + } + Canvas canvas = mSurface.lockHardwareCanvas(); + if (traceCalls) { + Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); + } + + if (drawFps) { + int textSize = canvas.getHeight() / 24; + Paint paint = new Paint(); + paint.setTextSize(textSize); + paint.setColor(0xFFFF8040); + canvas.drawARGB(92, 255, 255, 255); + canvas.drawText("FPS: " + String.format("%.2f", getFps()), 10, 300, paint); + } + + if (traceCalls) { + Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, LOCAL_POST_BUFFER); + } + mSurface.unlockCanvasAndPost(canvas); + if (traceCalls) { + Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); + } + + mFrameTimes.add(System.currentTimeMillis()); + } + } + + /** + * Wait until surface is created and ready to use or return immediately if surface + * already exists. + */ + public void waitForSurfaceReady() { + synchronized (mSurfaceLock) { + if (mSurface == null) { + try { + mSurfaceLock.wait(5000); + } catch(InterruptedException e) { + e.printStackTrace(); + } + } + if (mSurface == null) + throw new IllegalStateException("Surface is not ready."); + } + } + + /** + * Waits until surface is destroyed or return immediately if surface does not exist. + */ + public void waitForSurfaceDestroyed() { + synchronized (mSurfaceLock) { + if (mSurface != null) { + try { + mSurfaceLock.wait(5000); + } catch(InterruptedException e) { + } + } + if (mSurface != null) + throw new IllegalStateException("Surface still exists."); + } + } + + + @Override + public void surfaceCreated(SurfaceHolder holder) { + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + // This method is always called at least once, after surfaceCreated. + synchronized (mSurfaceLock) { + mSurface = holder.getSurface(); + mSurfaceLock.notify(); + mHandler.post(mInvalidateSurfaceTask); + } + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + synchronized (mSurfaceLock) { + mHandler.removeCallbacks(mInvalidateSurfaceTask); + mSurface = null; + mSurfaceLock.notify(); + } + } +} diff --git a/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java b/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java new file mode 100644 index 000000000000..b0e6196b53d7 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2018 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.gameperformance; + +import java.util.concurrent.CountDownLatch; + +import android.app.Activity; +import android.graphics.Rect; +import android.os.Bundle; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.RelativeLayout; + +/** + * Minimal activity that holds SurfaceView or GLSurfaceView. + * call attachSurfaceView or attachOpenGLView to switch views. + */ +public class GamePerformanceActivity extends Activity { + private CustomSurfaceView mSurfaceView = null; + private CustomOpenGLView mOpenGLView = null; + private RelativeLayout mRootLayout; + + public void attachSurfaceView() throws InterruptedException { + synchronized (mRootLayout) { + if (mSurfaceView != null) { + return; + } + final CountDownLatch latch = new CountDownLatch(1); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (mOpenGLView != null) { + mRootLayout.removeView(mOpenGLView); + mOpenGLView = null; + } + mSurfaceView = new CustomSurfaceView(GamePerformanceActivity.this); + mRootLayout.addView(mSurfaceView); + latch.countDown(); + } + }); + latch.await(); + mSurfaceView.waitForSurfaceReady(); + } + } + + public void attachOpenGLView() throws InterruptedException { + synchronized (mRootLayout) { + if (mOpenGLView != null) { + return; + } + final CountDownLatch latch = new CountDownLatch(1); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (mSurfaceView != null) { + mRootLayout.removeView(mSurfaceView); + mSurfaceView = null; + } + mOpenGLView = new CustomOpenGLView(GamePerformanceActivity.this); + mRootLayout.addView(mOpenGLView); + latch.countDown(); + } + }); + latch.await(); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + // To layouts in parent. First contains list of Surfaces and second + // controls. Controls stay on top. + mRootLayout = new RelativeLayout(this); + mRootLayout.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + Rect rect = new Rect(); + getWindow().getDecorView().getWindowVisibleDisplayFrame(rect); + + mOpenGLView = new CustomOpenGLView(this); + mRootLayout.addView(mOpenGLView); + + setContentView(mRootLayout); + } + + public void resetFrameTimes() { + if (mSurfaceView != null) { + mSurfaceView.resetFrameTimes(); + } else if (mOpenGLView != null) { + mOpenGLView.resetFrameTimes(); + } else { + throw new IllegalStateException("Nothing attached"); + } + } + + public double getFps() { + if (mSurfaceView != null) { + return mSurfaceView.getFps(); + } else if (mOpenGLView != null) { + return mOpenGLView.getFps(); + } else { + throw new IllegalStateException("Nothing attached"); + } + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + } +}
\ No newline at end of file diff --git a/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java b/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java new file mode 100644 index 000000000000..e5de7d75886e --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018 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.gameperformance; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import android.app.Activity; +import android.content.Context; +import android.graphics.PixelFormat; +import android.os.Build; +import android.os.Bundle; +import android.os.Trace; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; + +public class GamePerformanceTest extends + ActivityInstrumentationTestCase2<GamePerformanceActivity> { + private final static String TAG = "GamePerformanceTest"; + + private final static int GRAPHIC_BUFFER_WARMUP_LOOP_CNT = 60; + + public GamePerformanceTest() { + super(GamePerformanceActivity.class); + } + + @SmallTest + public void testGraphicBufferMetrics() throws IOException, InterruptedException { + Bundle status = new Bundle(); + + for (int i = 0; i < 2; ++i) { + if (i == 0) { + getActivity().attachSurfaceView(); + } else { + getActivity().attachOpenGLView(); + } + + // Perform warm-up. + Thread.sleep(2000); + + // Once atrace is done, this one is triggered. + CountDownLatch latch = new CountDownLatch(1); + + final String passTag = i == 0 ? "surface" : "opengl"; + final String output = (new File(getInstrumentation().getContext().getFilesDir(), + "atrace_" + passTag + ".log")).getAbsolutePath(); + Log.i(TAG, "Collecting traces to " + output); + new ATraceRunner(getInstrumentation(), output, 5, "gfx", new ATraceRunner.Delegate() { + @Override + public void onProcessed(boolean success) { + latch.countDown(); + } + }).execute(); + + // Reset frame times and perform invalidation loop while atrace is running. + getActivity().resetFrameTimes(); + latch.await(); + + // Copy results. + final Map<String, Double> metrics = + GraphicBufferMetrics.processGraphicBufferResult(output, passTag); + for (Map.Entry<String, Double> metric : metrics.entrySet()) { + status.putDouble(metric.getKey(), metric.getValue()); + } + // Also record FPS. + status.putDouble(passTag + "_fps", getActivity().getFps()); + } + + getInstrumentation().sendStatus(Activity.RESULT_OK, status); + } +} diff --git a/tests/GamePerformance/src/android/gameperformance/GraphicBufferMetrics.java b/tests/GamePerformance/src/android/gameperformance/GraphicBufferMetrics.java new file mode 100644 index 000000000000..dffce1acdec3 --- /dev/null +++ b/tests/GamePerformance/src/android/gameperformance/GraphicBufferMetrics.java @@ -0,0 +1,530 @@ +/* + * Copyright (C) 2018 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.gameperformance; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * Utility class that performs analysis of atrace logs. This is implemented without Android + * dependencies and therefore can be used in stand-alone mode. + * Idea of this is to track atrace gfx event from graphics buffer producer/consumer. + * We analyze here from surfaceflinger + * queueBuffer - event when buffer was queued. + * acquireBuffer - event when buffer was requested for composition. + * releaseBuffer - even when buffer was released after composition. + * This also track events, issued locally + * localPostBuffer - event when buffer was posted from the local app. + * + * queueBuffer, acquireBuffer, releaseBuffer is accompanied with buffer name so we + * can track life-cycle of particular buffer. + * We don't have such information for localPostBuffer, however we can track next queueBuffer + * from surfaceflinger corresponds to previous localPostBuffer. + * + * Following results are calculated: + * post_time_[min/max/avr]_mcs - time for localPostBuffer duration. + * ready_time_[min/max/avr]_mcs - time from localPostBuffer to when buffer was acquired by + * surfaceflinger. + * latency_[min/max/avr]_mcs - time from localPostBuffer to when buffer was released by + * surfaceflinger. + * missed_frame_percents - percentage of missed frames (frames that do not have right sequence + * of events). + * + * Following is example of atrace logs from different platforms + * <...>-5237 (-----) [000] ...1 228.380392: tracing_mark_write: B|11|SurfaceView - android.gameperformance/android.gameperformance.GamePerformanceActivity#0: 2 + * surfaceflinger-5855 ( 5855) [001] ...1 169.627364: tracing_mark_write: B|24|acquireBuffer + * HwBinder:617_2-652 ( 617) [002] d..1 360262.921756: sde_evtlog: 617|sde_encoder_virt_atomic_check:855|19|0|0|0|0|0|0|0|0|0|0|0|0|0|0 + */ +public class GraphicBufferMetrics { + private final static String TAG = "GraphicBufferMetrics"; + + private final static String KEY_POST_TIME = "post_time"; + private final static String KEY_READY_TIME = "ready_time"; + private final static String KEY_LATENCY = "latency"; + private final static String SUFFIX_MIN = "min"; + private final static String SUFFIX_MAX = "max"; + private final static String SUFFIX_MEDIAN = "median"; + private final static String KEY_MISSED_FRAME_RATE = "missed_frame_percents"; + + private final static int EVENT_POST_BUFFER = 0; + private final static int EVENT_QUEUE_BUFFER = 1; + private final static int EVENT_ACQUIRE_BUFFER = 2; + private final static int EVENT_RELEASE_BUFFER = 3; + + // atrace prints this line. Used as a marker to make sure that we can parse its output. + private final static String ATRACE_HEADER = + "# TASK-PID TGID CPU# |||| TIMESTAMP FUNCTION"; + + private final static String[] KNOWN_PHRASES = new String[] { + "capturing trace... done", "TRACE:"}; + private final static List<String> KNWON_PHRASES_LIST = Arrays.asList(KNOWN_PHRASES); + + // Type of the atrace event we can parse and analyze. + private final static String FUNCTION_TRACING_MARK_WRITE = "tracing_mark_write"; + + // Trace event we can ignore. It contains current timestamp information for the atrace output. + private final static String TRACE_EVENT_CLOCK_SYNC = "trace_event_clock_sync:"; + + // Threshold we consider test passes successfully. If we cannot collect enough amount of frames + // let fail the test. 50 is calculated 10 frames per second running for five seconds. + private final static int MINIMAL_SAMPLE_CNT_TO_PASS = 50; + + /** + * Raw event in atrace. Stored hierarchically. + */ + private static class RawEvent { + // Parent of this event or null for the root holder. + public final RawEvent mParent; + // Time of the event in mcs. + public final long mTime; + // Duration of the event in mcs. + public long mDuration; + // Name/body of the event. + public final String mName; + // Children events. + public final List<RawEvent> mChildren; + + public RawEvent(RawEvent parent, long time, String name) { + mParent = parent; + mTime = time; + mName = name; + mDuration = -1; + mChildren = new ArrayList<>(); + } + + /** + * Recursively finds child events. + * @param path specify path to events. For example a/b. That means to find child with name + * 'a' of the current event and in this child find child with name 'b'. Path + * can consist from only one segment and that means we analyze only children of + * the current event. + * @param collector to collect found events. + */ + public void findEvents(String path, List<RawEvent> collector) { + final int separator = path.indexOf('/'); + final String current = separator > 0 ? path.substring(0, separator) : path; + final String nextPath = separator > 0 ? path.substring(separator + 1) : null; + for (RawEvent child : mChildren) { + if (child.mName.equals(current)) { + if (nextPath != null) { + child.findEvents(nextPath, collector); + } else { + collector.add(child); + } + } + } + } + + public void dump(String prefix) { + System.err.print(prefix); + System.err.println(mTime + "[" + mDuration + "] " + mName); + for (RawEvent e : mChildren) { + e.dump(prefix + " "); + } + } + } + + /** + * Describes graphic buffer event. local post, queued, acquired, released. + */ + private static class BufferEvent { + public final int mType; + public final long mTime; + public final long mDuration; + public final String mBufferId; + + public BufferEvent(int type, long time, long duration, String bufferId) { + mType = type; + mTime = time; + mDuration = duration; + mBufferId = bufferId; + } + + @Override + public String toString() { + return "Type: " + mType + ". Time: " + mTime + + "[" + mDuration + "]. Buffer: " + mBufferId + "."; + } + } + + /** + * Returns true if given char is digit. + */ + private static boolean isDigitChar(char c) { + return (c >= '0') && (c <= '9'); + } + + /** + * Returns true if given char is digit or '.'. + */ + private static boolean isDoubleDigitChar(char c) { + return (c == '.') || isDigitChar(c); + } + + /** + * Convert timestamp string that represents double value in seconds to long time that represents + * timestamp in microseconds. + */ + private static long getTimeStamp(String timeStampStr) { + return (long)(1000000.0 * Double.parseDouble(timeStampStr)); + } + + /** + * Reads atrace log and build event model. Result is a map, where key specifies event for the + * particular thread. Value is the synthetic root RawEvent that holds all events for the + * thread. Events are stored hierarchically. + */ + private static Map<Integer, RawEvent> buildEventModel(String fileName) throws IOException { + Map<Integer, RawEvent> result = new HashMap<>(); + + BufferedReader bufferedReader = null; + String line = ""; + boolean headerDetected = false; + try { + bufferedReader = new BufferedReader(new FileReader(fileName)); + while ((line = bufferedReader.readLine()) != null) { + // Make sure we find comment that describes output format we can with. + headerDetected |= line.equals(ATRACE_HEADER); + // Skip comments. + if (line.startsWith("#")) { + continue; + } + // Skip known service output + if (KNWON_PHRASES_LIST.contains(line)) { + continue; + } + + if (!headerDetected) { + // We don't know the format of this line. + throw new IllegalStateException("Header was not detected"); + } + + // TASK-PID in header exists at position 12. PID position 17 should contains first + // digit of thread id after the '-'. + if (!isDigitChar(line.charAt(17)) || line.charAt(16) != '-') { + throw new IllegalStateException("Failed to parse thread id: " + line); + } + int rightIndex = line.indexOf(' ', 17); + final String threadIdStr = line.substring(17, rightIndex); + final int threadId = Integer.parseInt(threadIdStr); + + // TIMESTAMP in header exists at position 45 + // This position should point in the middle of timestamp which is ended by ':'. + int leftIndex = 45; + while (isDoubleDigitChar(line.charAt(leftIndex))) { + --leftIndex; + } + rightIndex = line.indexOf(':', 45); + + final String timeStampString = line.substring(leftIndex + 1, rightIndex); + final long timeStampMcs = getTimeStamp(timeStampString); + + // Find function name, pointed by FUNCTION. Long timestamp can shift if position + // so use end of timestamp to find the function which is ended by ':'. + leftIndex = rightIndex + 1; + while (Character.isWhitespace(line.charAt(leftIndex))) { + ++leftIndex; + } + rightIndex = line.indexOf(':', leftIndex); + final String function = line.substring(leftIndex, rightIndex); + + if (!function.equals(FUNCTION_TRACING_MARK_WRITE)) { + continue; + } + + // Rest of the line is event body. + leftIndex = rightIndex + 1; + while (Character.isWhitespace(line.charAt(leftIndex))) { + ++leftIndex; + } + + final String event = line.substring(leftIndex); + if (event.startsWith(TRACE_EVENT_CLOCK_SYNC)) { + continue; + } + + // Parse event, for example: + // B|615|SurfaceView - android.gameperformance.GamePerformanceActivity#0: 1 + // E|615 + // C|11253|operation id|2 + StringTokenizer eventTokenizer = new StringTokenizer(event, "|"); + final String eventType = eventTokenizer.nextToken(); + + // Attach root on demand. + if (!result.containsKey(threadId)) { + result.put(threadId, new RawEvent(null /* parent */, + timeStampMcs, + "#ROOT_" + threadId)); + } + + switch (eventType) { + case "B": { + // Log entry starts. + eventTokenizer.nextToken(); // PID + String eventText = eventTokenizer.nextToken(); + while (eventTokenizer.hasMoreTokens()) { + eventText += " "; + eventText += eventTokenizer.nextToken(); + } + RawEvent parent = result.get(threadId); + RawEvent current = new RawEvent(parent, timeStampMcs, eventText); + parent.mChildren.add(current); + result.put(threadId, current); + } + break; + case "E": { + // Log entry ends. + RawEvent current = result.get(threadId); + current.mDuration = timeStampMcs - current.mTime; + if (current.mParent == null) { + // Detect a tail of the previous call. Remove last child element if it + // exists once it does not belong to the root. + if (!current.mChildren.isEmpty()) { + current.mChildren.remove(current.mChildren.size() -1); + } + } else { + result.put(threadId, current.mParent); + } + } + break; + case "C": + // Counter, ignore + break; + default: + throw new IllegalStateException( + "Unrecognized trace: " + line + " # " + eventType + " # " + event); + } + } + + // Detect incomplete events and detach from the root. + Set<Integer> threadIds = new TreeSet<>(); + threadIds.addAll(result.keySet()); + for (int threadId : threadIds) { + RawEvent root = result.get(threadId); + if (root.mParent == null) { + // Last trace was closed. + continue; + } + // Find the root. + while (root.mParent != null) { + root = root.mParent; + } + // Discard latest incomplete element. + root.mChildren.remove(root.mChildren.size() - 1); + result.put(threadId, root); + } + } catch (Exception e) { + throw new IOException("Failed to process " + line, e); + } finally { + Utils.closeQuietly(bufferedReader); + } + + return result; + } + + /** + * Processes provided atrace log and calculates graphics buffer metrics. + * @param fileName name of atrace log file. + * @param testTag tag to separate results for the different passes. + */ + public static Map<String, Double> processGraphicBufferResult( + String fileName, String testTag) throws IOException { + final Map<Integer, RawEvent> model = buildEventModel(fileName); + + List<RawEvent> collectorPostBuffer = new ArrayList<>(); + List<RawEvent> collectorQueueBuffer = new ArrayList<>(); + List<RawEvent> collectorReleaseBuffer = new ArrayList<>(); + List<RawEvent> collectorAcquireBuffer = new ArrayList<>(); + + // Collect required events. + for (RawEvent root : model.values()) { + // Surface view + root.findEvents("localPostBuffer", collectorPostBuffer); + // OpengGL view + root.findEvents("eglSwapBuffersWithDamageKHR", collectorPostBuffer); + + root.findEvents("queueBuffer", collectorQueueBuffer); + root.findEvents("onMessageReceived/handleMessageInvalidate/latchBuffer/" + + "updateTexImage/acquireBuffer", + collectorAcquireBuffer); + // PI stack + root.findEvents( + "onMessageReceived/handleMessageRefresh/postComposition/releaseBuffer", + collectorReleaseBuffer); + // NYC stack + root.findEvents( + "onMessageReceived/handleMessageRefresh/releaseBuffer", + collectorReleaseBuffer); + } + + // Convert raw event to buffer events. + List<BufferEvent> bufferEvents = new ArrayList<>(); + for (RawEvent event : collectorPostBuffer) { + bufferEvents.add( + new BufferEvent(EVENT_POST_BUFFER, event.mTime, event.mDuration, null)); + } + toBufferEvents(EVENT_QUEUE_BUFFER, collectorQueueBuffer, bufferEvents); + toBufferEvents(EVENT_ACQUIRE_BUFFER, collectorAcquireBuffer, bufferEvents); + toBufferEvents(EVENT_RELEASE_BUFFER, collectorReleaseBuffer, bufferEvents); + + // Sort events based on time. These events are originally taken from different threads so + // order is not always preserved. + Collections.sort(bufferEvents, new Comparator<BufferEvent>() { + @Override + public int compare(BufferEvent o1, BufferEvent o2) { + if (o1.mTime < o2.mTime) { + return -1; + } if (o1.mTime > o2.mTime) { + return +1; + } else { + return 0; + } + } + }); + + // Collect samples. + List<Long> postTimes = new ArrayList<>(); + List<Long> readyTimes = new ArrayList<>(); + List<Long> latencyTimes = new ArrayList<>(); + long missedCnt = 0; + + for (int i = 0; i < bufferEvents.size(); ++i) { + if (bufferEvents.get(i).mType != EVENT_POST_BUFFER) { + continue; + } + final int queueIndex = findNextOfType(bufferEvents, i + 1, EVENT_QUEUE_BUFFER); + if (queueIndex < 0) { + break; + } + final int acquireIndex = findNextOfBufferId(bufferEvents, queueIndex + 1, + bufferEvents.get(queueIndex).mBufferId); + if (acquireIndex < 0) { + break; + } + if (bufferEvents.get(acquireIndex).mType != EVENT_ACQUIRE_BUFFER) { + // Was not actually presented. + ++missedCnt; + continue; + } + final int releaseIndex = findNextOfBufferId(bufferEvents, acquireIndex + 1, + bufferEvents.get(queueIndex).mBufferId); + if (releaseIndex < 0) { + break; + } + if (bufferEvents.get(releaseIndex).mType != EVENT_RELEASE_BUFFER) { + // Was not actually presented. + ++missedCnt; + continue; + } + + postTimes.add(bufferEvents.get(i).mDuration); + readyTimes.add( + bufferEvents.get(acquireIndex).mTime - bufferEvents.get(i).mTime); + latencyTimes.add( + bufferEvents.get(releaseIndex).mTime - bufferEvents.get(i).mTime); + } + + if (postTimes.size() < MINIMAL_SAMPLE_CNT_TO_PASS) { + throw new IllegalStateException("Too few sample cnt: " + postTimes.size() +". " + + MINIMAL_SAMPLE_CNT_TO_PASS + " is required."); + } + + Map<String, Double> status = new TreeMap<>(); + addResults(status, testTag, KEY_POST_TIME, postTimes); + addResults(status, testTag, KEY_READY_TIME, readyTimes); + addResults(status, testTag, KEY_LATENCY, latencyTimes); + status.put(testTag + "_" + KEY_MISSED_FRAME_RATE, + 100.0 * missedCnt / (missedCnt + postTimes.size())); + return status; + } + + private static void addResults( + Map<String, Double> status, String tag, String key, List<Long> times) { + Collections.sort(times); + long min = times.get(0); + long max = times.get(0); + for (long time : times) { + min = Math.min(min, time); + max = Math.max(max, time); + } + status.put(tag + "_" + key + "_" + SUFFIX_MIN, (double)min); + status.put(tag + "_" + key + "_" + SUFFIX_MAX, (double)max); + status.put(tag + "_" + key + "_" + SUFFIX_MEDIAN, (double)times.get(times.size() / 2)); + } + + // Helper to convert surface flinger events to buffer events. + private static void toBufferEvents( + int type, List<RawEvent> rawEvents, List<BufferEvent> bufferEvents) { + for (RawEvent event : rawEvents) { + if (event.mChildren.isEmpty()) { + throw new IllegalStateException("Buffer name is expected"); + } + final String bufferName = event.mChildren.get(0).mName; + if (bufferName.startsWith("SurfaceView - android.gameperformance")) { + bufferEvents.add( + new BufferEvent(type, event.mTime, event.mDuration, bufferName)); + } + } + } + + private static int findNextOfType(List<BufferEvent> events, int startIndex, int type) { + for (int i = startIndex; i < events.size(); ++i) { + if (events.get(i).mType == type) { + return i; + } + } + return -1; + } + + private static int findNextOfBufferId( + List<BufferEvent> events, int startIndex, String bufferId) { + for (int i = startIndex; i < events.size(); ++i) { + if (bufferId.equals(events.get(i).mBufferId)) { + return i; + } + } + return -1; + } + + public static void main(String[] args) { + if (args.length != 1) { + System.err.println("Usage: " + TAG + " atrace.log"); + return; + } + + try { + System.out.println("Results:"); + for (Map.Entry<?, ?> entry : + processGraphicBufferResult(args[0], "default").entrySet()) { + System.out.println(" " + entry.getKey() + " = " + entry.getValue()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/cmds/statsd/src/logd/LogListener.h b/tests/GamePerformance/src/android/gameperformance/Utils.java index d8b06e9fab92..64819712bf6d 100644 --- a/cmds/statsd/src/logd/LogListener.h +++ b/tests/GamePerformance/src/android/gameperformance/Utils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 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. @@ -13,28 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package android.gameperformance; -#pragma once +import java.io.Closeable; +import java.io.IOException; -#include "logd/LogEvent.h" - -#include <utils/RefBase.h> - -namespace android { -namespace os { -namespace statsd { - -/** - * Callback for LogReader - */ -class LogListener : public virtual android::RefBase { -public: - LogListener(); - virtual ~LogListener(); - - virtual void OnLogEvent(LogEvent* msg) = 0; -}; - -} // namespace statsd -} // namespace os -} // namespace android +public class Utils { + public static void closeQuietly(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (IOException e) { + // Ignore + } + } +} diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index d0c261279c9d..28af7cee8f38 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -19,11 +19,16 @@ package com.android.server; import static com.android.server.PackageWatchdog.TRIGGER_FAILURE_COUNT; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import android.content.Context; import android.content.pm.VersionedPackage; +import android.os.Handler; +import android.os.RemoteException; import android.os.test.TestLooper; +import android.util.AtomicFile; import androidx.test.InstrumentationRegistry; @@ -36,11 +41,14 @@ import org.junit.Test; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; -// TODO(zezeozue): Write test without using PackageWatchdog#getPackages. Just rely on +// TODO: Write test without using PackageWatchdog#getPackages. Just rely on // behavior of observers receiving crash notifications or not to determine if it's registered +// TODO: Use Truth in tests. /** * Test PackageWatchdog. */ @@ -77,12 +85,11 @@ public class PackageWatchdogTest { TestObserver observer3 = new TestObserver(OBSERVER_NAME_3); // Start observing for observer1 which will be unregistered - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); // Start observing for observer2 which will expire - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION, - false); + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION); // Start observing for observer3 which will have expiry duration reduced - watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION, false); + watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION); // Verify packages observed at start // 1 @@ -145,9 +152,8 @@ public class PackageWatchdogTest { TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false); - watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION, - false); + watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION); // Verify 2 observers are registered and saved internally // 1 @@ -193,8 +199,8 @@ public class PackageWatchdogTest { TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION, false); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false); + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); // Then fail APP_A below the threshold for (int i = 0; i < TRIGGER_FAILURE_COUNT - 1; i++) { @@ -220,8 +226,8 @@ public class PackageWatchdogTest { TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION, false); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION, false); + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION); // Then fail APP_C (not observed) above the threshold for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { @@ -255,7 +261,7 @@ public class PackageWatchdogTest { } }; - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION, false); + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); // Then fail APP_A (different version) above the threshold for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { @@ -288,13 +294,13 @@ public class PackageWatchdogTest { // Start observing for all impact observers watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D), - SHORT_DURATION, false); + SHORT_DURATION); watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C), - SHORT_DURATION, false); + SHORT_DURATION); watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B), - SHORT_DURATION, false); + SHORT_DURATION); watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A), - SHORT_DURATION, false); + SHORT_DURATION); // Then fail all apps above the threshold for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { @@ -346,8 +352,8 @@ public class PackageWatchdogTest { PackageHealthObserverImpact.USER_IMPACT_MEDIUM); // Start observing for observerFirst and observerSecond with failure handling - watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION, false); - watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION, false); + watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION); + watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION); // Then fail APP_A above the threshold for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { @@ -424,8 +430,8 @@ public class PackageWatchdogTest { PackageHealthObserverImpact.USER_IMPACT_HIGH); // Start observing for observer1 and observer2 with failure handling - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION, false); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false); + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); // Then fail APP_A above the threshold for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { @@ -442,11 +448,12 @@ public class PackageWatchdogTest { } /** - * Test explicit health check status determines package failure or success on expiry + * Test package passing explicit health checks does not fail and vice versa. */ @Test - public void testPackageFailureExplicitHealthCheck() throws Exception { - PackageWatchdog watchdog = createWatchdog(); + public void testExplicitHealthChecks() throws Exception { + TestController controller = new TestController(); + PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, PackageHealthObserverImpact.USER_IMPACT_HIGH); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, @@ -457,21 +464,36 @@ public class PackageWatchdogTest { // Start observing with explicit health checks for APP_A and APP_B respectively // with observer1 and observer2 - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, true); - watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION, true); - // Explicit health check passed for APP_A (observer1 is aware) - watchdog.onExplicitHealthCheckPassed(APP_A); - // Start observing APP_A with explicit health checks for observer3. + controller.setSupportedPackages(Arrays.asList(APP_A, APP_B)); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION); + + // Run handler so requests are dispatched to the controller + mTestLooper.dispatchAll(); + + // Verify we requested health checks for APP_A and APP_B + List<String> requestedPackages = controller.getRequestedPackages(); + assertEquals(2, requestedPackages.size()); + assertEquals(APP_A, requestedPackages.get(0)); + assertEquals(APP_B, requestedPackages.get(1)); + + // Then health check passed for APP_A (observer1 is aware) + controller.setPackagePassed(APP_A); + + // Then start observing APP_A with explicit health checks for observer3. // Observer3 didn't exist when we got the explicit health check above, so // it starts out with a non-passing explicit health check and has to wait for a pass // otherwise it would be notified of APP_A failure on expiry - watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION, true); + watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION); // Then expire observers Thread.sleep(SHORT_DURATION); // Run handler so package failures are dispatched to observers mTestLooper.dispatchAll(); + // Verify we cancelled all requests on expiry + assertEquals(0, controller.getRequestedPackages().size()); + // Verify observer1 is not notified assertEquals(0, observer1.mFailedPackages.size()); @@ -484,9 +506,96 @@ public class PackageWatchdogTest { assertEquals(APP_A, observer3.mFailedPackages.get(0)); } + /** + * Test explicit health check state can be disabled and enabled correctly. + */ + @Test + public void testExplicitHealthCheckStateChanges() throws Exception { + TestController controller = new TestController(); + PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); + TestObserver observer = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + + // Start observing with explicit health checks for APP_A and APP_B + controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C)); + watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION); + + // Run handler so requests are dispatched to the controller + mTestLooper.dispatchAll(); + + // Verify we requested health checks for APP_A and APP_B + List<String> requestedPackages = controller.getRequestedPackages(); + assertEquals(2, requestedPackages.size()); + assertEquals(APP_A, requestedPackages.get(0)); + assertEquals(APP_B, requestedPackages.get(1)); + + // Disable explicit health checks (marks APP_A and APP_B as passed) + watchdog.setExplicitHealthCheckEnabled(false); + + // Run handler so requests/cancellations are dispatched to the controller + mTestLooper.dispatchAll(); + + // Verify all checks are cancelled + assertEquals(0, controller.getRequestedPackages().size()); + + // Then expire APP_A + Thread.sleep(SHORT_DURATION); + mTestLooper.dispatchAll(); + + // Verify APP_A is not failed (APP_B) is not expired yet + assertEquals(0, observer.mFailedPackages.size()); + + // Re-enable explicit health checks + watchdog.setExplicitHealthCheckEnabled(true); + + // Run handler so requests/cancellations are dispatched to the controller + mTestLooper.dispatchAll(); + + // Verify no requests are made cos APP_A is expired and APP_B was marked as passed + assertEquals(0, controller.getRequestedPackages().size()); + + // Then set new supported packages + controller.setSupportedPackages(Arrays.asList(APP_C)); + // Start observing APP_A and APP_C; only APP_C has support for explicit health checks + watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION); + + // Run handler so requests/cancellations are dispatched to the controller + mTestLooper.dispatchAll(); + + // Verify requests are only made for APP_C + requestedPackages = controller.getRequestedPackages(); + assertEquals(1, requestedPackages.size()); + assertEquals(APP_C, requestedPackages.get(0)); + + // Then expire APP_A and APP_C + Thread.sleep(SHORT_DURATION); + mTestLooper.dispatchAll(); + + // Verify only APP_C is failed because explicit health checks was not supported for APP_A + assertEquals(1, observer.mFailedPackages.size()); + assertEquals(APP_C, observer.mFailedPackages.get(0)); + } + private PackageWatchdog createWatchdog() { - return new PackageWatchdog(InstrumentationRegistry.getContext(), - mTestLooper.getLooper()); + return createWatchdog(new TestController(), true /* withPackagesReady */); + } + + private PackageWatchdog createWatchdog(TestController controller, boolean withPackagesReady) { + Context context = InstrumentationRegistry.getContext(); + AtomicFile policyFile = + new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml")); + Handler handler = new Handler(mTestLooper.getLooper()); + PackageWatchdog watchdog = + new PackageWatchdog(context, policyFile, handler, handler, controller); + // Verify controller is not automatically started + assertFalse(controller.mIsEnabled); + if (withPackagesReady) { + watchdog.onPackagesReady(); + // Verify controller by default is started when packages are ready + assertTrue(controller.mIsEnabled); + } + return watchdog; } private static class TestObserver implements PackageHealthObserver { @@ -517,4 +626,69 @@ public class PackageWatchdogTest { return mName; } } + + private static class TestController extends ExplicitHealthCheckController { + TestController() { + super(null /* controller */); + } + + private boolean mIsEnabled; + private List<String> mSupportedPackages = new ArrayList<>(); + private List<String> mRequestedPackages = new ArrayList<>(); + private Runnable mStateChangedRunnable; + private Consumer<String> mPassedConsumer; + + @Override + public void request(String packageName) throws RemoteException { + if (!mRequestedPackages.contains(packageName)) { + mRequestedPackages.add(packageName); + } + } + + @Override + public void cancel(String packageName) throws RemoteException { + mRequestedPackages.remove(packageName); + } + + @Override + public void getSupportedPackages(Consumer<List<String>> consumer) throws RemoteException { + consumer.accept(mIsEnabled ? mSupportedPackages : Collections.emptyList()); + } + + @Override + public void getRequestedPackages(Consumer<List<String>> consumer) throws RemoteException { + // Pass copy to prevent ConcurrentModificationException during test + consumer.accept( + mIsEnabled ? new ArrayList<>(mRequestedPackages) : Collections.emptyList()); + } + + @Override + public void setEnabled(boolean enabled) { + mIsEnabled = enabled; + mStateChangedRunnable.run(); + } + + @Override + public void setCallbacks(Runnable stateChangedRunnable, Consumer<String> passedConsumer) { + mStateChangedRunnable = stateChangedRunnable; + mPassedConsumer = passedConsumer; + } + + public void setSupportedPackages(List<String> packages) { + mSupportedPackages.clear(); + mSupportedPackages.addAll(packages); + } + + public void setPackagePassed(String packageName) { + mPassedConsumer.accept(packageName); + } + + public List<String> getRequestedPackages() { + if (mIsEnabled) { + return mRequestedPackages; + } else { + return Collections.emptyList(); + } + } + } } diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java index 76cccc95742d..1a3ea6096c82 100644 --- a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java +++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java @@ -44,6 +44,8 @@ public class ParcelableTests { assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable()))); builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); + // lease will expire in two hours + builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000); // groupHint stays null this time around builder.setDnsAddresses(Collections.emptyList()); builder.setMtu(18); @@ -51,6 +53,7 @@ public class ParcelableTests { assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable()))); builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("6.7.8.9")); + builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 3_600_000); builder.setGroupHint("groupHint"); builder.setDnsAddresses(Arrays.asList( InetAddress.getByName("ACA1:652B:0911:DE8F:1200:115E:913B:AA2A"), @@ -66,7 +69,7 @@ public class ParcelableTests { // Verify that this test does not miss any new field added later. // If any field is added to NetworkAttributes it must be tested here for parceling // roundtrip. - assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields()) + assertEquals(5, Arrays.stream(NetworkAttributes.class.getDeclaredFields()) .filter(f -> !Modifier.isStatic(f.getModifiers())).count()); } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 2fac8e0963e8..d82b4e4850bc 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -104,6 +104,7 @@ import android.net.ConnectivityManager.PacketKeepalive; import android.net.ConnectivityManager.PacketKeepaliveCallback; import android.net.ConnectivityManager.TooManyRequestsException; import android.net.ConnectivityThread; +import android.net.IDnsResolver; import android.net.INetd; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; @@ -240,6 +241,7 @@ public class ConnectivityServiceTest { private static final String CLAT_PREFIX = "v4-"; private static final String MOBILE_IFNAME = "test_rmnet_data0"; private static final String WIFI_IFNAME = "test_wlan0"; + private static final String[] EMPTY_STRING_ARRAY = new String[0]; private MockContext mServiceContext; private WrappedConnectivityService mService; @@ -256,6 +258,7 @@ public class ConnectivityServiceTest { @Mock INetworkManagementService mNetworkManagementService; @Mock INetworkStatsService mStatsService; @Mock INetworkPolicyManager mNpm; + @Mock IDnsResolver mMockDnsResolver; @Mock INetd mMockNetd; @Mock NetworkStackClient mNetworkStack; @@ -496,7 +499,7 @@ public class ConnectivityServiceTest { }; try { - doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected(); + doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected(any(), any()); doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt()); } catch (RemoteException e) { fail(e.getMessage()); @@ -1053,8 +1056,8 @@ public class ConnectivityServiceTest { public WrappedConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager, - IpConnectivityLog log, INetd netd) { - super(context, netManager, statsService, policyManager, log); + IpConnectivityLog log, INetd netd, IDnsResolver dnsResolver) { + super(context, netManager, statsService, policyManager, dnsResolver, log); mNetd = netd; mLingerDelayMs = TEST_LINGER_DELAY_MS; } @@ -1218,7 +1221,8 @@ public class ConnectivityServiceTest { mStatsService, mNpm, mock(IpConnectivityLog.class), - mMockNetd); + mMockNetd, + mMockDnsResolver); final ArgumentCaptor<INetworkPolicyListener> policyListenerCaptor = ArgumentCaptor.forClass(INetworkPolicyListener.class); @@ -4066,8 +4070,6 @@ public class ConnectivityServiceTest { // TODO: 1. Move this outside of ConnectivityServiceTest. // 2. Make test to verify that Nat-T keepalive socket is created by IpSecService. // 3. Mock ipsec service. - // 4. Find a free port instead of a fixed port. - final int srcPort = 12345; final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1"); @@ -4078,7 +4080,8 @@ public class ConnectivityServiceTest { final int invalidKaInterval = 9; final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE); - final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort); + final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(); + final int srcPort = testSocket.getPort(); LinkProperties lp = new LinkProperties(); lp.setInterfaceName("wlan12"); @@ -4198,6 +4201,7 @@ public class ConnectivityServiceTest { // Check that keepalive slots start from 1 and increment. The first one gets slot 1. mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + int srcPort2 = 0; try (SocketKeepalive ka = mCm.createSocketKeepalive( myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { ka.start(validKaInterval); @@ -4205,7 +4209,8 @@ public class ConnectivityServiceTest { // The second one gets slot 2. mWiFiNetworkAgent.setExpectedKeepaliveSlot(2); - final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(6789); + final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(); + srcPort2 = testSocket2.getPort(); TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback(executor); try (SocketKeepalive ka2 = mCm.createSocketKeepalive( myNet, testSocket2, myIPv4, dstIPv4, executor, callback2)) { @@ -4223,6 +4228,10 @@ public class ConnectivityServiceTest { } } + // Check that there is no port leaked after all keepalives and sockets are closed. + assertFalse(isUdpPortInUse(srcPort)); + assertFalse(isUdpPortInUse(srcPort2)); + mWiFiNetworkAgent.disconnect(); waitFor(mWiFiNetworkAgent.getDisconnectedCV()); mWiFiNetworkAgent = null; @@ -4305,7 +4314,6 @@ public class ConnectivityServiceTest { } private void doTestNattSocketKeepalivesFdWithExecutor(Executor executor) throws Exception { - final int srcPort = 12345; final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); final InetAddress anyIPv4 = InetAddress.getByName("0.0.0.0"); final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); @@ -4324,7 +4332,8 @@ public class ConnectivityServiceTest { // Prepare the target file descriptor, keep only one instance. final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE); - final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort); + final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(); + final int srcPort = testSocket.getPort(); final ParcelFileDescriptor testPfd = ParcelFileDescriptor.dup(testSocket.getFileDescriptor()); testSocket.close(); @@ -4772,14 +4781,14 @@ public class ConnectivityServiceTest { ArgumentCaptor<String[]> tlsServers = ArgumentCaptor.forClass(String[].class); // Clear any interactions that occur as a result of CS starting up. - reset(mNetworkManagementService); + reset(mMockDnsResolver); - final String[] EMPTY_STRING_ARRAY = new String[0]; mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); waitForIdle(); - verify(mNetworkManagementService, never()).setDnsConfigurationForNetwork( - anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), eq(EMPTY_STRING_ARRAY)); - verifyNoMoreInteractions(mNetworkManagementService); + verify(mMockDnsResolver, never()).setResolverConfiguration( + anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), + eq(EMPTY_STRING_ARRAY), eq(EMPTY_STRING_ARRAY)); + verifyNoMoreInteractions(mMockDnsResolver); final LinkProperties cellLp = new LinkProperties(); cellLp.setInterfaceName(MOBILE_IFNAME); @@ -4796,28 +4805,29 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false); waitForIdle(); // CS tells netd about the empty DNS config for this network. - verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( - anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), eq(EMPTY_STRING_ARRAY)); - reset(mNetworkManagementService); + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( + anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), + eq(EMPTY_STRING_ARRAY), eq(EMPTY_STRING_ARRAY)); + reset(mMockDnsResolver); cellLp.addDnsServer(InetAddress.getByName("2001:db8::1")); mCellNetworkAgent.sendLinkProperties(cellLp); waitForIdle(); - verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( anyInt(), mStringArrayCaptor.capture(), any(), any(), - eq(""), tlsServers.capture()); + eq(""), tlsServers.capture(), eq(EMPTY_STRING_ARRAY)); assertEquals(1, mStringArrayCaptor.getValue().length); assertTrue(ArrayUtils.contains(mStringArrayCaptor.getValue(), "2001:db8::1")); // Opportunistic mode. assertTrue(ArrayUtils.contains(tlsServers.getValue(), "2001:db8::1")); - reset(mNetworkManagementService); + reset(mMockDnsResolver); cellLp.addDnsServer(InetAddress.getByName("192.0.2.1")); mCellNetworkAgent.sendLinkProperties(cellLp); waitForIdle(); - verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( anyInt(), mStringArrayCaptor.capture(), any(), any(), - eq(""), tlsServers.capture()); + eq(""), tlsServers.capture(), eq(EMPTY_STRING_ARRAY)); assertEquals(2, mStringArrayCaptor.getValue().length); assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); @@ -4825,7 +4835,7 @@ public class ConnectivityServiceTest { assertEquals(2, tlsServers.getValue().length); assertTrue(ArrayUtils.containsAll(tlsServers.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); - reset(mNetworkManagementService); + reset(mMockDnsResolver); final String TLS_SPECIFIER = "tls.example.com"; final String TLS_SERVER6 = "2001:db8:53::53"; @@ -4835,22 +4845,21 @@ public class ConnectivityServiceTest { new PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS).toParcel()); waitForIdle(); - verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( anyInt(), mStringArrayCaptor.capture(), any(), any(), - eq(TLS_SPECIFIER), eq(TLS_SERVERS)); + eq(TLS_SPECIFIER), eq(TLS_SERVERS), eq(EMPTY_STRING_ARRAY)); assertEquals(2, mStringArrayCaptor.getValue().length); assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); - reset(mNetworkManagementService); + reset(mMockDnsResolver); } @Test public void testPrivateDnsSettingsChange() throws Exception { - final String[] EMPTY_STRING_ARRAY = new String[0]; ArgumentCaptor<String[]> tlsServers = ArgumentCaptor.forClass(String[].class); // Clear any interactions that occur as a result of CS starting up. - reset(mNetworkManagementService); + reset(mMockDnsResolver); // The default on Android is opportunistic mode ("Automatic"). setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); @@ -4863,9 +4872,10 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); waitForIdle(); // CS tells netd about the empty DNS config for this network. - verify(mNetworkManagementService, never()).setDnsConfigurationForNetwork( - anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), eq(EMPTY_STRING_ARRAY)); - verifyNoMoreInteractions(mNetworkManagementService); + verify(mMockDnsResolver, never()).setResolverConfiguration( + anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), + eq(EMPTY_STRING_ARRAY), eq(EMPTY_STRING_ARRAY)); + verifyNoMoreInteractions(mMockDnsResolver); final LinkProperties cellLp = new LinkProperties(); cellLp.setInterfaceName(MOBILE_IFNAME); @@ -4884,9 +4894,9 @@ public class ConnectivityServiceTest { mCellNetworkAgent.sendLinkProperties(cellLp); mCellNetworkAgent.connect(false); waitForIdle(); - verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( anyInt(), mStringArrayCaptor.capture(), any(), any(), - eq(""), tlsServers.capture()); + eq(""), tlsServers.capture(), eq(EMPTY_STRING_ARRAY)); assertEquals(2, mStringArrayCaptor.getValue().length); assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); @@ -4894,7 +4904,7 @@ public class ConnectivityServiceTest { assertEquals(2, tlsServers.getValue().length); assertTrue(ArrayUtils.containsAll(tlsServers.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); - reset(mNetworkManagementService); + reset(mMockDnsResolver); cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, mCellNetworkAgent); @@ -4906,26 +4916,26 @@ public class ConnectivityServiceTest { assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com"); - verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork( + verify(mMockDnsResolver, times(1)).setResolverConfiguration( anyInt(), mStringArrayCaptor.capture(), any(), any(), - eq(""), eq(EMPTY_STRING_ARRAY)); + eq(""), eq(EMPTY_STRING_ARRAY), eq(EMPTY_STRING_ARRAY)); assertEquals(2, mStringArrayCaptor.getValue().length); assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); - reset(mNetworkManagementService); + reset(mMockDnsResolver); cellNetworkCallback.assertNoCallback(); setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); - verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( anyInt(), mStringArrayCaptor.capture(), any(), any(), - eq(""), tlsServers.capture()); + eq(""), tlsServers.capture(), eq(EMPTY_STRING_ARRAY)); assertEquals(2, mStringArrayCaptor.getValue().length); assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); assertEquals(2, tlsServers.getValue().length); assertTrue(ArrayUtils.containsAll(tlsServers.getValue(), new String[]{"2001:db8::1", "192.0.2.1"})); - reset(mNetworkManagementService); + reset(mMockDnsResolver); cellNetworkCallback.assertNoCallback(); setPrivateDnsSettings(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, "strict.example.com"); @@ -5756,6 +5766,7 @@ public class ConnectivityServiceTest { cellLp.addRoute(new RouteInfo((IpPrefix) null, myIpv6.getAddress(), MOBILE_IFNAME)); cellLp.addRoute(new RouteInfo(myIpv6, null, MOBILE_IFNAME)); reset(mNetworkManagementService); + reset(mMockDnsResolver); when(mNetworkManagementService.getInterfaceConfig(CLAT_PREFIX + MOBILE_IFNAME)) .thenReturn(getClatInterfaceConfig(myIpv4)); @@ -5763,7 +5774,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.sendLinkProperties(cellLp); mCellNetworkAgent.connect(true); networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); - verify(mMockNetd, times(1)).resolverStartPrefix64Discovery(cellNetId); + verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); // Switching default network updates TCP buffer sizes. verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES); @@ -5773,17 +5784,22 @@ public class ConnectivityServiceTest { cellLp.addLinkAddress(myIpv4); mCellNetworkAgent.sendLinkProperties(cellLp); networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); - verify(mMockNetd, times(1)).resolverStopPrefix64Discovery(cellNetId); + verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId); + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( + eq(cellNetId), eq(EMPTY_STRING_ARRAY), any(), any(), + eq(""), eq(EMPTY_STRING_ARRAY), eq(EMPTY_STRING_ARRAY)); verifyNoMoreInteractions(mMockNetd); + verifyNoMoreInteractions(mMockDnsResolver); reset(mMockNetd); + reset(mMockDnsResolver); // Remove IPv4 address. Expect prefix discovery to be started again. cellLp.removeLinkAddress(myIpv4); cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME)); mCellNetworkAgent.sendLinkProperties(cellLp); networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); - verify(mMockNetd, times(1)).resolverStartPrefix64Discovery(cellNetId); + verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); // When NAT64 prefix discovery succeeds, LinkProperties are updated and clatd is started. Nat464Xlat clat = mService.getNat464Xlat(mCellNetworkAgent); @@ -5813,6 +5829,12 @@ public class ConnectivityServiceTest { assertNotEquals(stackedLpsAfterChange, Collections.EMPTY_LIST); assertEquals(makeClatLinkProperties(myIpv4), stackedLpsAfterChange.get(0)); + verify(mMockDnsResolver, times(1)).setResolverConfiguration( + eq(cellNetId), mStringArrayCaptor.capture(), any(), any(), + eq(""), eq(EMPTY_STRING_ARRAY), eq(EMPTY_STRING_ARRAY)); + assertEquals(1, mStringArrayCaptor.getValue().length); + assertTrue(ArrayUtils.contains(mStringArrayCaptor.getValue(), "8.8.8.8")); + // Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked // linkproperties are cleaned up. cellLp.addLinkAddress(myIpv4); @@ -5820,7 +5842,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.sendLinkProperties(cellLp); networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); - verify(mMockNetd, times(1)).resolverStopPrefix64Discovery(cellNetId); + verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId); // As soon as stop is called, the linkproperties lose the stacked interface. networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); @@ -5835,7 +5857,9 @@ public class ConnectivityServiceTest { networkCallback.assertNoCallback(); verifyNoMoreInteractions(mMockNetd); + verifyNoMoreInteractions(mMockDnsResolver); reset(mMockNetd); + reset(mMockDnsResolver); // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone. mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */, @@ -5849,7 +5873,7 @@ public class ConnectivityServiceTest { cellLp.removeDnsServer(InetAddress.getByName("8.8.8.8")); mCellNetworkAgent.sendLinkProperties(cellLp); networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); - verify(mMockNetd, times(1)).resolverStartPrefix64Discovery(cellNetId); + verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */, kNat64PrefixString, 96); networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); @@ -5932,6 +5956,7 @@ public class ConnectivityServiceTest { // Disconnect cell reset(mNetworkManagementService); + reset(mMockNetd); mCellNetworkAgent.disconnect(); networkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); // LOST callback is triggered earlier than removing idle timer. Broadcast should also be @@ -5939,8 +5964,9 @@ public class ConnectivityServiceTest { // unexpectedly before network being removed. waitForIdle(); verify(mNetworkManagementService, times(0)).removeIdleTimer(eq(MOBILE_IFNAME)); - verify(mNetworkManagementService, times(1)).removeNetwork( - eq(mCellNetworkAgent.getNetwork().netId)); + verify(mMockNetd, times(1)).networkDestroy(eq(mCellNetworkAgent.getNetwork().netId)); + verify(mMockDnsResolver, times(1)) + .clearResolverConfiguration(eq(mCellNetworkAgent.getNetwork().netId)); // Disconnect wifi ConditionVariable cv = waitForConnectivityBroadcasts(1); diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java index 15ba43df832f..8fa0ab979a54 100644 --- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java @@ -29,13 +29,13 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; import android.content.Context; +import android.net.IDnsResolver; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.RouteInfo; import android.net.shared.PrivateDnsConfig; -import android.os.INetworkManagementService; import android.provider.Settings; import android.test.mock.MockContentResolver; @@ -73,7 +73,7 @@ public class DnsManagerTest { MockContentResolver mContentResolver; @Mock Context mCtx; - @Mock INetworkManagementService mNMService; + @Mock IDnsResolver mMockDnsResolver; @Mock MockableSystemProperties mSystemProperties; @Before @@ -83,7 +83,7 @@ public class DnsManagerTest { mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); when(mCtx.getContentResolver()).thenReturn(mContentResolver); - mDnsManager = new DnsManager(mCtx, mNMService, mSystemProperties); + mDnsManager = new DnsManager(mCtx, mMockDnsResolver, mSystemProperties); // Clear the private DNS settings Settings.Global.putString(mContentResolver, PRIVATE_DNS_DEFAULT_MODE, ""); diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java index 6de4aa1be1ea..142769f61335 100644 --- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java @@ -32,6 +32,7 @@ import android.app.PendingIntent; import android.content.Context; import android.content.res.Resources; import android.net.ConnectivityManager; +import android.net.IDnsResolver; import android.net.INetd; import android.net.Network; import android.net.NetworkCapabilities; @@ -69,6 +70,7 @@ public class LingerMonitorTest { LingerMonitor mMonitor; @Mock ConnectivityService mConnService; + @Mock IDnsResolver mDnsResolver; @Mock INetd mNetd; @Mock INetworkManagementService mNMS; @Mock Context mCtx; @@ -353,7 +355,7 @@ public class LingerMonitorTest { caps.addCapability(0); caps.addTransportType(transport); NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null, - caps, 50, mCtx, null, mMisc, mConnService, mNetd, mNMS, + caps, 50, mCtx, null, mMisc, mConnService, mNetd, mDnsResolver, mNMS, NetworkFactory.SerialNumber.NONE); nai.everValidated = true; return nai; diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java index cc09fb7ba66f..b709af1a02f1 100644 --- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java +++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.net.ConnectivityManager; +import android.net.IDnsResolver; import android.net.INetd; import android.net.InterfaceConfiguration; import android.net.IpPrefix; @@ -63,6 +64,7 @@ public class Nat464XlatTest { @Mock ConnectivityService mConnectivity; @Mock NetworkMisc mMisc; + @Mock IDnsResolver mDnsResolver; @Mock INetd mNetd; @Mock INetworkManagementService mNms; @Mock InterfaceConfiguration mConfig; @@ -72,7 +74,7 @@ public class Nat464XlatTest { Handler mHandler; Nat464Xlat makeNat464Xlat() { - return new Nat464Xlat(mNai, mNetd, mNms) { + return new Nat464Xlat(mNai, mNetd, mDnsResolver, mNms) { @Override protected int getNetId() { return NETID; } @@ -205,7 +207,7 @@ public class Nat464XlatTest { verify(mNms).unregisterObserver(eq(nat)); assertTrue(c.getValue().getStackedLinks().isEmpty()); assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); - verify(mNetd).resolverStopPrefix64Discovery(eq(NETID)); + verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); assertIdle(nat); // Stacked interface removed notification arrives and is ignored. @@ -331,7 +333,7 @@ public class Nat464XlatTest { verify(mNetd).clatdStop(eq(BASE_IFACE)); verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture()); verify(mNms).unregisterObserver(eq(nat)); - verify(mNetd).resolverStopPrefix64Discovery(eq(NETID)); + verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); assertTrue(c.getValue().getStackedLinks().isEmpty()); assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); assertIdle(nat); @@ -358,7 +360,7 @@ public class Nat464XlatTest { verify(mNetd).clatdStop(eq(BASE_IFACE)); verify(mNms).unregisterObserver(eq(nat)); - verify(mNetd).resolverStopPrefix64Discovery(eq(NETID)); + verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); assertIdle(nat); // In-flight interface up notification arrives: no-op @@ -390,7 +392,7 @@ public class Nat464XlatTest { verify(mNetd).clatdStop(eq(BASE_IFACE)); verify(mNms).unregisterObserver(eq(nat)); - verify(mNetd).resolverStopPrefix64Discovery(eq(NETID)); + verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); assertIdle(nat); verifyNoMoreInteractions(mNetd, mNms, mConnectivity); diff --git a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java index bac509802258..d28ab708f051 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -16,6 +16,7 @@ package com.android.server.connectivity.tethering; +import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; import static android.net.ConnectivityManager.TETHERING_USB; import static android.net.ConnectivityManager.TETHERING_WIFI; import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; @@ -30,6 +31,8 @@ import static org.junit.Assert.fail; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ContentResolver; @@ -72,12 +75,14 @@ public final class EntitlementManagerTest { private static final int EVENT_EM_UPDATE = 1; private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; + private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app"; @Mock private CarrierConfigManager mCarrierConfigManager; @Mock private Context mContext; @Mock private MockableSystemProperties mSystemProperties; @Mock private Resources mResources; @Mock private SharedLog mLog; + @Mock private EntitlementManager.OnUiEntitlementFailedListener mEntitlementFailedListener; // Like so many Android system APIs, these cannot be mocked because it is marked final. // We have to use the real versions. @@ -107,18 +112,31 @@ public final class EntitlementManagerTest { public class WrappedEntitlementManager extends EntitlementManager { public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; - public boolean everRunUiEntitlement = false; + public int uiProvisionCount = 0; + public int silentProvisionCount = 0; public WrappedEntitlementManager(Context ctx, StateMachine target, - SharedLog log, MockableSystemProperties systemProperties) { - super(ctx, target, log, systemProperties); + SharedLog log, int what, MockableSystemProperties systemProperties) { + super(ctx, target, log, what, systemProperties); + } + + public void reset() { + fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; + uiProvisionCount = 0; + silentProvisionCount = 0; } @Override protected void runUiTetherProvisioning(int type, ResultReceiver receiver) { - everRunUiEntitlement = true; + uiProvisionCount++; receiver.send(fakeEntitlementResult, null); } + + @Override + protected void runSilentTetherProvisioning(int type) { + silentProvisionCount++; + addDownstreamMapping(type, fakeEntitlementResult); + } } @Before @@ -141,7 +159,9 @@ public final class EntitlementManagerTest { mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); mMockContext = new MockContext(mContext); mSM = new TestStateMachine(); - mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, mSystemProperties); + mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE, + mSystemProperties); + mEnMgr.setOnUiEntitlementFailedListener(mEntitlementFailedListener); mEnMgr.updateConfiguration( new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)); } @@ -158,7 +178,9 @@ public final class EntitlementManagerTest { // Produce some acceptable looking provision app setting if requested. when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) .thenReturn(PROVISIONING_APP_NAME); - // Don't disable tethering provisioning unless requested. + when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui)) + .thenReturn(PROVISIONING_NO_UI_APP_NAME); + // Don't disable tethering provisioning unless requested. when(mSystemProperties.getBoolean(eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), anyBoolean())).thenReturn(false); // Act like the CarrierConfigManager is present and ready unless told otherwise. @@ -229,7 +251,6 @@ public final class EntitlementManagerTest { final CountDownLatch mCallbacklatch = new CountDownLatch(1); // 1. Entitlement check is not required. mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.everRunUiEntitlement = false; ResultReceiver receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -238,14 +259,15 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); - assertFalse(mEnMgr.everRunUiEntitlement); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); setupForRequiredProvisioning(); mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)); // 2. No cache value and don't need to run entitlement check. - mEnMgr.everRunUiEntitlement = false; receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -254,11 +276,12 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); - assertFalse(mEnMgr.everRunUiEntitlement); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); // 3. No cache value and ui entitlement check is needed. mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.everRunUiEntitlement = false; receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -269,10 +292,10 @@ public final class EntitlementManagerTest { mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); - assertTrue(mEnMgr.everRunUiEntitlement); + assertEquals(1, mEnMgr.uiProvisionCount); + mEnMgr.reset(); // 4. Cache value is TETHER_ERROR_PROVISION_FAILED and don't need to run entitlement check. mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.everRunUiEntitlement = false; receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -281,11 +304,12 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); - assertFalse(mEnMgr.everRunUiEntitlement); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); // 5. Cache value is TETHER_ERROR_PROVISION_FAILED and ui entitlement check is needed. mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.everRunUiEntitlement = false; receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -296,10 +320,10 @@ public final class EntitlementManagerTest { mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); - assertTrue(mEnMgr.everRunUiEntitlement); + assertEquals(1, mEnMgr.uiProvisionCount); + mEnMgr.reset(); // 6. Cache value is TETHER_ERROR_NO_ERROR. mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.everRunUiEntitlement = false; receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -308,10 +332,11 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); - assertFalse(mEnMgr.everRunUiEntitlement); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); // 7. Test get value for other downstream type. - mEnMgr.everRunUiEntitlement = false; receiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -320,19 +345,152 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_USB, receiver, false); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); - assertFalse(mEnMgr.everRunUiEntitlement); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); } void callbackTimeoutHelper(final CountDownLatch latch) throws Exception { if (!latch.await(1, TimeUnit.SECONDS)) { - fail("Timout, fail to recieve callback"); + fail("Timout, fail to receive callback"); } } + + @Test + public void verifyPermissionResult() { + setupForRequiredProvisioning(); + mEnMgr.notifyUpstream(true); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); + mLooper.dispatchAll(); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + } + + @Test + public void verifyPermissionIfAllNotApproved() { + setupForRequiredProvisioning(); + mEnMgr.notifyUpstream(true); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + } + + @Test + public void verifyPermissionIfAnyApproved() { + setupForRequiredProvisioning(); + mEnMgr.notifyUpstream(true); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mLooper.dispatchAll(); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); + mLooper.dispatchAll(); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + + } + + @Test + public void testRunTetherProvisioning() { + setupForRequiredProvisioning(); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + // 1. start ui provisioning, upstream is mobile + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); + mLooper.dispatchAll(); + assertEquals(1, mEnMgr.uiProvisionCount); + assertEquals(0, mEnMgr.silentProvisionCount); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.reset(); + // 2. start no-ui provisioning + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, false); + mLooper.dispatchAll(); + assertEquals(0, mEnMgr.uiProvisionCount); + assertEquals(1, mEnMgr.silentProvisionCount); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.reset(); + // 3. tear down mobile, then start ui provisioning + mEnMgr.notifyUpstream(false); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true); + mLooper.dispatchAll(); + assertEquals(0, mEnMgr.uiProvisionCount); + assertEquals(0, mEnMgr.silentProvisionCount); + mEnMgr.reset(); + // 4. switch upstream back to mobile + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + assertEquals(1, mEnMgr.uiProvisionCount); + assertEquals(0, mEnMgr.silentProvisionCount); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.reset(); + // 5. tear down mobile, then switch SIM + mEnMgr.notifyUpstream(false); + mLooper.dispatchAll(); + mEnMgr.reevaluateSimCardProvisioning(); + assertEquals(0, mEnMgr.uiProvisionCount); + assertEquals(0, mEnMgr.silentProvisionCount); + mEnMgr.reset(); + // 6. switch upstream back to mobile again + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + assertEquals(0, mEnMgr.uiProvisionCount); + assertEquals(3, mEnMgr.silentProvisionCount); + mEnMgr.reset(); + } + + @Test + public void testCallStopTetheringWhenUiProvisioningFail() { + setupForRequiredProvisioning(); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, + INVALID_SUBSCRIPTION_ID)); + verify(mEntitlementFailedListener, times(0)).onUiEntitlementFailed(TETHERING_WIFI); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertEquals(1, mEnMgr.uiProvisionCount); + verify(mEntitlementFailedListener, times(1)).onUiEntitlementFailed(TETHERING_WIFI); + } + + public class TestStateMachine extends StateMachine { public final ArrayList<Message> messages = new ArrayList<>(); - private final State mLoggingState = - new EntitlementManagerTest.TestStateMachine.LoggingState(); + private final State + mLoggingState = new EntitlementManagerTest.TestStateMachine.LoggingState(); class LoggingState extends State { @Override public void enter() { diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java index 5a1f853e75a9..0d276cbd1b85 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java @@ -90,6 +90,7 @@ public class UpstreamNetworkMonitorTest { private static final NetworkRequest mDefaultRequest = new NetworkRequest.Builder().build(); @Mock private Context mContext; + @Mock private EntitlementManager mEntitleMgr; @Mock private IConnectivityManager mCS; @Mock private SharedLog mLog; @@ -103,6 +104,7 @@ public class UpstreamNetworkMonitorTest { reset(mCS); reset(mLog); when(mLog.forSubComponent(anyString())).thenReturn(mLog); + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); mCM = spy(new TestConnectivityManager(mContext, mCS)); mSM = new TestStateMachine(); @@ -138,7 +140,7 @@ public class UpstreamNetworkMonitorTest { @Test public void testDefaultNetworkIsTracked() throws Exception { assertTrue(mCM.hasNoCallbacks()); - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); assertEquals(1, mCM.trackingDefault.size()); @@ -151,7 +153,7 @@ public class UpstreamNetworkMonitorTest { public void testListensForAllNetworks() throws Exception { assertTrue(mCM.listening.isEmpty()); - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); assertFalse(mCM.listening.isEmpty()); assertTrue(mCM.isListeningForAll()); @@ -162,7 +164,7 @@ public class UpstreamNetworkMonitorTest { @Test public void testCallbacksRegistered() { - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); verify(mCM, times(1)).requestNetwork( eq(mDefaultRequest), any(NetworkCallback.class), any(Handler.class)); mUNM.startObserveAllNetworks(); @@ -285,7 +287,7 @@ public class UpstreamNetworkMonitorTest { final Collection<Integer> preferredTypes = new ArrayList<>(); preferredTypes.add(TYPE_WIFI); - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); // There are no networks, so there is nothing to select. assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); @@ -319,6 +321,14 @@ public class UpstreamNetworkMonitorTest { NetworkRequest netReq = (NetworkRequest) mCM.requested.values().toArray()[0]; assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)); assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); + // mobile is not permitted, we should not use HIPRI. + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); + assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); + assertEquals(0, mCM.requested.size()); + // mobile change back to permitted, HIRPI should come back + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); + assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI, + mUNM.selectPreferredUpstreamType(preferredTypes)); wifiAgent.fakeConnect(); // WiFi is up, and we should prefer it over cell. @@ -347,11 +357,19 @@ public class UpstreamNetworkMonitorTest { netReq = (NetworkRequest) mCM.requested.values().toArray()[0]; assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)); assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); + // mobile is not permitted, we should not use DUN. + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); + assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); + assertEquals(0, mCM.requested.size()); + // mobile change back to permitted, DUN should come back + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); + assertSatisfiesLegacyType(TYPE_MOBILE_DUN, + mUNM.selectPreferredUpstreamType(preferredTypes)); } @Test public void testGetCurrentPreferredUpstream() throws Exception { - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); mUNM.updateMobileRequiresDun(false); @@ -361,37 +379,46 @@ public class UpstreamNetworkMonitorTest { mCM.makeDefaultNetwork(cellAgent); assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - // [1] WiFi connects but not validated/promoted to default -> mobile selected. + // [1] Mobile connects but not permitted -> null selected + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); + assertEquals(null, mUNM.getCurrentPreferredUpstream()); + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); + + // [2] WiFi connects but not validated/promoted to default -> mobile selected. final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); wifiAgent.fakeConnect(); assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - // [2] WiFi validates and is promoted to the default network -> WiFi selected. + // [3] WiFi validates and is promoted to the default network -> WiFi selected. mCM.makeDefaultNetwork(wifiAgent); assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - // [3] DUN required, no other changes -> WiFi still selected + // [4] DUN required, no other changes -> WiFi still selected mUNM.updateMobileRequiresDun(true); assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network); - // [4] WiFi no longer validated, mobile becomes default, DUN required -> null selected. + // [5] WiFi no longer validated, mobile becomes default, DUN required -> null selected. mCM.makeDefaultNetwork(cellAgent); assertEquals(null, mUNM.getCurrentPreferredUpstream()); // TODO: make sure that a DUN request has been filed. This is currently // triggered by code over in Tethering, but once that has been moved // into UNM we should test for this here. - // [5] DUN network arrives -> DUN selected + // [6] DUN network arrives -> DUN selected final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN); dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET); dunAgent.fakeConnect(); assertEquals(dunAgent.networkId, mUNM.getCurrentPreferredUpstream().network); + + // [7] Mobile is not permitted -> null selected + when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); + assertEquals(null, mUNM.getCurrentPreferredUpstream()); } @Test public void testLocalPrefixes() throws Exception { - mUNM.startTrackDefaultNetwork(mDefaultRequest); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); mUNM.startObserveAllNetworks(); // [0] Test minimum set of local prefixes. @@ -492,6 +519,26 @@ public class UpstreamNetworkMonitorTest { assertTrue(local.isEmpty()); } + @Test + public void testSelectMobileWhenMobileIsNotDefault() { + final Collection<Integer> preferredTypes = new ArrayList<>(); + // Mobile has higher pirority than wifi. + preferredTypes.add(TYPE_MOBILE_HIPRI); + preferredTypes.add(TYPE_WIFI); + mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); + mUNM.startObserveAllNetworks(); + // Setup wifi and make wifi as default network. + final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); + wifiAgent.fakeConnect(); + mCM.makeDefaultNetwork(wifiAgent); + // Setup mobile network. + final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); + cellAgent.fakeConnect(); + + assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI, + mUNM.selectPreferredUpstreamType(preferredTypes)); + verify(mEntitleMgr, times(1)).maybeRunProvisioning(); + } private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) { if (legacyType == TYPE_NONE) { assertTrue(ns == null); diff --git a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java index dc2018543050..fb84611cb662 100644 --- a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java +++ b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java @@ -57,6 +57,7 @@ public class NetworkAttributesTest { final NetworkAttributes na = new NetworkAttributes( (Inet4Address) Inet4Address.getByAddress(new byte[] {1, 2, 3, 4}), + System.currentTimeMillis() + 7_200_000, "some hint", Arrays.asList(Inet4Address.getByAddress(new byte[] {5, 6, 7, 8}), Inet4Address.getByAddress(new byte[] {9, 0, 1, 2})), diff --git a/tests/testables/src/android/testing/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java index fff9635992d4..e2668bc4281f 100644 --- a/tests/testables/src/android/testing/TestableContext.java +++ b/tests/testables/src/android/testing/TestableContext.java @@ -296,13 +296,13 @@ public class TestableContext extends ContextWrapper implements TestRule { @Override public void registerComponentCallbacks(ComponentCallbacks callback) { if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable()); - super.registerComponentCallbacks(callback); + getBaseContext().registerComponentCallbacks(callback); } @Override public void unregisterComponentCallbacks(ComponentCallbacks callback) { if (mComponent != null) mComponent.getLeakInfo(callback).clearAllocations(); - super.unregisterComponentCallbacks(callback); + getBaseContext().unregisterComponentCallbacks(callback); } public TestablePermissions getTestablePermissions() { diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 53361414e9b8..857792192902 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -217,6 +217,29 @@ TEST_F(ResourceParserTest, ParseStyledStringWithWhitespace) { EXPECT_THAT(str->value->spans[1].last_char, Eq(13u)); } +TEST_F(ResourceParserTest, ParseStringTranslatableAttribute) { + // If there is no translate attribute the default is 'true' + EXPECT_TRUE(TestParse(R"(<string name="foo1">Translate</string>)")); + String* str = test::GetValue<String>(&table_, "string/foo1"); + ASSERT_THAT(str, NotNull()); + ASSERT_TRUE(str->IsTranslatable()); + + // Explicit 'true' translate attribute + EXPECT_TRUE(TestParse(R"(<string name="foo2" translatable="true">Translate</string>)")); + str = test::GetValue<String>(&table_, "string/foo2"); + ASSERT_THAT(str, NotNull()); + ASSERT_TRUE(str->IsTranslatable()); + + // Explicit 'false' translate attribute + EXPECT_TRUE(TestParse(R"(<string name="foo3" translatable="false">Do not translate</string>)")); + str = test::GetValue<String>(&table_, "string/foo3"); + ASSERT_THAT(str, NotNull()); + ASSERT_FALSE(str->IsTranslatable()); + + // Invalid value for the translate attribute, should be boolean ('true' or 'false') + EXPECT_FALSE(TestParse(R"(<string name="foo4" translatable="yes">Translate</string>)")); +} + TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) { std::string input = R"( <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 2ec1ab31a58c..9b81369fa9f0 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -143,6 +143,8 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer, const std::string& output_path) { TRACE_CALL(); + // Filenames starting with "donottranslate" are not localizable + bool translatable_file = path_data.name.find("donottranslate") != 0; ResourceTable table; { auto fin = file->OpenInputStream(); @@ -157,9 +159,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, ResourceParserOptions parser_options; parser_options.error_on_positional_arguments = !options.legacy_mode; - - // If the filename includes donottranslate, then the default translatable is false. - parser_options.translatable = path_data.name.find("donottranslate") == std::string::npos; + parser_options.translatable = translatable_file; // If visibility was forced, we need to use it when creating a new resource and also error if // we try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags. @@ -172,7 +172,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, } } - if (options.pseudolocalize) { + if (options.pseudolocalize && translatable_file) { // Generate pseudo-localized strings (en-XA and ar-XB). // These are created as weak symbols, and are only generated from default // configuration diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp index c0c05cda35e7..5f637bd8d582 100644 --- a/tools/aapt2/cmd/Compile_test.cpp +++ b/tools/aapt2/cmd/Compile_test.cpp @@ -27,6 +27,8 @@ namespace aapt { +using CompilerTest = CommandTestFixture; + std::string BuildPath(std::vector<std::string> args) { std::string out; if (args.empty()) { @@ -51,7 +53,7 @@ int TestCompile(const std::string& path, const std::string& outDir, bool legacy, return CompileCommand(&diag).Execute(args, &std::cerr); } -TEST(CompilerTest, MultiplePeriods) { +TEST_F(CompilerTest, MultiplePeriods) { StdErrDiagnostics diag; std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()), @@ -108,7 +110,7 @@ TEST(CompilerTest, MultiplePeriods) { ASSERT_EQ(::android::base::utf8::unlink(path5_out.c_str()), 0); } -TEST(CompilerTest, DirInput) { +TEST_F(CompilerTest, DirInput) { StdErrDiagnostics diag; std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()), @@ -138,7 +140,7 @@ TEST(CompilerTest, DirInput) { ASSERT_EQ(::android::base::utf8::unlink(kOutputFlata.c_str()), 0); } -TEST(CompilerTest, ZipInput) { +TEST_F(CompilerTest, ZipInput) { StdErrDiagnostics diag; std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const std::string kResZip = @@ -169,4 +171,86 @@ TEST(CompilerTest, ZipInput) { ASSERT_EQ(::android::base::utf8::unlink(kOutputFlata.c_str()), 0); } -} // namespace aapt
\ No newline at end of file +/* + * This tests the "protection" from pseudo-translation of + * non-translatable files (starting with 'donotranslate') + * and strings (with the translatable="false" attribute) + * + * We check 4 string files, 2 translatable, and 2 not (based on file name) + * Each file contains 2 strings, one translatable, one not (attribute based) + * Each of these files are compiled and linked into one .apk, then we load the + * strings from the apk and check if there are pseudo-translated strings. + */ + +// Using 000 and 111 because they are not changed by pseudo-translation, +// making our life easier. +constexpr static const char sTranslatableXmlContent[] = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<resources>" + " <string name=\"normal\">000</string>" + " <string name=\"non_translatable\" translatable=\"false\">111</string>" + "</resources>"; + +static void AssertTranslations(CommandTestFixture *ctf, std::string file_name, + std::vector<std::string> expected) { + + StdErrDiagnostics diag; + + const std::string source_file = ctf->GetTestPath("/res/values/" + file_name + ".xml"); + const std::string compiled_files_dir = ctf->GetTestPath("/compiled_" + file_name); + const std::string out_apk = ctf->GetTestPath("/" + file_name + ".apk"); + + CHECK(ctf->WriteFile(source_file, sTranslatableXmlContent)); + CHECK(file::mkdirs(compiled_files_dir.data())); + + ASSERT_EQ(CompileCommand(&diag).Execute({ + source_file, + "-o", compiled_files_dir, + "-v", + "--pseudo-localize" + }, &std::cerr), 0); + + ASSERT_TRUE(ctf->Link({ + "--manifest", ctf->GetDefaultManifest(), + "-o", out_apk + }, compiled_files_dir, &diag)); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + + ResourceTable* table = apk->GetResourceTable(); + ASSERT_NE(table, nullptr); + table->string_pool.Sort(); + + const std::vector<std::unique_ptr<StringPool::Entry>>& pool_strings = + table->string_pool.strings(); + + // The actual / expected vectors have the same size + const size_t pool_size = pool_strings.size(); + ASSERT_EQ(pool_size, expected.size()); + + for (size_t i = 0; i < pool_size; i++) { + std::string actual = pool_strings[i]->value; + ASSERT_EQ(actual, expected[i]); + } +} + +TEST_F(CompilerTest, DoNotTranslateTest) { + // The first string (000) is translatable, the second is not + // ar-XB uses "\u200F\u202E...\u202C\u200F" + std::vector<std::string> expected_translatable = { + "000", "111", // default locale + "[000 one]", // en-XA + "\xE2\x80\x8F\xE2\x80\xAE" "000" "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB + }; + AssertTranslations(this, "foo", expected_translatable); + AssertTranslations(this, "foo_donottranslate", expected_translatable); + + // No translatable strings because these are non-translatable files + std::vector<std::string> expected_not_translatable = { + "000", "111", // default locale + }; + AssertTranslations(this, "donottranslate", expected_not_translatable); + AssertTranslations(this, "donottranslate_foo", expected_not_translatable); +} + +} // namespace aapt diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index e17fb4783a45..92f1ddb292e1 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -2314,7 +2314,7 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Visit(xml::Elemen int DumpManifest(LoadedApk* apk, DumpManifestOptions& options, text::Printer* printer, IDiagnostics* diag) { ManifestExtractor extractor(apk, options); - return extractor.Dump(printer, diag); + return extractor.Dump(printer, diag) ? 0 : 1; } } // namespace aapt diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 78967e4a4a9f..be227e79909e 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -2190,22 +2190,7 @@ public class WifiConfiguration implements Parcelable { key += "-" + Integer.toString(UserHandle.getUserId(creatorUid)); } } else { - if (allowedKeyManagement.get(KeyMgmt.WPA_PSK)) { - key = SSID + KeyMgmt.strings[KeyMgmt.WPA_PSK]; - } else if (allowedKeyManagement.get(KeyMgmt.WPA_EAP) || - allowedKeyManagement.get(KeyMgmt.IEEE8021X)) { - key = SSID + KeyMgmt.strings[KeyMgmt.WPA_EAP]; - } else if (wepKeys[0] != null) { - key = SSID + "WEP"; - } else if (allowedKeyManagement.get(KeyMgmt.OWE)) { - key = SSID + KeyMgmt.strings[KeyMgmt.OWE]; - } else if (allowedKeyManagement.get(KeyMgmt.SAE)) { - key = SSID + KeyMgmt.strings[KeyMgmt.SAE]; - } else if (allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) { - key = SSID + KeyMgmt.strings[KeyMgmt.SUITE_B_192]; - } else { - key = SSID + KeyMgmt.strings[KeyMgmt.NONE]; - } + key = getSsidAndSecurityTypeString(); if (!shared) { key += "-" + Integer.toString(UserHandle.getUserId(creatorUid)); } @@ -2215,6 +2200,30 @@ public class WifiConfiguration implements Parcelable { } /** @hide + * return the SSID + security type in String format. + */ + public String getSsidAndSecurityTypeString() { + String key; + if (allowedKeyManagement.get(KeyMgmt.WPA_PSK)) { + key = SSID + KeyMgmt.strings[KeyMgmt.WPA_PSK]; + } else if (allowedKeyManagement.get(KeyMgmt.WPA_EAP) + || allowedKeyManagement.get(KeyMgmt.IEEE8021X)) { + key = SSID + KeyMgmt.strings[KeyMgmt.WPA_EAP]; + } else if (wepKeys[0] != null) { + key = SSID + "WEP"; + } else if (allowedKeyManagement.get(KeyMgmt.OWE)) { + key = SSID + KeyMgmt.strings[KeyMgmt.OWE]; + } else if (allowedKeyManagement.get(KeyMgmt.SAE)) { + key = SSID + KeyMgmt.strings[KeyMgmt.SAE]; + } else if (allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) { + key = SSID + KeyMgmt.strings[KeyMgmt.SUITE_B_192]; + } else { + key = SSID + KeyMgmt.strings[KeyMgmt.NONE]; + } + return key; + } + + /** @hide * get configKey, force calculating the config string */ public String configKey() { diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java index d927052e0b2a..ba9fc786afe7 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -348,4 +348,47 @@ public class WifiConfigurationTest { } assertTrue(exceptionThrown); } + + /** + * Verifies that getSsidAndSecurityTypeString returns the correct String for networks of + * various different security types + */ + @Test + public void testGetSsidAndSecurityTypeString() { + WifiConfiguration config = new WifiConfiguration(); + final String mSsid = "TestAP"; + config.SSID = mSsid; + + // Test various combinations + config.allowedKeyManagement.set(KeyMgmt.WPA_PSK); + assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.WPA_PSK], + config.getSsidAndSecurityTypeString()); + + config.allowedKeyManagement.clear(); + config.allowedKeyManagement.set(KeyMgmt.WPA_EAP); + assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.WPA_EAP], + config.getSsidAndSecurityTypeString()); + + config.wepKeys[0] = "TestWep"; + config.allowedKeyManagement.clear(); + assertEquals(mSsid + "WEP", config.getSsidAndSecurityTypeString()); + + config.wepKeys[0] = null; + config.allowedKeyManagement.clear(); + config.allowedKeyManagement.set(KeyMgmt.OWE); + assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.OWE], config.getSsidAndSecurityTypeString()); + + config.allowedKeyManagement.clear(); + config.allowedKeyManagement.set(KeyMgmt.SAE); + assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.SAE], config.getSsidAndSecurityTypeString()); + + config.allowedKeyManagement.clear(); + config.allowedKeyManagement.set(KeyMgmt.SUITE_B_192); + assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.SUITE_B_192], + config.getSsidAndSecurityTypeString()); + + config.allowedKeyManagement.clear(); + config.allowedKeyManagement.set(KeyMgmt.NONE); + assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.NONE], config.getSsidAndSecurityTypeString()); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 600abc927b7f..fa17db1e956c 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -1382,4 +1382,60 @@ i * Verify that a call to cancel WPS immediately returns a failure. r.run(); } } + + /** + * Test behavior of isEnhancedOpenSupported + * @throws Exception + */ + @Test + public void testIsEnhancedOpenSupported() throws Exception { + when(mWifiService.getSupportedFeatures()) + .thenReturn(new Long(WifiManager.WIFI_FEATURE_OWE)); + assertTrue(mWifiManager.isEnhancedOpenSupported()); + when(mWifiService.getSupportedFeatures()) + .thenReturn(new Long(~WifiManager.WIFI_FEATURE_OWE)); + assertFalse(mWifiManager.isEnhancedOpenSupported()); + } + + /** + * Test behavior of isWpa3SaeSupported + * @throws Exception + */ + @Test + public void testIsWpa3SaeSupported() throws Exception { + when(mWifiService.getSupportedFeatures()) + .thenReturn(new Long(WifiManager.WIFI_FEATURE_WPA3_SAE)); + assertTrue(mWifiManager.isWpa3SaeSupported()); + when(mWifiService.getSupportedFeatures()) + .thenReturn(new Long(~WifiManager.WIFI_FEATURE_WPA3_SAE)); + assertFalse(mWifiManager.isWpa3SaeSupported()); + } + + /** + * Test behavior of isWpa3SuiteBSupported + * @throws Exception + */ + @Test + public void testIsWpa3SuiteBSupported() throws Exception { + when(mWifiService.getSupportedFeatures()) + .thenReturn(new Long(WifiManager.WIFI_FEATURE_WPA3_SUITE_B)); + assertTrue(mWifiManager.isWpa3SuiteBSupported()); + when(mWifiService.getSupportedFeatures()) + .thenReturn(new Long(~WifiManager.WIFI_FEATURE_WPA3_SUITE_B)); + assertFalse(mWifiManager.isWpa3SuiteBSupported()); + } + + /** + * Test behavior of isEasyConnectSupported + * @throws Exception + */ + @Test + public void testIsEasyConnectSupported() throws Exception { + when(mWifiService.getSupportedFeatures()) + .thenReturn(new Long(WifiManager.WIFI_FEATURE_DPP)); + assertTrue(mWifiManager.isEasyConnectSupported()); + when(mWifiService.getSupportedFeatures()) + .thenReturn(new Long(~WifiManager.WIFI_FEATURE_DPP)); + assertFalse(mWifiManager.isEasyConnectSupported()); + } } |