diff options
225 files changed, 4969 insertions, 1191 deletions
diff --git a/Android.bp b/Android.bp index aa654865e09b..ed7a4813efe6 100644 --- a/Android.bp +++ b/Android.bp @@ -150,6 +150,9 @@ java_library { visibility: [ // DO NOT ADD ANY MORE ENTRIES TO THIS LIST "//external/robolectric-shadows:__subpackages__", + //This will eventually replace the item above, and serves the + //same purpose. + "//external/robolectric:__subpackages__", "//frameworks/layoutlib:__subpackages__", ], } diff --git a/api/Android.bp b/api/Android.bp index 9306671d758c..a3e64a565422 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -98,6 +98,7 @@ combined_apis { "framework-configinfrastructure", "framework-connectivity", "framework-connectivity-t", + "framework-devicelock", "framework-federatedcompute", "framework-graphics", "framework-healthconnect", diff --git a/boot/Android.bp b/boot/Android.bp index 9fdb9bc6506a..7839918d6a54 100644 --- a/boot/Android.bp +++ b/boot/Android.bp @@ -72,6 +72,10 @@ platform_bootclasspath { module: "com.android.conscrypt-bootclasspath-fragment", }, { + apex: "com.android.devicelock", + module: "com.android.devicelock-bootclasspath-fragment", + }, + { apex: "com.android.federatedcompute", module: "com.android.federatedcompute-bootclasspath-fragment", }, diff --git a/core/api/current.txt b/core/api/current.txt index 8d032fea24ec..94d199cc00b6 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -107,6 +107,7 @@ package android { field public static final String LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK"; field public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS"; field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE"; + field public static final String MANAGE_DEVICE_LOCK_STATE = "android.permission.MANAGE_DEVICE_LOCK_STATE"; field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS"; field public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE"; field public static final String MANAGE_MEDIA = "android.permission.MANAGE_MEDIA"; @@ -9845,6 +9846,7 @@ package android.content { field public static final int CONTEXT_RESTRICTED = 4; // 0x4 field public static final String CREDENTIAL_SERVICE = "credential"; field public static final String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps"; + field public static final String DEVICE_LOCK_SERVICE = "device_lock"; field public static final String DEVICE_POLICY_SERVICE = "device_policy"; field public static final String DISPLAY_HASH_SERVICE = "display_hash"; field public static final String DISPLAY_SERVICE = "display"; @@ -11991,6 +11993,7 @@ package android.content.pm { field public static final String FEATURE_CONTROLS = "android.software.controls"; field public static final String FEATURE_CREDENTIALS = "android.software.credentials"; field public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin"; + field public static final String FEATURE_DEVICE_LOCK = "android.software.device_lock"; field public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded"; field public static final String FEATURE_ETHERNET = "android.hardware.ethernet"; field public static final String FEATURE_EXPANDED_PICTURE_IN_PICTURE = "android.software.expanded_picture_in_picture"; @@ -39320,6 +39323,7 @@ package android.service.notification { method public final void setNotificationsShown(String[]); method public final void snoozeNotification(String, long); method public final void updateNotificationChannel(@NonNull String, @NonNull android.os.UserHandle, @NonNull android.app.NotificationChannel); + field public static final String ACTION_SETTINGS_HOME = "android.service.notification.action.SETTINGS_HOME"; field public static final int FLAG_FILTER_TYPE_ALERTING = 2; // 0x2 field public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1; // 0x1 field public static final int FLAG_FILTER_TYPE_ONGOING = 8; // 0x8 @@ -39327,7 +39331,6 @@ package android.service.notification { field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4 field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2 - field public static final String INTENT_CATEGORY_SETTINGS_HOME = "android.service.notification.category.SETTINGS_HOME"; field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4 field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1 field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3 @@ -45407,7 +45410,7 @@ package android.text { method public final int getParagraphLeft(int); method public final int getParagraphRight(int); method public float getPrimaryHorizontal(int); - method @Nullable public android.util.Range<java.lang.Integer> getRangeForRect(@NonNull android.graphics.RectF, @NonNull android.text.SegmentFinder, @NonNull android.text.Layout.TextInclusionStrategy); + method @Nullable public int[] getRangeForRect(@NonNull android.graphics.RectF, @NonNull android.text.SegmentFinder, @NonNull android.text.Layout.TextInclusionStrategy); method public float getSecondaryHorizontal(int); method public void getSelectionPath(int, int, android.graphics.Path); method public final float getSpacingAdd(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index c170f74b41c3..09b0dcaf98db 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2502,11 +2502,49 @@ package android.app.time { field @NonNull public static final android.os.Parcelable.Creator<android.app.time.ExternalTimeSuggestion> CREATOR; } + public final class TimeCapabilities implements android.os.Parcelable { + method public int describeContents(); + method public int getConfigureAutoDetectionEnabledCapability(); + method public int getSetManualTimeCapability(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeCapabilities> CREATOR; + } + + public final class TimeCapabilitiesAndConfig implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.app.time.TimeCapabilities getCapabilities(); + method @NonNull public android.app.time.TimeConfiguration getConfiguration(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeCapabilitiesAndConfig> CREATOR; + } + + public final class TimeConfiguration implements android.os.Parcelable { + method public int describeContents(); + method public boolean isAutoDetectionEnabled(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeConfiguration> CREATOR; + } + + public static final class TimeConfiguration.Builder { + ctor public TimeConfiguration.Builder(); + ctor public TimeConfiguration.Builder(@NonNull android.app.time.TimeConfiguration); + method @NonNull public android.app.time.TimeConfiguration build(); + method @NonNull public android.app.time.TimeConfiguration.Builder setAutoDetectionEnabled(boolean); + } + public final class TimeManager { method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public void addTimeZoneDetectorListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.time.TimeManager.TimeZoneDetectorListener); + method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean confirmTime(@NonNull android.app.time.UnixEpochTime); + method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean confirmTimeZone(@NonNull String); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public android.app.time.TimeCapabilitiesAndConfig getTimeCapabilitiesAndConfig(); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public android.app.time.TimeState getTimeState(); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public android.app.time.TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig(); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public android.app.time.TimeZoneState getTimeZoneState(); method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public void removeTimeZoneDetectorListener(@NonNull android.app.time.TimeManager.TimeZoneDetectorListener); + method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean setManualTime(@NonNull android.app.time.UnixEpochTime); + method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean setManualTimeZone(@NonNull String); method @RequiresPermission(android.Manifest.permission.SUGGEST_EXTERNAL_TIME) public void suggestExternalTime(@NonNull android.app.time.ExternalTimeSuggestion); + method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean updateTimeConfiguration(@NonNull android.app.time.TimeConfiguration); method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean updateTimeZoneConfiguration(@NonNull android.app.time.TimeZoneConfiguration); } @@ -2514,10 +2552,19 @@ package android.app.time { method public void onChange(); } + public final class TimeState implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.app.time.UnixEpochTime getUnixEpochTime(); + method public boolean getUserShouldConfirmTime(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeState> CREATOR; + } + public final class TimeZoneCapabilities implements android.os.Parcelable { method public int describeContents(); method public int getConfigureAutoDetectionEnabledCapability(); method public int getConfigureGeoDetectionEnabledCapability(); + method public int getSetManualTimeZoneCapability(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeZoneCapabilities> CREATOR; } @@ -2546,6 +2593,24 @@ package android.app.time { method @NonNull public android.app.time.TimeZoneConfiguration.Builder setGeoDetectionEnabled(boolean); } + public final class TimeZoneState implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public String getId(); + method public boolean getUserShouldConfirmId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeZoneState> CREATOR; + } + + public final class UnixEpochTime implements android.os.Parcelable { + ctor public UnixEpochTime(long, long); + method @NonNull public android.app.time.UnixEpochTime at(long); + method public int describeContents(); + method public long getElapsedRealtimeMillis(); + method public long getUnixEpochTimeMillis(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.time.UnixEpochTime> CREATOR; + } + } package android.app.usage { diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 4ddfdb603e73..08a6b8c4e135 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -85,6 +85,7 @@ import android.credentials.CredentialManager; import android.credentials.ICredentialManager; import android.debug.AdbManager; import android.debug.IAdbManager; +import android.devicelock.DeviceLockFrameworkInitializer; import android.graphics.fonts.FontManager; import android.hardware.ConsumerIrManager; import android.hardware.ISerialManager; @@ -1555,6 +1556,7 @@ public final class SystemServiceRegistry { ConnectivityFrameworkInitializerTiramisu.registerServiceWrappers(); NearbyFrameworkInitializer.registerServiceWrappers(); OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers(); + DeviceLockFrameworkInitializer.registerServiceWrappers(); } finally { // If any of the above code throws, we're in a pretty bad shape and the process // will likely crash, but we'll reset it just in case there's an exception handler... diff --git a/core/java/android/app/time/TimeCapabilities.java b/core/java/android/app/time/TimeCapabilities.java index 76bad58e924b..752caac0c5cd 100644 --- a/core/java/android/app/time/TimeCapabilities.java +++ b/core/java/android/app/time/TimeCapabilities.java @@ -20,6 +20,7 @@ import static android.app.time.Capabilities.CAPABILITY_NOT_APPLICABLE; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.app.time.Capabilities.CapabilityState; import android.os.Parcel; import android.os.Parcelable; @@ -37,6 +38,7 @@ import java.util.Objects; * * @hide */ +@SystemApi public final class TimeCapabilities implements Parcelable { public static final @NonNull Creator<TimeCapabilities> CREATOR = new Creator<>() { diff --git a/core/java/android/app/time/TimeCapabilitiesAndConfig.java b/core/java/android/app/time/TimeCapabilitiesAndConfig.java index b6a081825757..c9a45e04227a 100644 --- a/core/java/android/app/time/TimeCapabilitiesAndConfig.java +++ b/core/java/android/app/time/TimeCapabilitiesAndConfig.java @@ -17,6 +17,7 @@ package android.app.time; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -27,6 +28,7 @@ import java.util.Objects; * * @hide */ +@SystemApi public final class TimeCapabilitiesAndConfig implements Parcelable { public static final @NonNull Creator<TimeCapabilitiesAndConfig> CREATOR = diff --git a/core/java/android/app/time/TimeConfiguration.java b/core/java/android/app/time/TimeConfiguration.java index 7d986983160e..048f85a1e1a4 100644 --- a/core/java/android/app/time/TimeConfiguration.java +++ b/core/java/android/app/time/TimeConfiguration.java @@ -18,6 +18,7 @@ package android.app.time; import android.annotation.NonNull; import android.annotation.StringDef; +import android.annotation.SystemApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -40,6 +41,7 @@ import java.util.Objects; * * @hide */ +@SystemApi public final class TimeConfiguration implements Parcelable { public static final @NonNull Creator<TimeConfiguration> CREATOR = @@ -155,6 +157,7 @@ public final class TimeConfiguration implements Parcelable { * * @hide */ + @SystemApi public static final class Builder { private final Bundle mBundle = new Bundle(); diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java index 9f66f094786b..e35e359424e2 100644 --- a/core/java/android/app/time/TimeManager.java +++ b/core/java/android/app/time/TimeManager.java @@ -88,8 +88,6 @@ public final class TimeManager { /** * Returns the calling user's time capabilities and configuration. - * - * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) @NonNull @@ -107,10 +105,26 @@ public final class TimeManager { /** * Modifies the time detection configuration. * - * @return {@code true} if all the configuration settings specified have been set to the - * new values, {@code false} if none have + * <p>The ability to modify configuration settings can be subject to restrictions. For + * example, they may be determined by device hardware, general policy (i.e. only the primary + * user can set them), or by a managed device policy. Use {@link + * #getTimeCapabilitiesAndConfig()} to obtain information at runtime about the user's + * capabilities. + * + * <p>Attempts to modify configuration settings with capabilities that are {@link + * Capabilities#CAPABILITY_NOT_SUPPORTED} or {@link + * Capabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false} + * will be returned. Modifying configuration settings with capabilities that are {@link + * Capabilities#CAPABILITY_NOT_APPLICABLE} or {@link + * Capabilities#CAPABILITY_POSSESSED} will succeed. See {@link + * TimeZoneCapabilities} for further details. * - * @hide + * <p>If the supplied configuration only has some values set, then only the specified settings + * will be updated (where the user's capabilities allow) and other settings will be left + * unchanged. + * + * @return {@code true} if all the configuration settings specified have been set to the + * new values, {@code false} if none have */ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean updateTimeConfiguration(@NonNull TimeConfiguration configuration) { @@ -280,8 +294,6 @@ public final class TimeManager { /** * Returns a snapshot of the device's current system clock time state. See also {@link * #confirmTime(UnixEpochTime)} for how this information can be used. - * - * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) @NonNull @@ -306,8 +318,6 @@ public final class TimeManager { * <p>Returns {@code false} if the confirmation is invalid, i.e. if the time being * confirmed is no longer the time the device is currently set to. Confirming a time * in which the system already has high confidence will return {@code true}. - * - * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean confirmTime(@NonNull UnixEpochTime unixEpochTime) { @@ -329,8 +339,6 @@ public final class TimeManager { * capabilities prevents the time being accepted, e.g. if the device is currently set to * "automatic time detection". This method returns {@code true} if the time was accepted even * if it is the same as the current device time. - * - * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean setManualTime(@NonNull UnixEpochTime unixEpochTime) { @@ -353,8 +361,6 @@ public final class TimeManager { * Returns a snapshot of the device's current time zone state. See also {@link * #confirmTimeZone(String)} and {@link #setManualTimeZone(String)} for how this information may * be used. - * - * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) @NonNull @@ -379,8 +385,6 @@ public final class TimeManager { * <p>Returns {@code false} if the confirmation is invalid, i.e. if the time zone ID being * confirmed is no longer the time zone ID the device is currently set to. Confirming a time * zone ID in which the system already has high confidence returns {@code true}. - * - * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean confirmTimeZone(@NonNull String timeZoneId) { @@ -402,8 +406,6 @@ public final class TimeManager { * capabilities prevents the time zone being accepted, e.g. if the device is currently set to * "automatic time zone detection". {@code true} is returned if the time zone is accepted. A * time zone that is accepted and matches the current device time zone returns {@code true}. - * - * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean setManualTimeZone(@NonNull String timeZoneId) { diff --git a/core/java/android/app/time/TimeState.java b/core/java/android/app/time/TimeState.java index 01c869d99338..c209cde2cf49 100644 --- a/core/java/android/app/time/TimeState.java +++ b/core/java/android/app/time/TimeState.java @@ -18,6 +18,7 @@ package android.app.time; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.os.ShellCommand; @@ -36,6 +37,7 @@ import java.util.Objects; * * @hide */ +@SystemApi public final class TimeState implements Parcelable { public static final @NonNull Creator<TimeState> CREATOR = new Creator<>() { diff --git a/core/java/android/app/time/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java index 2f147cef9ffe..b647fc33055d 100644 --- a/core/java/android/app/time/TimeZoneCapabilities.java +++ b/core/java/android/app/time/TimeZoneCapabilities.java @@ -114,8 +114,6 @@ public final class TimeZoneCapabilities implements Parcelable { * <p>The time zone will be ignored in all cases unless the value is {@link * Capabilities#CAPABILITY_POSSESSED}. See also * {@link TimeZoneConfiguration#isAutoDetectionEnabled()}. - * - * @hide */ @CapabilityState public int getSetManualTimeZoneCapability() { diff --git a/core/java/android/app/time/TimeZoneState.java b/core/java/android/app/time/TimeZoneState.java index 8e87111986ce..beb6dc6d1dfb 100644 --- a/core/java/android/app/time/TimeZoneState.java +++ b/core/java/android/app/time/TimeZoneState.java @@ -18,6 +18,7 @@ package android.app.time; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.os.ShellCommand; @@ -36,6 +37,7 @@ import java.util.Objects; * * @hide */ +@SystemApi public final class TimeZoneState implements Parcelable { public static final @NonNull Creator<TimeZoneState> CREATOR = new Creator<>() { diff --git a/core/java/android/app/time/UnixEpochTime.java b/core/java/android/app/time/UnixEpochTime.java index 576bf6453eca..3a35f3cd1acb 100644 --- a/core/java/android/app/time/UnixEpochTime.java +++ b/core/java/android/app/time/UnixEpochTime.java @@ -19,6 +19,7 @@ package android.app.time; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.os.ShellCommand; @@ -38,6 +39,7 @@ import java.util.Objects; * * @hide */ +@SystemApi public final class UnixEpochTime implements Parcelable { @ElapsedRealtimeLong private final long mElapsedRealtimeMillis; private final long mUnixEpochTimeMillis; @@ -153,9 +155,8 @@ public final class UnixEpochTime implements Parcelable { * Creates a new Unix epoch time value at {@code elapsedRealtimeTimeMillis} by adjusting this * Unix epoch time by the difference between the elapsed realtime value supplied and the one * associated with this instance. - * - * @hide */ + @NonNull public UnixEpochTime at(@ElapsedRealtimeLong long elapsedRealtimeTimeMillis) { long adjustedUnixEpochTimeMillis = (elapsedRealtimeTimeMillis - mElapsedRealtimeMillis) + mUnixEpochTimeMillis; diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java index 24e47bf9e47c..2dced96d3583 100644 --- a/core/java/android/appwidget/AppWidgetHost.java +++ b/core/java/android/appwidget/AppWidgetHost.java @@ -329,6 +329,22 @@ public class AppWidgetHost { } /** + * Set the visibiity of all widgets associated with this host to hidden + * + * @hide + */ + public void setAppWidgetHidden() { + if (sService == null) { + return; + } + try { + sService.setAppWidgetHidden(mContextOpPackageName, mHostId); + } catch (RemoteException e) { + throw new RuntimeException("System server dead?", e); + } + } + + /** * Set the host's interaction handler. * * @hide diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 753c93612f40..d65210b8a0bc 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3938,6 +3938,7 @@ public abstract class Context { //@hide: SAFETY_CENTER_SERVICE, DISPLAY_HASH_SERVICE, CREDENTIAL_SERVICE, + DEVICE_LOCK_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -6073,6 +6074,14 @@ public abstract class Context { public static final String CREDENTIAL_SERVICE = "credential"; /** + * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.devicelock.DeviceLockManager}. + * + * @see #getSystemService(String) + */ + public static final String DEVICE_LOCK_SERVICE = "device_lock"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index db991dcd3afc..823c14281818 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -4194,6 +4194,14 @@ public abstract class PackageManager { @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_CREDENTIALS = "android.software.credentials"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device supports locking (for example, by a financing provider in case of a missed + * payment). + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_DEVICE_LOCK = "android.software.device_lock"; + /** @hide */ public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true; diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java index 122c54ad8144..b9ee72dcdcf8 100644 --- a/core/java/android/credentials/ui/Entry.java +++ b/core/java/android/credentials/ui/Entry.java @@ -30,12 +30,39 @@ import com.android.internal.util.AnnotationValidations; * @hide */ public class Entry implements Parcelable { - // TODO: move to jetpack. + // TODO: these constants should go to jetpack. public static final String VERSION = "v1"; public static final Uri CREDENTIAL_MANAGER_ENTRY_URI = Uri.parse("credentialmanager.slice"); - public static final String HINT_TITLE = "hint_title"; - public static final String HINT_SUBTITLE = "hint_subtitle"; - public static final String HINT_ICON = "hint_icon"; + // TODO: remove these hint constants and use the credential entry & action ones defined below. + public static final String HINT_TITLE = "HINT_TITLE"; + public static final String HINT_SUBTITLE = "HINT_SUBTITLE"; + public static final String HINT_ICON = "HINT_ICON"; + /** + * 1. CREDENTIAL ENTRY CONSTANTS + */ + // User profile picture associated with this credential entry. + public static final String HINT_PROFILE_ICON = "HINT_PROFILE_ICON"; + public static final String HINT_CREDENTIAL_TYPE_ICON = "HINT_CREDENTIAL_TYPE_ICON"; + // The user account name of this provider app associated with this entry. + // Note: this is independent from the request app. + public static final String HINT_USER_PROVIDER_ACCOUNT_NAME = "HINT_USER_PROVIDER_ACCOUNT_NAME"; + public static final String HINT_PASSWORD_COUNT = "HINT_PASSWORD_COUNT"; + public static final String HINT_PASSKEY_COUNT = "HINT_PASSKEY_COUNT"; + public static final String HINT_TOTAL_CREDENTIAL_COUNT = "HINT_TOTAL_CREDENTIAL_COUNT"; + public static final String HINT_LAST_USED_TIME_MILLIS = "HINT_LAST_USED_TIME_MILLIS"; + /** Below are only available for get flows. */ + public static final String HINT_NOTE = "HINT_NOTE"; + public static final String HINT_USER_NAME = "HINT_USER_NAME"; + public static final String HINT_CREDENTIAL_TYPE = "HINT_CREDENTIAL_TYPE"; + public static final String HINT_PASSKEY_USER_DISPLAY_NAME = "HINT_PASSKEY_USER_DISPLAY_NAME"; + public static final String HINT_PASSWORD_VALUE = "HINT_PASSWORD_VALUE"; + + /** + * 2. ACTION CONSTANTS + */ + public static final String HINT_ACTION_TITLE = "HINT_ACTION_TITLE"; + public static final String HINT_ACTION_SUBTEXT = "HINT_ACTION_SUBTEXT"; + public static final String HINT_ACTION_ICON = "HINT_ACTION_ICON"; /** * The intent extra key for the action chip {@code Entry} list when launching the UX activities. @@ -55,7 +82,7 @@ public class Entry implements Parcelable { public static final String EXTRA_ENTRY_AUTHENTICATION_ACTION = "android.credentials.ui.extra.ENTRY_AUTHENTICATION_ACTION"; - // TODO: may be changed to other type depending on the service implementation. + // TODO: change to string key + string subkey. private final int mId; @NonNull diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java index 9a038d137434..1b70ea4ebd71 100644 --- a/core/java/android/credentials/ui/IntentFactory.java +++ b/core/java/android/credentials/ui/IntentFactory.java @@ -34,8 +34,7 @@ public class IntentFactory { ArrayList<ProviderData> providerDataList, ResultReceiver resultReceiver) { Intent intent = new Intent(); // TODO: define these as proper config strings. - String activityName = "com.androidauth.tatiaccountselector/.CredentialSelectorActivity"; - // String activityName = "com.android.credentialmanager/.CredentialSelectorActivity"; + String activityName = "com.android.credentialmanager/.CredentialSelectorActivity"; intent.setComponent(ComponentName.unflattenFromString(activityName)); intent.putParcelableArrayListExtra( diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 7247ef77afb4..197739b6a067 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -768,6 +768,20 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } } + /** + * Schedules a watchdog. + * + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void scheduleWatchdog() { + try { + mService.scheduleWatchdog(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private void cancelEnrollment(long requestId) { if (mService != null) { try { diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index 9b56f43a0f22..2bf187ac9006 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -172,4 +172,9 @@ interface IFaceService { // Registers BiometricStateListener. void registerBiometricStateListener(IBiometricStateListener listener); + + // Internal operation used to clear face biometric scheduler. + // Ensures that the scheduler is not stuck. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + void scheduleWatchdog(); } diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 0fd164de8ffb..5403f089b308 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -1080,7 +1080,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing */ public boolean isPowerbuttonFps() { final FingerprintSensorPropertiesInternal sensorProps = getFirstFingerprintSensor(); - return sensorProps.sensorType == TYPE_POWER_BUTTON; + return sensorProps == null ? false : sensorProps.sensorType == TYPE_POWER_BUTTON; } /** @@ -1125,6 +1125,20 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } /** + * Schedules a watchdog. + * + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void scheduleWatchdog() { + try { + mService.scheduleWatchdog(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * @hide */ public void addLockoutResetCallback(final LockoutResetCallback callback) { diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 1ba9a0471c88..051e3a4caa4e 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -208,4 +208,9 @@ interface IFingerprintService { // Sends a power button pressed event to all listeners. @EnforcePermission("USE_BIOMETRIC_INTERNAL") oneway void onPowerPressed(); + + // Internal operation used to clear fingerprint biometric scheduler. + // Ensures that the scheduler is not stuck. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + void scheduleWatchdog(); } diff --git a/core/java/android/hardware/radio/Announcement.java b/core/java/android/hardware/radio/Announcement.java index 8febed3fb2a0..3ba3ebceeb18 100644 --- a/core/java/android/hardware/radio/Announcement.java +++ b/core/java/android/hardware/radio/Announcement.java @@ -85,9 +85,9 @@ public final class Announcement implements Parcelable { /** @hide */ public Announcement(@NonNull ProgramSelector selector, @Type int type, @NonNull Map<String, String> vendorInfo) { - mSelector = Objects.requireNonNull(selector); - mType = Objects.requireNonNull(type); - mVendorInfo = Objects.requireNonNull(vendorInfo); + mSelector = Objects.requireNonNull(selector, "Program selector cannot be null"); + mType = type; + mVendorInfo = Objects.requireNonNull(vendorInfo, "Vendor info cannot be null"); } private Announcement(@NonNull Parcel in) { diff --git a/core/java/android/os/storage/OWNERS b/core/java/android/os/storage/OWNERS index 1f686e5c449c..c80c57ce917a 100644 --- a/core/java/android/os/storage/OWNERS +++ b/core/java/android/os/storage/OWNERS @@ -1,11 +1,15 @@ # Bug component: 95221 +# Android Storage Team +abkaur@google.com corinac@google.com -nandana@google.com -zezeozue@google.com -maco@google.com +dipankarb@google.com +krishang@google.com sahanas@google.com -abkaur@google.com -chiangi@google.com +sergeynv@google.com +shubhisaxena@google.com +tylersaunders@google.com + +maco@google.com +nandana@google.com narayan@google.com -dipankarb@google.com diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 4e15b38463d6..29e24598f874 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6875,6 +6875,14 @@ public final class Settings { @Readable public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service"; + + /** + * The currently selected credential service(s) flattened ComponentName. + * + * @hide + */ + public static final String CREDENTIAL_SERVICE = "credential_service"; + /** * The currently selected autofill service flattened ComponentName. * @hide diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index cb0dce91589e..32bdf7962273 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -1047,7 +1047,7 @@ public class DreamService extends Service implements Window.Callback { } if (mDreamToken == null) { - Slog.w(mTag, "Finish was called before the dream was attached."); + if (mDebug) Slog.v(mTag, "finish() called when not attached."); stopSelf(); return; } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index bd4a4957775e..cfc79e4fef66 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -392,13 +392,13 @@ public abstract class NotificationListenerService extends Service { public static final int NOTIFICATION_CHANNEL_OR_GROUP_DELETED = 3; /** - * An optional activity intent category that shows additional settings for what notifications + * An optional activity intent action that shows additional settings for what notifications * should be processed by this notification listener service. If defined, the OS may link to * this activity from the system notification listener service filter settings page. */ - @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY) - public static final String INTENT_CATEGORY_SETTINGS_HOME = - "android.service.notification.category.SETTINGS_HOME"; + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SETTINGS_HOME = + "android.service.notification.action.SETTINGS_HOME"; private final Object mLock = new Object(); diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 519fc55b523d..1337d6a87df8 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -36,7 +36,6 @@ import android.text.style.LineBackgroundSpan; import android.text.style.ParagraphStyle; import android.text.style.ReplacementSpan; import android.text.style.TabStopSpan; -import android.util.Range; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; @@ -1859,13 +1858,12 @@ public abstract class Layout { * @param segmentFinder SegmentFinder for determining the ranges of text to be considered as a * text segment * @param inclusionStrategy strategy for determining whether a text segment is inside the - * specified area - * @return an integer range where the endpoints are the start (inclusive) and end (exclusive) - * character offsets of the text range, or null if there are no text segments inside the - * area + * specified area + * @return int array of size 2 containing the start (inclusive) and end (exclusive) character + * offsets of the text range, or null if there are no text segments inside the area */ @Nullable - public Range<Integer> getRangeForRect(@NonNull RectF area, @NonNull SegmentFinder segmentFinder, + public int[] getRangeForRect(@NonNull RectF area, @NonNull SegmentFinder segmentFinder, @NonNull TextInclusionStrategy inclusionStrategy) { // Find the first line whose bottom (without line spacing) is below the top of the area. int startLine = getLineForVertical((int) area.top); @@ -1923,7 +1921,7 @@ public abstract class Layout { start = segmentFinder.previousStartBoundary(start + 1); end = segmentFinder.nextEndBoundary(end - 1); - return new Range(start, end); + return new int[] {start, end}; } /** diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 9789b5670fbb..06c1b258cb70 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -2001,7 +2001,6 @@ public class KeyEvent extends InputEvent implements Parcelable { case KeyEvent.KEYCODE_MEDIA_PLAY: case KeyEvent.KEYCODE_MEDIA_PAUSE: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - case KeyEvent.KEYCODE_MUTE: case KeyEvent.KEYCODE_HEADSETHOOK: case KeyEvent.KEYCODE_MEDIA_STOP: case KeyEvent.KEYCODE_MEDIA_NEXT: diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index bfa13507ed50..efda257aed27 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -888,20 +888,18 @@ public final class ViewRootImpl implements ViewParent, static BLASTBufferQueue.TransactionHangCallback sTransactionHangCallback = new BLASTBufferQueue.TransactionHangCallback() { @Override - public void onTransactionHang(boolean isGPUHang) { - if (isGPUHang && !sAnrReported) { - sAnrReported = true; - try { - ActivityManager.getService().appNotResponding( - "Buffer processing hung up due to stuck fence. Indicates GPU hang"); - } catch (RemoteException e) { - // We asked the system to crash us, but the system - // already crashed. Unfortunately things may be - // out of control. - } - } else { - // TODO: Do something with this later. For now we just ANR - // in dequeue buffer later like we always have. + public void onTransactionHang(String reason) { + if (sAnrReported) { + return; + } + + sAnrReported = true; + try { + ActivityManager.getService().appNotResponding(reason); + } catch (RemoteException e) { + // We asked the system to crash us, but the system + // already crashed. Unfortunately things may be + // out of control. } } }; diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 5c73b8feb740..574d0356ae42 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -30,7 +30,9 @@ import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodCl import static android.view.inputmethod.InputMethodManagerProto.ACTIVE; import static android.view.inputmethod.InputMethodManagerProto.CUR_ID; import static android.view.inputmethod.InputMethodManagerProto.FULLSCREEN_MODE; +import static android.view.inputmethod.InputMethodManagerProto.NEXT_SERVED_VIEW; import static android.view.inputmethod.InputMethodManagerProto.SERVED_CONNECTING; +import static android.view.inputmethod.InputMethodManagerProto.SERVED_VIEW; import static com.android.internal.inputmethod.StartInputReason.BOUND_TO_IMMS; @@ -3990,6 +3992,8 @@ public final class InputMethodManager { proto.write(FULLSCREEN_MODE, mFullscreenMode); proto.write(ACTIVE, mActive); proto.write(SERVED_CONNECTING, mServedConnecting); + proto.write(SERVED_VIEW, Objects.toString(mServedView)); + proto.write(NEXT_SERVED_VIEW, Objects.toString(mNextServedView)); proto.end(token); if (mCurRootView != null) { mCurRootView.dumpDebug(proto, VIEW_ROOT_IMPL); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 57103e4955ca..b5c58fb4bfc0 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -151,7 +151,6 @@ import android.util.DisplayMetrics; import android.util.FeatureFlagUtils; import android.util.IntArray; import android.util.Log; -import android.util.Range; import android.util.SparseIntArray; import android.util.TypedValue; import android.view.AccessibilityIterators.TextSegmentIterator; @@ -9317,40 +9316,42 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** @hide */ public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) { - Range<Integer> range = getRangeForRect( + int[] range = getRangeForRect( convertFromScreenToContentCoordinates(gesture.getSelectionArea()), gesture.getGranularity()); if (range == null) { return handleGestureFailure(gesture); } - Selection.setSelection(getEditableText(), range.getLower(), range.getUpper()); + Selection.setSelection(getEditableText(), range[0], range[1]); mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false); return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; } /** @hide */ public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) { - Range<Integer> startRange = getRangeForRect( + int[] startRange = getRangeForRect( convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()), gesture.getGranularity()); if (startRange == null) { return handleGestureFailure(gesture); } - Range<Integer> endRange = getRangeForRect( + int[] endRange = getRangeForRect( convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()), gesture.getGranularity()); - if (endRange == null || endRange.getUpper() <= startRange.getLower()) { + if (endRange == null) { return handleGestureFailure(gesture); } - Range<Integer> range = startRange.extend(endRange); - Selection.setSelection(getEditableText(), range.getLower(), range.getUpper()); + int[] range = new int[] { + Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1]) + }; + Selection.setSelection(getEditableText(), range[0], range[1]); mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false); return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; } /** @hide */ public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) { - Range<Integer> range = getRangeForRect( + int[] range = getRangeForRect( convertFromScreenToContentCoordinates(gesture.getDeletionArea()), gesture.getGranularity()); if (range == null) { @@ -9361,42 +9362,44 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener range = adjustHandwritingDeleteGestureRange(range); } - getEditableText().delete(range.getLower(), range.getUpper()); - Selection.setSelection(getEditableText(), range.getLower()); + getEditableText().delete(range[0], range[1]); + Selection.setSelection(getEditableText(), range[0]); return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; } /** @hide */ public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) { - Range<Integer> startRange = getRangeForRect( + int[] startRange = getRangeForRect( convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()), gesture.getGranularity()); if (startRange == null) { return handleGestureFailure(gesture); } - Range<Integer> endRange = getRangeForRect( + int[] endRange = getRangeForRect( convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()), gesture.getGranularity()); if (endRange == null) { return handleGestureFailure(gesture); } - Range<Integer> range = startRange.extend(endRange); + int[] range = new int[] { + Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1]) + }; if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) { range = adjustHandwritingDeleteGestureRange(range); } - getEditableText().delete(range.getLower(), range.getUpper()); - Selection.setSelection(getEditableText(), range.getLower()); + getEditableText().delete(range[0], range[1]); + Selection.setSelection(getEditableText(), range[0]); return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; } - private Range<Integer> adjustHandwritingDeleteGestureRange(Range<Integer> range) { + private int[] adjustHandwritingDeleteGestureRange(int[] range) { // For handwriting delete gestures with word granularity, adjust the start and end offsets // to remove extra whitespace around the deleted text. - int start = range.getLower(); - int end = range.getUpper(); + int start = range[0]; + int end = range[1]; // If the deleted text is at the start of the text, the behavior is the same as the case // where the deleted text follows a new line character. @@ -9425,7 +9428,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (start == 0) break; codePointBeforeStart = Character.codePointBefore(mText, start); } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)); - return new Range(start, end); + return new int[] {start, end}; } if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd) @@ -9444,7 +9447,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (end == mText.length()) break; codePointAtEnd = Character.codePointAt(mText, end); } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)); - return new Range(start, end); + return new int[] {start, end}; } // Return the original range. @@ -9494,14 +9497,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener lineVerticalCenter + 0.1f, Math.max(startPoint.x, endPoint.x), lineVerticalCenter - 0.1f); - Range<Integer> range = mLayout.getRangeForRect( + int[] range = mLayout.getRangeForRect( area, new GraphemeClusterSegmentFinder(mText, mTextPaint), Layout.INCLUSION_STRATEGY_ANY_OVERLAP); if (range == null) { return handleGestureFailure(gesture); } - int startOffset = range.getLower(); - int endOffset = range.getUpper(); + int startOffset = range[0]; + int endOffset = range[1]; // TODO(b/247557062): This doesn't handle bidirectional text correctly. Pattern whitespacePattern = getWhitespacePattern(); @@ -9606,7 +9609,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } @Nullable - private Range<Integer> getRangeForRect(@NonNull RectF area, int granularity) { + private int[] getRangeForRect(@NonNull RectF area, int granularity) { SegmentFinder segmentFinder; if (granularity == HandwritingGesture.GRANULARITY_WORD) { WordIterator wordIterator = getWordIterator(); diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl index e74898294c09..8e7207fa91ee 100644 --- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl +++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl @@ -45,6 +45,7 @@ interface IAppWidgetService { @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) RemoteViews getAppWidgetViews(String callingPackage, int appWidgetId); int[] getAppWidgetIdsForHost(String callingPackage, int hostId); + void setAppWidgetHidden(in String callingPackage, int hostId); IntentSender createAppWidgetConfigIntentSender(String callingPackage, int appWidgetId, int intentFlags); diff --git a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java index a09c8236b47d..04dd2d72729d 100644 --- a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java +++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java @@ -97,7 +97,6 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { case KeyEvent.KEYCODE_MEDIA_PLAY: case KeyEvent.KEYCODE_MEDIA_PAUSE: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - case KeyEvent.KEYCODE_MUTE: case KeyEvent.KEYCODE_HEADSETHOOK: case KeyEvent.KEYCODE_MEDIA_STOP: case KeyEvent.KEYCODE_MEDIA_NEXT: @@ -224,7 +223,6 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { } case KeyEvent.KEYCODE_HEADSETHOOK: - case KeyEvent.KEYCODE_MUTE: case KeyEvent.KEYCODE_MEDIA_PLAY: case KeyEvent.KEYCODE_MEDIA_PAUSE: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 44cfe1aa4a79..1d4b246de5c8 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -322,4 +322,7 @@ oneway interface IStatusBar /** Unregisters a nearby media devices provider. */ void unregisterNearbyMediaDevicesProvider(in INearbyMediaDevicesProvider provider); + + /** Dump protos from SystemUI. The proto definition is defined there */ + void dumpProto(in String[] args, in ParcelFileDescriptor pfd); } diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index 1520ea5c6831..03815108f6dd 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -71,10 +71,12 @@ public: } } - void onTransactionHang(bool isGpuHang) { + void onTransactionHang(const std::string& reason) { if (mTransactionHangObject) { + JNIEnv* env = getenv(mVm); + ScopedLocalRef<jstring> jReason(env, env->NewStringUTF(reason.c_str())); getenv(mVm)->CallVoidMethod(mTransactionHangObject, - gTransactionHangCallback.onTransactionHang, isGpuHang); + gTransactionHangCallback.onTransactionHang, jReason.get()); } } @@ -177,7 +179,7 @@ static bool nativeIsSameSurfaceControl(JNIEnv* env, jclass clazz, jlong ptr, jlo sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); return queue->isSameSurfaceControl(reinterpret_cast<SurfaceControl*>(surfaceControl)); } - + static void nativeSetTransactionHangCallback(JNIEnv* env, jclass clazz, jlong ptr, jobject transactionHangCallback) { sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); @@ -186,9 +188,8 @@ static void nativeSetTransactionHangCallback(JNIEnv* env, jclass clazz, jlong pt } else { sp<TransactionHangCallbackWrapper> wrapper = new TransactionHangCallbackWrapper{env, transactionHangCallback}; - queue->setTransactionHangCallback([wrapper](bool isGpuHang) { - wrapper->onTransactionHang(isGpuHang); - }); + queue->setTransactionHangCallback( + [wrapper](const std::string& reason) { wrapper->onTransactionHang(reason); }); } } @@ -236,7 +237,8 @@ int register_android_graphics_BLASTBufferQueue(JNIEnv* env) { jclass transactionHangClass = FindClassOrDie(env, "android/graphics/BLASTBufferQueue$TransactionHangCallback"); gTransactionHangCallback.onTransactionHang = - GetMethodIDOrDie(env, transactionHangClass, "onTransactionHang", "(Z)V"); + GetMethodIDOrDie(env, transactionHangClass, "onTransactionHang", + "(Ljava/lang/String;)V"); return 0; } diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 40f6e4f63cd7..5c71f692b80f 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -580,6 +580,7 @@ void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail // TODO(narayan): This will be an error in a future android release. // error = true; // ALOGW("Zygote closed file descriptor %d.", it->first); + delete it->second; it = open_fd_map_.erase(it); } else { // The entry from the file descriptor table is still open. Restat diff --git a/core/proto/android/view/imefocuscontroller.proto b/core/proto/android/view/imefocuscontroller.proto index ff9dee69207b..ccde9b7a7966 100644 --- a/core/proto/android/view/imefocuscontroller.proto +++ b/core/proto/android/view/imefocuscontroller.proto @@ -25,6 +25,6 @@ option java_multiple_files = true; */ message ImeFocusControllerProto { optional bool has_ime_focus = 1; - optional string served_view = 2; - optional string next_served_view = 3; + optional string served_view = 2 [deprecated = true]; + optional string next_served_view = 3 [deprecated = true]; }
\ No newline at end of file diff --git a/core/proto/android/view/inputmethod/inputmethodmanager.proto b/core/proto/android/view/inputmethod/inputmethodmanager.proto index 9fed0ef95a27..ea5f1e8f3be2 100644 --- a/core/proto/android/view/inputmethod/inputmethodmanager.proto +++ b/core/proto/android/view/inputmethod/inputmethodmanager.proto @@ -29,4 +29,6 @@ message InputMethodManagerProto { optional int32 display_id = 3; optional bool active = 4; optional bool served_connecting = 5; + optional string served_view = 6; + optional string next_served_view = 7; }
\ No newline at end of file diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index de1a7f9eaaa5..595ea0eb99eb 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1151,7 +1151,28 @@ android:protectionLevel="dangerous" /> <!-- Allows an application to write to external storage. - <p class="note"><strong>Note:</strong> If <em>both</em> your <a + <p><strong>Note: </strong>If your app targets {@link android.os.Build.VERSION_CODES#R} or + higher, this permission has no effect. + + <p>If your app is on a device that runs API level 19 or higher, you don't need to declare + this permission to read and write files in your application-specific directories returned + by {@link android.content.Context#getExternalFilesDir} and + {@link android.content.Context#getExternalCacheDir}. + + <p>Learn more about how to + <a href="{@docRoot}training/data-storage/shared/media#update-other-apps-files">modify media + files</a> that your app doesn't own, and how to + <a href="{@docRoot}training/data-storage/shared/documents-files">modify non-media files</a> + that your app doesn't own. + + <p>If your app is a file manager and needs broad access to external storage files, then + the system must place your app on an allowlist so that you can successfully request the + <a href="#MANAGE_EXTERNAL_STORAGE><code>MANAGE_EXTERNAL_STORAGE</code></a> permission. + Learn more about the appropriate use cases for + <a href="{@docRoot}training/data-storage/manage-all-files>managing all files on a storage + device</a>. + + <p>If <em>both</em> your <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code minSdkVersion}</a> and <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code @@ -1159,12 +1180,6 @@ grants your app this permission. If you don't need this permission, be sure your <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code targetSdkVersion}</a> is 4 or higher. - <p>Starting in API level 19, this permission is <em>not</em> required to - read/write files in your application-specific directories returned by - {@link android.content.Context#getExternalFilesDir} and - {@link android.content.Context#getExternalCacheDir}. - <p>If this permission is not allowlisted for an app that targets an API level before - {@link android.os.Build.VERSION_CODES#Q} this permission cannot be granted to apps.</p> <p>Protection level: dangerous</p> --> <permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" @@ -6577,6 +6592,13 @@ android:protectionLevel="signature" /> <uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" /> + <!-- Allows financed device kiosk apps to perform actions on the Device Lock service + <p>Protection level: internal|role + <p>Intended for use by the FINANCED_DEVICE_KIOSK role only. + --> + <permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE" + android:protectionLevel="internal|role" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/res/anim/dream_activity_close_exit.xml b/core/res/res/anim/dream_activity_close_exit.xml index c4599dad31a0..8df624fdd2e5 100644 --- a/core/res/res/anim/dream_activity_close_exit.xml +++ b/core/res/res/anim/dream_activity_close_exit.xml @@ -19,5 +19,5 @@ <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:fromAlpha="1.0" android:toAlpha="0.0" - android:duration="100" /> + android:duration="@integer/config_dreamCloseAnimationDuration" /> diff --git a/core/res/res/anim/dream_activity_open_enter.xml b/core/res/res/anim/dream_activity_open_enter.xml index 9e1c6e2ee0d7..d6d9c5c990f8 100644 --- a/core/res/res/anim/dream_activity_open_enter.xml +++ b/core/res/res/anim/dream_activity_open_enter.xml @@ -22,5 +22,5 @@ those two has to be the same. --> <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:fromAlpha="0.0" android:toAlpha="1.0" - android:duration="1000" /> + android:duration="@integer/config_dreamOpenAnimationDuration" /> diff --git a/core/res/res/anim/dream_activity_open_exit.xml b/core/res/res/anim/dream_activity_open_exit.xml index 740f52856b7f..2c2e501eda69 100644 --- a/core/res/res/anim/dream_activity_open_exit.xml +++ b/core/res/res/anim/dream_activity_open_exit.xml @@ -22,4 +22,4 @@ dream_activity_open_enter animation. --> <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:fromAlpha="1.0" android:toAlpha="1.0" - android:duration="1000" /> + android:duration="@integer/config_dreamOpenAnimationDuration" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index caa67de1fe61..173908d97b56 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2447,6 +2447,11 @@ <!-- Whether dreams are disabled when ambient mode is suppressed. --> <bool name="config_dreamsDisabledByAmbientModeSuppressionConfig">false</bool> + <!-- The duration in milliseconds of the dream opening animation. --> + <integer name="config_dreamOpenAnimationDuration">250</integer> + <!-- The duration in milliseconds of the dream closing animation. --> + <integer name="config_dreamCloseAnimationDuration">100</integer> + <!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to assistant activities (ACTIVITY_TYPE_ASSISTANT) --> <bool name="config_dismissDreamOnActivityStart">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index faabfeaee3b7..29ec347fa6b2 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2236,6 +2236,8 @@ <java-symbol type="string" name="config_dreamsDefaultComponent" /> <java-symbol type="bool" name="config_dreamsDisabledByAmbientModeSuppressionConfig" /> <java-symbol type="bool" name="config_dreamsOnlyEnabledForSystemUser" /> + <java-symbol type="integer" name="config_dreamOpenAnimationDuration" /> + <java-symbol type="integer" name="config_dreamCloseAnimationDuration" /> <java-symbol type="array" name="config_supportedDreamComplications" /> <java-symbol type="array" name="config_disabledDreamComponents" /> <java-symbol type="bool" name="config_dismissDreamOnActivityStart" /> diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java new file mode 100644 index 000000000000..42143b92e9d8 --- /dev/null +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2022 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.hardware.radio.tests.unittests; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertThrows; + +import android.hardware.radio.Announcement; +import android.hardware.radio.ProgramSelector; +import android.util.ArrayMap; + +import org.junit.Test; + +import java.util.Map; + +public final class RadioAnnouncementTest { + private static final ProgramSelector.Identifier FM_IDENTIFIER = new ProgramSelector.Identifier( + ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 90500); + private static final ProgramSelector FM_PROGRAM_SELECTOR = new ProgramSelector( + ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER, /* secondaryIds= */ null, + /* vendorIds= */ null); + private static final int TRAFFIC_ANNOUNCEMENT_TYPE = Announcement.TYPE_TRAFFIC; + private static final Map<String, String> VENDOR_INFO = createVendorInfo(); + private static final Announcement TEST_ANNOUNCEMENT = + new Announcement(FM_PROGRAM_SELECTOR, TRAFFIC_ANNOUNCEMENT_TYPE, VENDOR_INFO); + + @Test + public void constructor_withNullSelector_fails() { + NullPointerException thrown = assertThrows(NullPointerException.class, () -> { + new Announcement(/* selector= */ null, TRAFFIC_ANNOUNCEMENT_TYPE, VENDOR_INFO); + }); + + assertWithMessage("Exception for null program selector in announcement constructor") + .that(thrown).hasMessageThat().contains("Program selector cannot be null"); + } + + @Test + public void constructor_withNullVendorInfo_fails() { + NullPointerException thrown = assertThrows(NullPointerException.class, () -> { + new Announcement(FM_PROGRAM_SELECTOR, TRAFFIC_ANNOUNCEMENT_TYPE, + /* vendorInfo= */ null); + }); + + assertWithMessage("Exception for null vendor info in announcement constructor") + .that(thrown).hasMessageThat().contains("Vendor info cannot be null"); + } + + @Test + public void getSelector() { + assertWithMessage("Radio announcement selector") + .that(TEST_ANNOUNCEMENT.getSelector()).isEqualTo(FM_PROGRAM_SELECTOR); + } + + @Test + public void getType() { + assertWithMessage("Radio announcement type") + .that(TEST_ANNOUNCEMENT.getType()).isEqualTo(TRAFFIC_ANNOUNCEMENT_TYPE); + } + + @Test + public void getVendorInfo() { + assertWithMessage("Radio announcement vendor info") + .that(TEST_ANNOUNCEMENT.getVendorInfo()).isEqualTo(VENDOR_INFO); + } + + private static Map<String, String> createVendorInfo() { + Map<String, String> vendorInfo = new ArrayMap<>(); + vendorInfo.put("vendorKeyMock", "vendorValueMock"); + return vendorInfo; + } +} diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java index 259a11852784..9bfa2fba6948 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java @@ -20,9 +20,12 @@ import static com.google.common.truth.Truth.assertWithMessage; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; +import android.hardware.radio.RadioMetadata; import org.junit.Test; +import java.util.Arrays; + public final class RadioManagerTest { private static final int REGION = RadioManager.REGION_ITU_2; @@ -63,6 +66,32 @@ public final class RadioManagerTest { private static final RadioManager.AmBandConfig AM_BAND_CONFIG = createAmBandConfig(); private static final RadioManager.ModuleProperties AMFM_PROPERTIES = createAmFmProperties(); + /** + * Info flags with live, tuned and stereo enabled + */ + private static final int INFO_FLAGS = 0b110001; + private static final int SIGNAL_QUALITY = 2; + private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, + /* value= */ 0x10000111); + private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_RELATED = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, + /* value= */ 0x10000113); + private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, + /* value= */ 0x1013); + private static final ProgramSelector.Identifier DAB_FREQUENCY_IDENTIFIER = + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, + /* value= */ 95500); + private static final ProgramSelector DAB_SELECTOR = + new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB, DAB_SID_EXT_IDENTIFIER, + new ProgramSelector.Identifier[]{ + DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, + /* vendorIds= */ null); + private static final RadioMetadata METADATA = createMetadata(); + private static final RadioManager.ProgramInfo DAB_PROGRAM_INFO = + createDabProgramInfo(DAB_SELECTOR); + @Test public void getType_forBandDescriptor() { RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor(); @@ -460,6 +489,123 @@ public final class RadioManagerTest { .that(AMFM_PROPERTIES).isNotEqualTo(propertiesDab); } + @Test + public void getSelector_forProgramInfo() { + assertWithMessage("Selector of DAB program info") + .that(DAB_PROGRAM_INFO.getSelector()).isEqualTo(DAB_SELECTOR); + } + + @Test + public void getLogicallyTunedTo_forProgramInfo() { + assertWithMessage("Identifier logically tuned to in DAB program info") + .that(DAB_PROGRAM_INFO.getLogicallyTunedTo()).isEqualTo(DAB_FREQUENCY_IDENTIFIER); + } + + @Test + public void getPhysicallyTunedTo_forProgramInfo() { + assertWithMessage("Identifier physically tuned to DAB program info") + .that(DAB_PROGRAM_INFO.getPhysicallyTunedTo()).isEqualTo(DAB_SID_EXT_IDENTIFIER); + } + + @Test + public void getRelatedContent_forProgramInfo() { + assertWithMessage("Related contents of DAB program info") + .that(DAB_PROGRAM_INFO.getRelatedContent()) + .containsExactly(DAB_SID_EXT_IDENTIFIER_RELATED); + } + + @Test + public void getChannel_forProgramInfo() { + assertWithMessage("Main channel of DAB program info") + .that(DAB_PROGRAM_INFO.getChannel()).isEqualTo(0); + } + + @Test + public void getSubChannel_forProgramInfo() { + assertWithMessage("Sub channel of DAB program info") + .that(DAB_PROGRAM_INFO.getSubChannel()).isEqualTo(0); + } + + @Test + public void isTuned_forProgramInfo() { + assertWithMessage("Tuned status of DAB program info") + .that(DAB_PROGRAM_INFO.isTuned()).isTrue(); + } + + @Test + public void isStereo_forProgramInfo() { + assertWithMessage("Stereo support in DAB program info") + .that(DAB_PROGRAM_INFO.isStereo()).isTrue(); + } + + @Test + public void isDigital_forProgramInfo() { + assertWithMessage("Digital DAB program info") + .that(DAB_PROGRAM_INFO.isDigital()).isTrue(); + } + + @Test + public void isLive_forProgramInfo() { + assertWithMessage("Live status of DAB program info") + .that(DAB_PROGRAM_INFO.isLive()).isTrue(); + } + + @Test + public void isMuted_forProgramInfo() { + assertWithMessage("Muted status of DAB program info") + .that(DAB_PROGRAM_INFO.isMuted()).isFalse(); + } + + @Test + public void isTrafficProgram_forProgramInfo() { + assertWithMessage("Traffic program support in DAB program info") + .that(DAB_PROGRAM_INFO.isTrafficProgram()).isFalse(); + } + + @Test + public void isTrafficAnnouncementActive_forProgramInfo() { + assertWithMessage("Active traffic announcement for DAB program info") + .that(DAB_PROGRAM_INFO.isTrafficAnnouncementActive()).isFalse(); + } + + @Test + public void getSignalStrength_forProgramInfo() { + assertWithMessage("Signal strength of DAB program info") + .that(DAB_PROGRAM_INFO.getSignalStrength()).isEqualTo(SIGNAL_QUALITY); + } + + @Test + public void getMetadata_forProgramInfo() { + assertWithMessage("Metadata of DAB program info") + .that(DAB_PROGRAM_INFO.getMetadata()).isEqualTo(METADATA); + } + + @Test + public void getVendorInfo_forProgramInfo() { + assertWithMessage("Vendor info of DAB program info") + .that(DAB_PROGRAM_INFO.getVendorInfo()).isEmpty(); + } + + @Test + public void equals_withSameProgramInfo_returnsTrue() { + RadioManager.ProgramInfo dabProgramInfoCompared = createDabProgramInfo(DAB_SELECTOR); + + assertWithMessage("The same program info") + .that(dabProgramInfoCompared).isEqualTo(DAB_PROGRAM_INFO); + } + + @Test + public void equals_withSameProgramInfoOfDifferentSecondaryIdSelectors_returnsFalse() { + ProgramSelector dabSelectorCompared = new ProgramSelector( + ProgramSelector.PROGRAM_TYPE_DAB, DAB_SID_EXT_IDENTIFIER, + new ProgramSelector.Identifier[]{DAB_FREQUENCY_IDENTIFIER}, + /* vendorIds= */ null); + RadioManager.ProgramInfo dabProgramInfoCompared = createDabProgramInfo(dabSelectorCompared); + + assertWithMessage("Program info with different secondary id selectors") + .that(DAB_PROGRAM_INFO).isNotEqualTo(dabProgramInfoCompared); + } + private static RadioManager.ModuleProperties createAmFmProperties() { return new RadioManager.ModuleProperties(PROPERTIES_ID, SERVICE_NAME, CLASS_ID, IMPLEMENTOR, PRODUCT, VERSION, SERIAL, NUM_TUNERS, NUM_AUDIO_SOURCES, @@ -487,4 +633,16 @@ public final class RadioManagerTest { private static RadioManager.AmBandConfig createAmBandConfig() { return new RadioManager.AmBandConfig(createAmBandDescriptor()); } + + private static RadioMetadata createMetadata() { + RadioMetadata.Builder metadataBuilder = new RadioMetadata.Builder(); + return metadataBuilder.putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest").build(); + } + + private static RadioManager.ProgramInfo createDabProgramInfo(ProgramSelector selector) { + return new RadioManager.ProgramInfo(selector, DAB_FREQUENCY_IDENTIFIER, + DAB_SID_EXT_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED), INFO_FLAGS, + SIGNAL_QUALITY, METADATA, /* vendorInfo= */ null); + } + } diff --git a/core/tests/coretests/src/android/app/time/TimeConfigurationTest.java b/core/tests/coretests/src/android/app/time/TimeConfigurationTest.java deleted file mode 100644 index 7c7cd12bcb73..000000000000 --- a/core/tests/coretests/src/android/app/time/TimeConfigurationTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app.time; - -import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable; - -import static com.google.common.truth.Truth.assertThat; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class TimeConfigurationTest { - - @Test - public void testBuilder() { - TimeConfiguration first = new TimeConfiguration.Builder() - .setAutoDetectionEnabled(true) - .build(); - - assertThat(first.isAutoDetectionEnabled()).isTrue(); - - TimeConfiguration copyFromBuilderConfiguration = new TimeConfiguration.Builder(first) - .build(); - - assertThat(first).isEqualTo(copyFromBuilderConfiguration); - } - - @Test - public void testParcelable() { - TimeConfiguration.Builder builder = new TimeConfiguration.Builder(); - - assertRoundTripParcelable(builder.setAutoDetectionEnabled(true).build()); - - assertRoundTripParcelable(builder.setAutoDetectionEnabled(false).build()); - } - -} diff --git a/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java index 3ab01f3d8832..e7d352cfee30 100644 --- a/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java +++ b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java @@ -16,11 +16,9 @@ package android.app.time; -import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable; import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import android.os.ShellCommand; @@ -31,35 +29,12 @@ import org.junit.runner.RunWith; /** * Tests for non-SDK methods on {@link UnixEpochTime}. + * + * <p>See also {@link android.app.time.cts.UnixEpochTimeTest} for SDK methods. */ @RunWith(AndroidJUnit4.class) public class UnixEpochTimeTest { - @Test - public void testEqualsAndHashcode() { - UnixEpochTime one1000one = new UnixEpochTime(1000, 1); - assertEqualsAndHashCode(one1000one, one1000one); - - UnixEpochTime one1000two = new UnixEpochTime(1000, 1); - assertEqualsAndHashCode(one1000one, one1000two); - - UnixEpochTime two1000 = new UnixEpochTime(1000, 2); - assertNotEquals(one1000one, two1000); - - UnixEpochTime one2000 = new UnixEpochTime(2000, 1); - assertNotEquals(one1000one, one2000); - } - - private static void assertEqualsAndHashCode(Object one, Object two) { - assertEquals(one, two); - assertEquals(one.hashCode(), two.hashCode()); - } - - @Test - public void testParceling() { - assertRoundTripParcelable(new UnixEpochTime(1000, 1)); - } - @Test(expected = IllegalArgumentException.class) public void testParseCommandLineArg_noElapsedRealtime() { ShellCommand testShellCommand = createShellCommandWithArgsAndOptions( @@ -91,22 +66,6 @@ public class UnixEpochTimeTest { } @Test - public void testAt() { - long timeMillis = 1000L; - int elapsedRealtimeMillis = 100; - UnixEpochTime unixEpochTime = new UnixEpochTime(elapsedRealtimeMillis, timeMillis); - // Reference time is after the timestamp. - UnixEpochTime at125 = unixEpochTime.at(125); - assertEquals(timeMillis + (125 - elapsedRealtimeMillis), at125.getUnixEpochTimeMillis()); - assertEquals(125, at125.getElapsedRealtimeMillis()); - - // Reference time is before the timestamp. - UnixEpochTime at75 = unixEpochTime.at(75); - assertEquals(timeMillis + (75 - elapsedRealtimeMillis), at75.getUnixEpochTimeMillis()); - assertEquals(75, at75.getElapsedRealtimeMillis()); - } - - @Test public void testElapsedRealtimeDifference() { UnixEpochTime value1 = new UnixEpochTime(1000, 123L); assertEquals(0, UnixEpochTime.elapsedRealtimeDifference(value1, value1)); diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index 1c41d06a3da2..9940ca3933c8 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -47,7 +47,7 @@ public final class BLASTBufferQueue { TransactionHangCallback callback); public interface TransactionHangCallback { - void onTransactionHang(boolean isGpuHang); + void onTransactionHang(String reason); } /** Create a new connection with the surface flinger. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java index d6803e8052c6..d3a9a672ec76 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java @@ -52,7 +52,7 @@ public class BubbleBadgeIconFactory extends BaseIconFactory { userBadgedAppIcon = new CircularRingDrawable(userBadgedAppIcon); } Bitmap userBadgedBitmap = createIconBitmap( - userBadgedAppIcon, 1, BITMAP_GENERATION_MODE_WITH_SHADOW); + userBadgedAppIcon, 1, MODE_WITH_SHADOW); return createIconBitmap(userBadgedBitmap); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java index 5dab8a071f76..4ded3ea951e5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java @@ -79,6 +79,6 @@ public class BubbleIconFactory extends BaseIconFactory { true /* shrinkNonAdaptiveIcons */, null /* outscale */, outScale); - return createIconBitmap(icon, outScale[0], BITMAP_GENERATION_MODE_WITH_SHADOW); + return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 8cee4f1dc8fb..6ce981e25f5e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -432,7 +432,8 @@ public class SplashscreenContentDrawer { final ShapeIconFactory factory = new ShapeIconFactory( SplashscreenContentDrawer.this.mContext, scaledIconDpi, mFinalIconSize); - final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(iconDrawable); + final Bitmap bitmap = factory.createScaledBitmap(iconDrawable, + BaseIconFactory.MODE_DEFAULT); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); createIconDrawable(new BitmapDrawable(bitmap), true, mHighResIconProvider.mLoadInDetail); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 7d1f130daaef..9d61c14e1435 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -101,9 +101,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL final int shadowRadiusID = taskInfo.isFocused ? R.dimen.freeform_decor_shadow_focused_thickness : R.dimen.freeform_decor_shadow_unfocused_thickness; - final boolean isFreeform = mTaskInfo.configuration.windowConfiguration.getWindowingMode() - == WindowConfiguration.WINDOWING_MODE_FREEFORM; - final boolean isDragResizeable = isFreeform && mTaskInfo.isResizeable; + final boolean isFreeform = + taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM; + final boolean isDragResizeable = isFreeform && taskInfo.isResizeable; WindowDecorLinearLayout oldRootView = mResult.mRootView; final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; @@ -114,6 +114,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL int outsetRightId = R.dimen.freeform_resize_handle; int outsetBottomId = R.dimen.freeform_resize_handle; + mRelayoutParams.reset(); mRelayoutParams.mRunningTaskInfo = taskInfo; mRelayoutParams.mLayoutResId = R.layout.caption_window_decoration; mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 01cab9aae312..b314163802ca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -91,7 +91,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> SurfaceControl mTaskBackgroundSurface; SurfaceControl mCaptionContainerSurface; - private CaptionWindowManager mCaptionWindowManager; + private WindowlessWindowManager mCaptionWindowManager; private SurfaceControlViewHost mViewHost; private final Rect mCaptionInsetsRect = new Rect(); @@ -199,13 +199,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); - final int decorContainerOffsetX = -loadResource(params.mOutsetLeftId); - final int decorContainerOffsetY = -loadResource(params.mOutsetTopId); + final Resources resources = mDecorWindowContext.getResources(); + final int decorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId); + final int decorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId); outResult.mWidth = taskBounds.width() - + loadResource(params.mOutsetRightId) + + loadDimensionPixelSize(resources, params.mOutsetRightId) - decorContainerOffsetX; outResult.mHeight = taskBounds.height() - + loadResource(params.mOutsetBottomId) + + loadDimensionPixelSize(resources, params.mOutsetBottomId) - decorContainerOffsetY; startT.setPosition( mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY) @@ -225,7 +226,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .build(); } - float shadowRadius = loadResource(params.mShadowRadiusId); + float shadowRadius = loadDimension(resources, params.mShadowRadiusId); int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor(); mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f; mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f; @@ -248,8 +249,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .build(); } - final int captionHeight = loadResource(params.mCaptionHeightId); - final int captionWidth = loadResource(params.mCaptionWidthId); + final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); + final int captionWidth = loadDimensionPixelSize(resources, params.mCaptionWidthId); //Prevent caption from going offscreen if task is too high up final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2; @@ -264,8 +265,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> if (mCaptionWindowManager == null) { // Put caption under a container surface because ViewRootImpl sets the destination frame // of windowless window layers and BLASTBufferQueue#update() doesn't support offset. - mCaptionWindowManager = new CaptionWindowManager( - mTaskInfo.getConfiguration(), mCaptionContainerSurface); + mCaptionWindowManager = new WindowlessWindowManager( + mTaskInfo.getConfiguration(), mCaptionContainerSurface, + null /* hostInputToken */); } // Caption view @@ -309,13 +311,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .setCrop(mTaskSurface, mTaskSurfaceCrop); } - private int loadResource(int resourceId) { - if (resourceId == Resources.ID_NULL) { - return 0; - } - return mDecorWindowContext.getResources().getDimensionPixelSize(resourceId); - } - /** * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or * registers {@link #mOnDisplaysChangedListener} if it doesn't. @@ -374,33 +369,18 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> releaseViews(); } - static class RelayoutResult<T extends View & TaskFocusStateConsumer> { - int mWidth; - int mHeight; - T mRootView; - - void reset() { - mWidth = 0; - mHeight = 0; - mRootView = null; - } - } - - private static class CaptionWindowManager extends WindowlessWindowManager { - CaptionWindowManager(Configuration config, SurfaceControl rootSurface) { - super(config, rootSurface, null /* hostInputToken */); - } - - @Override - public void setConfiguration(Configuration configuration) { - super.setConfiguration(configuration); + private static int loadDimensionPixelSize(Resources resources, int resourceId) { + if (resourceId == Resources.ID_NULL) { + return 0; } + return resources.getDimensionPixelSize(resourceId); } - interface SurfaceControlViewHostFactory { - default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) { - return new SurfaceControlViewHost(c, d, wmm); + private static float loadDimension(Resources resources, int resourceId) { + if (resourceId == Resources.ID_NULL) { + return 0; } + return resources.getDimension(resourceId); } static class RelayoutParams{ @@ -433,6 +413,23 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mOutsetLeftId = Resources.ID_NULL; mOutsetRightId = Resources.ID_NULL; } + } + static class RelayoutResult<T extends View & TaskFocusStateConsumer> { + int mWidth; + int mHeight; + T mRootView; + + void reset() { + mWidth = 0; + mHeight = 0; + mRootView = null; + } + } + + interface SurfaceControlViewHostFactory { + default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) { + return new SurfaceControlViewHost(c, d, wmm); + } } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt index fa783f231607..45eae2e2fe40 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt @@ -26,7 +26,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT import com.android.wm.shell.flicker.appWindowBecomesInvisible import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd @@ -35,7 +34,6 @@ import com.android.wm.shell.flicker.layerIsVisibleAtEnd import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible import com.android.wm.shell.flicker.splitScreenDismissed import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible -import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -95,7 +93,9 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible( primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false) - private fun secondaryAppBoundsIsFullscreenAtEnd_internal() { + @Presubmit + @Test + fun secondaryAppBoundsIsFullscreenAtEnd() { testSpec.assertLayers { this.isVisible(secondaryApp) .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) @@ -117,20 +117,6 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen @Presubmit @Test - fun secondaryAppBoundsIsFullscreenAtEnd() { - Assume.assumeFalse(isShellTransitionsEnabled) - secondaryAppBoundsIsFullscreenAtEnd_internal() - } - - @FlakyTest(bugId = 250528485) - @Test - fun secondaryAppBoundsIsFullscreenAtEnd_shellTransit() { - Assume.assumeTrue(isShellTransitionsEnabled) - secondaryAppBoundsIsFullscreenAtEnd_internal() - } - - @Presubmit - @Test fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp) @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt index 84a8c0a59f32..73159c981b82 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.splitscreen -import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.IwTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit @@ -146,19 +145,15 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB // robust enough to get the correct end state. } - @FlakyTest(bugId = 241524174) @Test fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) - @FlakyTest(bugId = 241524174) @Test fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp) - @FlakyTest(bugId = 241524174) @Test fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp) - @FlakyTest(bugId = 241524174) @Test fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( primaryApp, @@ -166,9 +161,6 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB portraitPosTop = true ) - // TODO(b/246490534): Move back to presubmit after withAppTransitionIdle is robust enough to - // get the correct end state. - @FlakyTest(bugId = 246490534) @Test fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( secondaryApp, @@ -176,11 +168,9 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB portraitPosTop = false ) - @FlakyTest(bugId = 241524174) @Test fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp) - @FlakyTest(bugId = 241524174) @Test fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp) diff --git a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml new file mode 100644 index 000000000000..8949a75d1a15 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 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> + <!-- Resources used in WindowDecorationTests --> + <dimen name="test_freeform_decor_caption_height">32dp</dimen> + <dimen name="test_freeform_decor_caption_width">216dp</dimen> + <dimen name="test_window_decor_left_outset">10dp</dimen> + <dimen name="test_window_decor_top_outset">20dp</dimen> + <dimen name="test_window_decor_right_outset">30dp</dimen> + <dimen name="test_window_decor_bottom_outset">40dp</dimen> + <dimen name="test_window_decor_shadow_radius">5dp</dimen> + <dimen name="test_window_decor_resize_handle">10dp</dimen> +</resources>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 103c8dab17d5..4d37e5dbc4dc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -50,12 +50,13 @@ import android.view.WindowManager.LayoutParams; import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; -import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.tests.R; import org.junit.Before; import org.junit.Test; @@ -145,8 +146,11 @@ public class WindowDecorationTests extends ShellTestCase { // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is // 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets(R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle, - R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle); + mRelayoutParams.setOutsets( + R.dimen.test_window_decor_left_outset, + R.dimen.test_window_decor_top_outset, + R.dimen.test_window_decor_right_outset, + R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -196,13 +200,11 @@ public class WindowDecorationTests extends ShellTestCase { // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is // 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; -// int outsetLeftId = R.dimen.split_divider_bar_width; -// int outsetTopId = R.dimen.gestures_onehanded_drag_threshold; -// int outsetRightId = R.dimen.freeform_resize_handle; -// int outsetBottomId = R.dimen.bubble_dismiss_target_padding_x; -// mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId); - mRelayoutParams.setOutsets(R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle, - R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle); + mRelayoutParams.setOutsets( + R.dimen.test_window_decor_left_outset, + R.dimen.test_window_decor_top_outset, + R.dimen.test_window_decor_right_outset, + R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -211,8 +213,8 @@ public class WindowDecorationTests extends ShellTestCase { verify(decorContainerSurfaceBuilder).setParent(taskSurface); verify(decorContainerSurfaceBuilder).setContainerLayer(); verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true); - verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -60, -60); - verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 420, 220); + verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40); + verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220); verify(taskBackgroundSurfaceBuilder).setParent(taskSurface); verify(taskBackgroundSurfaceBuilder).setEffectLayer(); @@ -225,36 +227,34 @@ public class WindowDecorationTests extends ShellTestCase { verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); verify(captionContainerSurfaceBuilder).setContainerLayer(); - verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -6, -156); - verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 432); + verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -46, 8); + verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any()); verify(mMockSurfaceControlViewHost) .setView(same(mMockView), - argThat(lp -> lp.height == 432 + argThat(lp -> lp.height == 64 && lp.width == 432 && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0)); if (ViewRootImpl.CAPTION_ON_SHELL) { verify(mMockView).setTaskFocusState(true); verify(mMockWindowContainerTransaction) .addRectInsetsProvider(taskInfo.token, - new Rect(100, 300, 400, 516), + new Rect(100, 300, 400, 332), new int[] { InsetsState.ITYPE_CAPTION_BAR }); } verify(mMockSurfaceControlFinishT) .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y); verify(mMockSurfaceControlFinishT) - .setCrop(taskSurface, new Rect(-60, -60, 360, 160)); + .setCrop(taskSurface, new Rect(-20, -40, 360, 180)); verify(mMockSurfaceControlStartT) .show(taskSurface); - assertEquals(420, mRelayoutResult.mWidth); + assertEquals(380, mRelayoutResult.mWidth); assertEquals(220, mRelayoutResult.mHeight); - - } @Test @@ -293,8 +293,11 @@ public class WindowDecorationTests extends ShellTestCase { // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is // 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets(R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle, - R.dimen.freeform_resize_handle, R.dimen.freeform_resize_handle); + mRelayoutParams.setOutsets( + R.dimen.test_window_decor_left_outset, + R.dimen.test_window_decor_top_outset, + R.dimen.test_window_decor_right_outset, + R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -365,7 +368,8 @@ public class WindowDecorationTests extends ShellTestCase { private TestWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) { - return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer, + return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(), + mMockDisplayController, mMockShellTaskOrganizer, taskInfo, testSurface, new MockObjectSupplier<>(mMockSurfaceControlBuilders, () -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))), @@ -417,12 +421,10 @@ public class WindowDecorationTests extends ShellTestCase { @Override void relayout(ActivityManager.RunningTaskInfo taskInfo) { - mRelayoutParams.mLayoutResId = 0; - mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_width; - mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width; - mRelayoutParams.mShadowRadiusId = - R.dimen.freeform_decor_shadow_unfocused_thickness; + mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height; + mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width; + mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius; relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mMockView, mRelayoutResult); diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 2547a963eb31..d975e96f193b 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6632,8 +6632,8 @@ public class AudioManager { } } if (k == ports.size()) { - // this hould never happen - Log.e(TAG, "updatePortConfig port not found for handle: "+port.handle().id()); + // This can happen in case of stale audio patch referring to a removed device and is + // handled by the caller. return null; } AudioGainConfig gainCfg = portCfg.gain(); diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java index 74c549943a74..ee825881ecae 100644 --- a/media/java/android/media/midi/MidiManager.java +++ b/media/java/android/media/midi/MidiManager.java @@ -240,8 +240,7 @@ public final class MidiManager { * @param handler The {@link android.os.Handler Handler} that will be used for delivering the * device notifications. If handler is null, then the thread used for the * callback is unspecified. - * @deprecated Use the {@link #registerDeviceCallback} - * method with Executor and transport instead. + * @deprecated Use {@link #registerDeviceCallback(int, Executor, DeviceCallback)} instead. */ @Deprecated public void registerDeviceCallback(DeviceCallback callback, Handler handler) { diff --git a/packages/CarrierDefaultApp/Android.bp b/packages/CarrierDefaultApp/Android.bp index 1e56a9340294..6990ad0fbd7d 100644 --- a/packages/CarrierDefaultApp/Android.bp +++ b/packages/CarrierDefaultApp/Android.bp @@ -10,7 +10,7 @@ package { android_app { name: "CarrierDefaultApp", srcs: ["src/**/*.java"], - static_libs: ["SliceStore"], + libs: ["SliceStore"], platform_apis: true, certificate: "platform", } diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml index 9566f22a7274..a5b104b597ee 100644 --- a/packages/CarrierDefaultApp/AndroidManifest.xml +++ b/packages/CarrierDefaultApp/AndroidManifest.xml @@ -28,6 +28,7 @@ <uses-permission android:name="android.permission.NETWORK_BYPASS_PRIVATE_DNS" /> <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <application android:label="@string/app_name" diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt new file mode 100644 index 000000000000..d6f1b5f5c8e9 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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.credentialmanager.jetpack + +import android.app.slice.Slice +import android.graphics.drawable.Icon + +/** + * UI representation for a credential entry used during the get credential flow. + * + * TODO: move to jetpack. + */ +abstract class CredentialEntryUi( + val credentialTypeIcon: Icon, + val profileIcon: Icon?, + val lastUsedTimeMillis: Long?, + val note: CharSequence?, +) { + companion object { + fun fromSlice(slice: Slice): CredentialEntryUi { + return when (slice.spec?.type) { + TYPE_PUBLIC_KEY_CREDENTIAL -> PasskeyCredentialEntryUi.fromSlice(slice) + TYPE_PASSWORD_CREDENTIAL -> PasswordCredentialEntryUi.fromSlice(slice) + else -> throw IllegalArgumentException("Unexpected type: ${slice.spec?.type}") + } + } + + const val TYPE_PUBLIC_KEY_CREDENTIAL: String = + "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" + const val TYPE_PASSWORD_CREDENTIAL: String = "androidx.credentials.TYPE_PASSWORD" + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt new file mode 100644 index 000000000000..bb3b206500b4 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 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.credentialmanager.jetpack + +import android.app.slice.Slice +import android.credentials.ui.Entry +import android.graphics.drawable.Icon + +class PasskeyCredentialEntryUi( + val userName: CharSequence, + val userDisplayName: CharSequence?, + credentialTypeIcon: Icon, + profileIcon: Icon?, + lastUsedTimeMillis: Long?, + note: CharSequence?, +) : CredentialEntryUi(credentialTypeIcon, profileIcon, lastUsedTimeMillis, note) { + companion object { + fun fromSlice(slice: Slice): CredentialEntryUi { + var userName: CharSequence? = null + var userDisplayName: CharSequence? = null + var credentialTypeIcon: Icon? = null + var profileIcon: Icon? = null + var lastUsedTimeMillis: Long? = null + var note: CharSequence? = null + + val items = slice.items + items.forEach { + if (it.hasHint(Entry.HINT_USER_NAME)) { + userName = it.text + } else if (it.hasHint(Entry.HINT_PASSKEY_USER_DISPLAY_NAME)) { + userDisplayName = it.text + } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) { + credentialTypeIcon = it.icon + } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) { + profileIcon = it.icon + } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) { + lastUsedTimeMillis = it.long + } else if (it.hasHint(Entry.HINT_NOTE)) { + note = it.text + } + } + // TODO: fail NPE more elegantly. + return PasskeyCredentialEntryUi( + userName!!, userDisplayName, credentialTypeIcon!!, + profileIcon, lastUsedTimeMillis, note, + ) + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt new file mode 100644 index 000000000000..7311b7081343 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 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.credentialmanager.jetpack + +import android.app.slice.Slice +import android.credentials.ui.Entry +import android.graphics.drawable.Icon + +/** + * UI representation for a password credential entry used during the get credential flow. + * + * TODO: move to jetpack. + */ +class PasswordCredentialEntryUi( + val userName: CharSequence, + val password: CharSequence, + credentialTypeIcon: Icon, + profileIcon: Icon?, + lastUsedTimeMillis: Long?, + note: CharSequence?, +) : CredentialEntryUi(credentialTypeIcon, profileIcon, lastUsedTimeMillis, note) { + companion object { + fun fromSlice(slice: Slice): CredentialEntryUi { + var userName: CharSequence? = null + var password: CharSequence? = null + var credentialTypeIcon: Icon? = null + var profileIcon: Icon? = null + var lastUsedTimeMillis: Long? = null + var note: CharSequence? = null + + val items = slice.items + items.forEach { + if (it.hasHint(Entry.HINT_USER_NAME)) { + userName = it.text + } else if (it.hasHint(Entry.HINT_PASSWORD_VALUE)) { + password = it.text + } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) { + credentialTypeIcon = it.icon + } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) { + profileIcon = it.icon + } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) { + lastUsedTimeMillis = it.long + } else if (it.hasHint(Entry.HINT_NOTE)) { + note = it.text + } + } + // TODO: fail NPE more elegantly. + return PasswordCredentialEntryUi( + userName!!, password!!, credentialTypeIcon!!, + profileIcon, lastUsedTimeMillis, note, + ) + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt new file mode 100644 index 000000000000..fad3309fb86f --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 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.credentialmanager.jetpack + +import android.app.slice.Slice +import android.credentials.ui.Entry +import android.graphics.drawable.Icon + +/** + * UI representation for a save entry used during the create credential flow. + * + * TODO: move to jetpack. + */ +class SaveEntryUi( + val userProviderAccountName: CharSequence, + val credentialTypeIcon: Icon?, + val profileIcon: Icon?, + val passwordCount: Int?, + val passkeyCount: Int?, + val totalCredentialCount: Int?, + val lastUsedTimeMillis: Long?, +) { + companion object { + fun fromSlice(slice: Slice): SaveEntryUi { + var userProviderAccountName: CharSequence? = null + var credentialTypeIcon: Icon? = null + var profileIcon: Icon? = null + var passwordCount: Int? = null + var passkeyCount: Int? = null + var totalCredentialCount: Int? = null + var lastUsedTimeMillis: Long? = null + + + val items = slice.items + items.forEach { + if (it.hasHint(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME)) { + userProviderAccountName = it.text + } else if (it.hasHint(Entry.HINT_CREDENTIAL_TYPE_ICON)) { + credentialTypeIcon = it.icon + } else if (it.hasHint(Entry.HINT_PROFILE_ICON)) { + profileIcon = it.icon + } else if (it.hasHint(Entry.HINT_PASSWORD_COUNT)) { + passwordCount = it.int + } else if (it.hasHint(Entry.HINT_PASSKEY_COUNT)) { + passkeyCount = it.int + } else if (it.hasHint(Entry.HINT_TOTAL_CREDENTIAL_COUNT)) { + totalCredentialCount = it.int + } else if (it.hasHint(Entry.HINT_LAST_USED_TIME_MILLIS)) { + lastUsedTimeMillis = it.long + } + } + // TODO: fail NPE more elegantly. + return SaveEntryUi( + userProviderAccountName!!, credentialTypeIcon, profileIcon, + passwordCount, passkeyCount, totalCredentialCount, lastUsedTimeMillis, + ) + } + } +} diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index acb22dac9854..4af25893ea37 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -25,6 +25,7 @@ import com.android.settingslib.spa.gallery.home.HomePageProvider import com.android.settingslib.spa.gallery.page.ArgumentPageProvider import com.android.settingslib.spa.gallery.page.FooterPageProvider import com.android.settingslib.spa.gallery.page.IllustrationPageProvider +import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider import com.android.settingslib.spa.gallery.page.SliderPageProvider import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider @@ -66,6 +67,7 @@ object GallerySpaEnvironment : SpaEnvironment() { IllustrationPageProvider, CategoryPageProvider, ActionButtonPageProvider, + ProgressBarPageProvider, ), rootPages = listOf( HomePageProvider.createSettingsPage(), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt index e40775a95813..7fd49db93748 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt @@ -31,6 +31,7 @@ import com.android.settingslib.spa.gallery.page.ArgumentPageModel import com.android.settingslib.spa.gallery.page.ArgumentPageProvider import com.android.settingslib.spa.gallery.page.FooterPageProvider import com.android.settingslib.spa.gallery.page.IllustrationPageProvider +import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider import com.android.settingslib.spa.gallery.page.SliderPageProvider import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider @@ -54,6 +55,7 @@ object HomePageProvider : SettingsPageProvider { IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ) } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt new file mode 100644 index 000000000000..dc45df4a0374 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2022 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.spa.gallery.page + +import android.os.Bundle +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.SystemUpdate +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPage +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.preference.ProgressBarPreference +import com.android.settingslib.spa.widget.preference.ProgressBarPreferenceModel +import com.android.settingslib.spa.widget.preference.ProgressBarWithDataPreference +import com.android.settingslib.spa.widget.scaffold.RegularScaffold +import com.android.settingslib.spa.widget.ui.CircularLoadingBar +import com.android.settingslib.spa.widget.ui.CircularProgressBar +import com.android.settingslib.spa.widget.ui.LinearLoadingBar +import kotlinx.coroutines.delay + +private const val TITLE = "Sample ProgressBar" + +object ProgressBarPageProvider : SettingsPageProvider { + override val name = "ProgressBar" + + fun buildInjectEntry(): SettingsEntryBuilder { + return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name)) + .setIsAllowSearch(true) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + } + + @Composable + override fun Page(arguments: Bundle?) { + // Mocks a loading time of 2 seconds. + var loading by remember { mutableStateOf(true) } + LaunchedEffect(Unit) { + delay(2000) + loading = false + } + + RegularScaffold(title = TITLE) { + // Auto update the progress and finally jump tp 0.4f. + var progress by remember { mutableStateOf(0f) } + LaunchedEffect(Unit) { + delay(2000) + while (progress < 1f) { + delay(100) + progress += 0.01f + } + delay(500) + progress = 0.4f + } + + // Show as a placeholder for progress bar + LargeProgressBar(progress) + // The remaining information only shows after loading complete. + if (!loading) { + SimpleProgressBar() + ProgressBarWithData() + CircularProgressBar(progress = progress, radius = 160f) + } + } + + // Add loading bar examples, running for 2 seconds. + LinearLoadingBar(isLoading = loading, yOffset = 64.dp) + CircularLoadingBar(isLoading = loading) + } +} + +@Composable +private fun LargeProgressBar(progress: Float) { + ProgressBarPreference(object : ProgressBarPreferenceModel { + override val title = "Large Progress Bar" + override val progress = progress + override val height = 20f + }) +} + +@Composable +private fun SimpleProgressBar() { + ProgressBarPreference(object : ProgressBarPreferenceModel { + override val title = "Simple Progress Bar" + override val progress = 0.2f + override val icon = Icons.Outlined.SystemUpdate + }) +} + +@Composable +private fun ProgressBarWithData() { + ProgressBarWithDataPreference(model = object : ProgressBarPreferenceModel { + override val title = "Progress Bar with Data" + override val progress = 0.2f + override val icon = Icons.Outlined.Delete + }, data = "25G") +} + +@Preview(showBackground = true) +@Composable +private fun ProgressBarPagePreview() { + SettingsTheme { + ProgressBarPageProvider.Page(null) + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt index 9a34dbf36735..6135203ec703 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt @@ -72,7 +72,7 @@ internal fun BaseLayout( } @Composable -private fun BaseIcon( +internal fun BaseIcon( icon: @Composable (() -> Unit)?, modifier: Modifier, paddingStart: Dp, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ProgressBarPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ProgressBarPreference.kt new file mode 100644 index 000000000000..b8c59ad07cfd --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ProgressBarPreference.kt @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2022 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.spa.widget.preference + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.widget.ui.LinearProgressBar +import com.android.settingslib.spa.widget.ui.SettingsTitle + +/** + * The widget model for [ProgressBarPreference] widget. + */ +interface ProgressBarPreferenceModel { + /** + * The title of this [ProgressBarPreference]. + */ + val title: String + + /** + * The progress fraction of the ProgressBar. Should be float in range [0f, 1f] + */ + val progress: Float + + /** + * The icon image for [ProgressBarPreference]. If not specified, hides the icon by default. + */ + val icon: ImageVector? + get() = null + + /** + * The height of the ProgressBar. + */ + val height: Float + get() = 4f + + /** + * Indicates whether to use rounded corner for the progress bars. + */ + val roundedCorner: Boolean + get() = true +} + +/** + * Progress bar preference widget. + * + * Data is provided through [ProgressBarPreferenceModel]. + */ +@Composable +fun ProgressBarPreference(model: ProgressBarPreferenceModel) { + ProgressBarPreference( + title = model.title, + progress = model.progress, + icon = model.icon, + height = model.height, + roundedCorner = model.roundedCorner, + ) +} + +/** + * Progress bar with data preference widget. + */ +@Composable +fun ProgressBarWithDataPreference(model: ProgressBarPreferenceModel, data: String) { + val icon = model.icon + ProgressBarWithDataPreference( + title = model.title, + data = data, + progress = model.progress, + icon = if (icon != null) ({ + Icon(imageVector = icon, contentDescription = null) + }) else null, + height = model.height, + roundedCorner = model.roundedCorner, + ) +} + +@Composable +internal fun ProgressBarPreference( + title: String, + progress: Float, + icon: ImageVector? = null, + height: Float = 4f, + roundedCorner: Boolean = true, +) { + BaseLayout( + title = title, + subTitle = { + LinearProgressBar(progress, height, roundedCorner) + }, + icon = if (icon != null) ({ + Icon(imageVector = icon, contentDescription = null) + }) else null, + ) +} + + +@Composable +internal fun ProgressBarWithDataPreference( + title: String, + data: String, + progress: Float, + icon: (@Composable () -> Unit)? = null, + height: Float = 4f, + roundedCorner: Boolean = true, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(end = SettingsDimension.itemPaddingEnd), + verticalAlignment = Alignment.CenterVertically, + ) { + BaseIcon(icon, Modifier, SettingsDimension.itemPaddingStart) + TitleWithData( + title = title, + data = data, + subTitle = { + LinearProgressBar(progress, height, roundedCorner) + }, + modifier = Modifier + .weight(1f) + .padding(vertical = SettingsDimension.itemPaddingVertical), + ) + } +} + +@Composable +private fun TitleWithData( + title: String, + data: String, + subTitle: @Composable () -> Unit, + modifier: Modifier +) { + Column(modifier) { + Row { + Box(modifier = Modifier.weight(1f)) { + SettingsTitle(title) + } + Text( + text = data, + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.titleMedium, + ) + } + subTitle() + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/LoadingBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/LoadingBar.kt new file mode 100644 index 000000000000..1741f134f3d1 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/LoadingBar.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 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.spa.widget.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.absoluteOffset +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * Indeterminate linear progress bar. Expresses an unspecified wait time. + */ +@Composable +fun LinearLoadingBar( + isLoading: Boolean, + xOffset: Dp = 0.dp, + yOffset: Dp = 0.dp +) { + if (isLoading) { + LinearProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .absoluteOffset(xOffset, yOffset) + ) + } +} + +/** + * Indeterminate circular progress bar. Expresses an unspecified wait time. + */ +@Composable +fun CircularLoadingBar(isLoading: Boolean) { + if (isLoading) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/ProgressBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/ProgressBar.kt new file mode 100644 index 000000000000..5d8502db4986 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/ProgressBar.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2022 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.spa.widget.ui + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.progressSemantics +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.unit.dp + +/** + * Determinate linear progress bar. Displays the current progress of the whole process. + * + * Rounded corner is supported and enabled by default. + */ +@Composable +fun LinearProgressBar( + progress: Float, + height: Float = 4f, + roundedCorner: Boolean = true +) { + Box(modifier = Modifier.padding(top = 8.dp, bottom = 8.dp)) { + val color = MaterialTheme.colorScheme.onSurface + val trackColor = MaterialTheme.colorScheme.surfaceVariant + Canvas( + Modifier + .progressSemantics(progress) + .fillMaxWidth() + .height(height.dp) + ) { + drawLinearBarTrack(trackColor, roundedCorner) + drawLinearBar(progress, color, roundedCorner) + } + } +} + +private fun DrawScope.drawLinearBar( + endFraction: Float, + color: Color, + roundedCorner: Boolean +) { + val width = endFraction * size.width + drawRoundRect( + color = color, + size = Size(width, size.height), + cornerRadius = if (roundedCorner) CornerRadius( + size.height / 2, + size.height / 2 + ) else CornerRadius.Zero, + ) +} + +private fun DrawScope.drawLinearBarTrack( + color: Color, + roundedCorner: Boolean +) = drawLinearBar(1f, color, roundedCorner) + +/** + * Determinate circular progress bar. Displays the current progress of the whole process. + * + * Displayed in default material3 style, and rounded corner is not supported. + */ +@Composable +fun CircularProgressBar(progress: Float, radius: Float = 40f) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator( + progress = progress, + modifier = Modifier.size(radius.dp, radius.dp) + ) + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ProgressBarPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ProgressBarPreferenceTest.kt new file mode 100644 index 000000000000..5611f8ce14db --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ProgressBarPreferenceTest.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 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.spa.widget.preference + +import androidx.compose.ui.semantics.ProgressBarRangeInfo +import androidx.compose.ui.semantics.SemanticsProperties.ProgressBarRangeInfo +import androidx.compose.ui.test.SemanticsMatcher +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ProgressBarPreferenceTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun title_displayed() { + composeTestRule.setContent { + ProgressBarPreference(object : ProgressBarPreferenceModel { + override val title = "Title" + override val progress = 0.2f + }) + } + composeTestRule.onNodeWithText("Title").assertIsDisplayed() + } + + @Test + fun data_displayed() { + composeTestRule.setContent { + ProgressBarWithDataPreference(model = object : ProgressBarPreferenceModel { + override val title = "Title" + override val progress = 0.2f + }, data = "Data") + } + composeTestRule.onNodeWithText("Title").assertIsDisplayed() + composeTestRule.onNodeWithText("Data").assertIsDisplayed() + } + + @Test + fun progressBar_displayed() { + composeTestRule.setContent { + ProgressBarPreference(object : ProgressBarPreferenceModel { + override val title = "Title" + override val progress = 0.2f + }) + } + + fun progressEqualsTo(progress: Float): SemanticsMatcher = + SemanticsMatcher.expectValue( + ProgressBarRangeInfo, + ProgressBarRangeInfo(progress, 0f..1f, 0) + ) + composeTestRule.onNode(progressEqualsTo(0.2f)).assertIsDisplayed() + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SliderPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SliderPreferenceTest.kt index 7ae11758a0ce..3e5dd527a6b3 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SliderPreferenceTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SliderPreferenceTest.kt @@ -16,6 +16,9 @@ package com.android.settingslib.spa.widget.preference +import androidx.compose.ui.semantics.ProgressBarRangeInfo +import androidx.compose.ui.semantics.SemanticsProperties.ProgressBarRangeInfo +import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText @@ -41,5 +44,20 @@ class SliderPreferenceTest { composeTestRule.onNodeWithText("Slider").assertIsDisplayed() } - // TODO: Add more unit tests for SliderPreference widget. + @Test + fun slider_displayed() { + composeTestRule.setContent { + SliderPreference(object : SliderPreferenceModel { + override val title = "Slider" + override val initValue = 40 + }) + } + + fun progressEqualsTo(progress: Float): SemanticsMatcher = + SemanticsMatcher.expectValue( + ProgressBarRangeInfo, + ProgressBarRangeInfo(progress, 0f..100f, 0) + ) + composeTestRule.onNode(progressEqualsTo(40f)).assertIsDisplayed() + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index 91b852ab9f67..6641db1e6449 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -235,7 +235,7 @@ public class A2dpProfile implements LocalBluetoothProfile { /** * @return whether high quality audio is enabled or not */ - @RequiresApi(Build.VERSION_CODES.TIRAMISU) + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public boolean isHighQualityAudioEnabled(BluetoothDevice device) { BluetoothDevice bluetoothDevice = (device != null) ? device : getActiveDevice(); if (bluetoothDevice == null) { @@ -287,7 +287,7 @@ public class A2dpProfile implements LocalBluetoothProfile { * @param device to get codec label from * @return the label associated with the device codec */ - @RequiresApi(Build.VERSION_CODES.TIRAMISU) + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public String getHighQualityAudioOptionLabel(BluetoothDevice device) { BluetoothDevice bluetoothDevice = (device != null) ? device : getActiveDevice(); int unknownCodecId = R.string.bluetooth_profile_a2dp_high_quality_unknown_codec; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java index 39977dfa5c80..f969a63dc663 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/MobileNetworkTypeIconsTest.java @@ -41,19 +41,19 @@ public class MobileNetworkTypeIconsTest { MobileNetworkTypeIcon icon = MobileNetworkTypeIcons.getNetworkTypeIcon(TelephonyIcons.FOUR_G); - assertThat(icon.getName()).isEqualTo(TelephonyIcons.H_PLUS.name); + assertThat(icon.getName()).isEqualTo(TelephonyIcons.FOUR_G.name); assertThat(icon.getIconResId()).isEqualTo(TelephonyIcons.ICON_4G); } @Test public void getNetworkTypeIcon_unknown_returnsUnknown() { - SignalIcon.MobileIconGroup unknownGroup = - new SignalIcon.MobileIconGroup("testUnknownNameHere", 45, 6); + SignalIcon.MobileIconGroup unknownGroup = new SignalIcon.MobileIconGroup( + "testUnknownNameHere", /* dataContentDesc= */ 45, /* dataType= */ 6); MobileNetworkTypeIcon icon = MobileNetworkTypeIcons.getNetworkTypeIcon(unknownGroup); assertThat(icon.getName()).isEqualTo("testUnknownNameHere"); - assertThat(icon.getIconResId()).isEqualTo(45); - assertThat(icon.getContentDescriptionResId()).isEqualTo(6); + assertThat(icon.getIconResId()).isEqualTo(6); + assertThat(icon.getContentDescriptionResId()).isEqualTo(45); } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 9747a6c5ca70..aea2f5235201 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -817,7 +817,8 @@ public class SettingsBackupTest { Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, - Settings.Secure.UI_TRANSLATION_ENABLED); + Settings.Secure.UI_TRANSLATION_ENABLED, + Settings.Secure.CREDENTIAL_SERVICE); @Test public void systemSettingsBackedUpOrDenied() { diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index fecf1241acf8..8acc3e7a1a9a 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -715,6 +715,9 @@ <!-- Permission required for CTS test - ActivityPermissionRationaleTest --> <uses-permission android:name="android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY" /> + <!-- Permission required for CTS test - CtsDeviceLockTestCases --> + <uses-permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/SystemUI/res-keyguard/drawable/fullscreen_userswitcher_menu_item_divider.xml b/packages/SystemUI/res-keyguard/drawable/fullscreen_userswitcher_menu_item_divider.xml new file mode 100644 index 000000000000..de0e526a97c3 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/fullscreen_userswitcher_menu_item_divider.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" > + <size android:height="@dimen/bouncer_user_switcher_popup_items_divider_height"/> + <solid android:color="@color/user_switcher_fullscreen_bg"/> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 46f6ab2399d1..0a55cf779683 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -119,6 +119,7 @@ <dimen name="bouncer_user_switcher_width">248dp</dimen> <dimen name="bouncer_user_switcher_popup_header_height">12dp</dimen> <dimen name="bouncer_user_switcher_popup_divider_height">4dp</dimen> + <dimen name="bouncer_user_switcher_popup_items_divider_height">2dp</dimen> <dimen name="bouncer_user_switcher_item_padding_vertical">10dp</dimen> <dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen> <dimen name="bouncer_user_switcher_header_padding_end">44dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 637ac1911a85..d4d8843acdea 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -641,7 +641,7 @@ <!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] --> <string name="quick_settings_color_correction_label">Color correction</string> <!-- QuickSettings: Control panel: Label for button that navigates to user settings. [CHAR LIMIT=NONE] --> - <string name="quick_settings_more_user_settings">User settings</string> + <string name="quick_settings_more_user_settings">Manage users</string> <!-- QuickSettings: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] --> <string name="quick_settings_done">Done</string> <!-- QuickSettings: Control panel: Label for button that dismisses user switcher control panel. [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt index e3c21cca2263..cd272635905b 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -21,7 +21,6 @@ import android.os.Handler import android.os.UserHandle import android.provider.Settings import android.util.Log -import com.android.systemui.dagger.qualifiers.Main import com.android.internal.annotations.Keep import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.ClockId @@ -31,7 +30,6 @@ import com.android.systemui.plugins.ClockProviderPlugin import com.android.systemui.plugins.PluginListener import com.android.systemui.shared.plugins.PluginManager import com.google.gson.Gson -import javax.inject.Inject private val TAG = ClockRegistry::class.simpleName private const val DEBUG = true @@ -43,13 +41,6 @@ open class ClockRegistry( val handler: Handler, defaultClockProvider: ClockProvider ) { - @Inject constructor( - context: Context, - pluginManager: PluginManager, - @Main handler: Handler, - defaultClockProvider: DefaultClockProvider - ) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { } - // Usually this would be a typealias, but a SAM provides better java interop fun interface ClockChangeListener { fun onClockChanged() diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index f5582761c0ae..558cf982a1ff 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -106,7 +106,6 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; @@ -2366,10 +2365,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab stopListeningForFace(FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER); } - public boolean isFaceScanning() { - return mFaceRunningState == BIOMETRIC_STATE_RUNNING; - } - private void updateFaceListeningState(int action, @NonNull FaceAuthUiEvent faceAuthUiEvent) { // If this message exists, we should not authenticate again until this message is // consumed by the handler @@ -3796,4 +3791,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } mListenModels.print(pw); } + + /** + * Schedules a watchdog for the face and fingerprint BiometricScheduler. + * Cancels all operations in the scheduler if it is hung for 10 seconds. + */ + public void startBiometricWatchdog() { + if (mFaceManager != null) { + mFaceManager.scheduleWatchdog(); + } + if (mFpm != null) { + mFpm.scheduleWatchdog(); + } + } } diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java index c4be1ba53503..72a44bd198f2 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/ClockModule.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java @@ -21,9 +21,14 @@ import java.util.List; import dagger.Module; import dagger.Provides; -/** Dagger Module for clock package. */ +/** + * Dagger Module for clock package. + * + * @deprecated Migrate to ClockRegistry + */ @Module -public abstract class ClockModule { +@Deprecated +public abstract class ClockInfoModule { /** */ @Provides diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java new file mode 100644 index 000000000000..f43f559b4234 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 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.dagger; + +import android.content.Context; +import android.os.Handler; + +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Application; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.shared.clocks.ClockRegistry; +import com.android.systemui.shared.clocks.DefaultClockProvider; +import com.android.systemui.shared.plugins.PluginManager; + +import dagger.Module; +import dagger.Provides; + +/** Dagger Module for clocks. */ +@Module +public abstract class ClockRegistryModule { + /** Provide the ClockRegistry as a singleton so that it is not instantiated more than once. */ + @Provides + @SysUISingleton + public static ClockRegistry getClockRegistry( + @Application Context context, + PluginManager pluginManager, + @Main Handler handler, + DefaultClockProvider defaultClockProvider) { + return new ClockRegistry(context, pluginManager, handler, defaultClockProvider); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/Dumpable.java b/packages/SystemUI/src/com/android/systemui/Dumpable.java index 652595100c0f..73fdce6c9045 100644 --- a/packages/SystemUI/src/com/android/systemui/Dumpable.java +++ b/packages/SystemUI/src/com/android/systemui/Dumpable.java @@ -30,7 +30,6 @@ public interface Dumpable { /** * Called when it's time to dump the internal state - * @param fd A file descriptor. * @param pw Where to write your dump to. * @param args Arguments. */ diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt index c5955860aebf..7002811c3c83 100644 --- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt @@ -123,7 +123,7 @@ class FaceScanningOverlay( } override fun enableShowProtection(show: Boolean) { - val showScanningAnimNow = keyguardUpdateMonitor.isFaceScanning && show + val showScanningAnimNow = keyguardUpdateMonitor.isFaceDetectionRunning && show if (showScanningAnimNow == showScanningAnim) { return } diff --git a/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt b/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt new file mode 100644 index 000000000000..4c3a7ff4e2eb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt @@ -0,0 +1,7 @@ +package com.android.systemui + +import com.android.systemui.dump.nano.SystemUIProtoDump + +interface ProtoDumpable : Dumpable { + fun dumpProto(systemUIProtoDump: SystemUIProtoDump, args: Array<String>) +} diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java index 7bcba3cc1c46..50e03992df49 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java @@ -121,6 +121,6 @@ public class SystemUIService extends Service { DumpHandler.PRIORITY_ARG_CRITICAL}; } - mDumpHandler.dump(pw, massagedArgs); + mDumpHandler.dump(fd, pw, massagedArgs); } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java new file mode 100644 index 000000000000..9d2a4d74a0e6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 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.accessibility.floatingmenu; + +import android.graphics.PointF; + +import androidx.dynamicanimation.animation.DynamicAnimation; + +/** + * Controls the interaction animations of the menu view {@link MenuView}. + */ +class MenuAnimationController { + private final MenuView mMenuView; + + MenuAnimationController(MenuView menuView) { + mMenuView = menuView; + } + + void moveToPosition(PointF position) { + DynamicAnimation.TRANSLATION_X.setValue(mMenuView, position.x); + DynamicAnimation.TRANSLATION_Y.setValue(mMenuView, position.y); + + mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java index 698d60a5b13e..0649463c8238 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java @@ -23,15 +23,18 @@ import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTT import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets; import static com.android.systemui.accessibility.floatingmenu.MenuViewAppearance.MenuSizeType.SMALL; +import android.annotation.FloatRange; import android.content.Context; import android.database.ContentObserver; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.provider.Settings; +import android.text.TextUtils; import com.android.internal.accessibility.dialog.AccessibilityTarget; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Prefs; import java.util.List; @@ -39,9 +42,16 @@ import java.util.List; * Stores and observe the settings contents for the menu view. */ class MenuInfoRepository { + @FloatRange(from = 0.0, to = 1.0) + private static final float DEFAULT_MENU_POSITION_X_PERCENT = 1.0f; + + @FloatRange(from = 0.0, to = 1.0) + private static final float DEFAULT_MENU_POSITION_Y_PERCENT = 0.9f; + private final Context mContext; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final OnSettingsContentsChanged mSettingsContentsCallback; + private Position mPercentagePosition; private final ContentObserver mMenuTargetFeaturesContentObserver = new ContentObserver(mHandler) { @@ -65,6 +75,12 @@ class MenuInfoRepository { MenuInfoRepository(Context context, OnSettingsContentsChanged settingsContentsChanged) { mContext = context; mSettingsContentsCallback = settingsContentsChanged; + + mPercentagePosition = getStartPosition(); + } + + void loadMenuPosition(OnInfoReady<Position> callback) { + callback.onReady(mPercentagePosition); } void loadMenuTargetFeatures(OnInfoReady<List<AccessibilityTarget>> callback) { @@ -75,6 +91,21 @@ class MenuInfoRepository { callback.onReady(getMenuSizeTypeFromSettings(mContext)); } + void updateMenuSavingPosition(Position percentagePosition) { + mPercentagePosition = percentagePosition; + Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, + percentagePosition.toString()); + } + + private Position getStartPosition() { + final String absolutePositionString = Prefs.getString(mContext, + Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null); + + return TextUtils.isEmpty(absolutePositionString) + ? new Position(DEFAULT_MENU_POSITION_X_PERCENT, DEFAULT_MENU_POSITION_Y_PERCENT) + : Position.fromString(absolutePositionString); + } + void registerContentObservers() { mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS), diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java index 576f23ee780e..d6f8885f6230 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -21,7 +21,11 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Configuration; +import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.drawable.GradientDrawable; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.widget.FrameLayout; import androidx.lifecycle.Observer; @@ -31,18 +35,25 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.accessibility.dialog.AccessibilityTarget; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** * The container view displays the accessibility features. */ @SuppressLint("ViewConstructor") -class MenuView extends FrameLayout { +class MenuView extends FrameLayout implements + ViewTreeObserver.OnComputeInternalInsetsListener { private static final int INDEX_MENU_ITEM = 0; private final List<AccessibilityTarget> mTargetFeatures = new ArrayList<>(); private final AccessibilityTargetAdapter mAdapter; private final MenuViewModel mMenuViewModel; + private final MenuAnimationController mMenuAnimationController; + private final Rect mBoundsInParent = new Rect(); private final RecyclerView mTargetFeaturesView; + private final ViewTreeObserver.OnDrawListener mSystemGestureExcludeUpdater = + this::updateSystemGestureExcludeRects; + private final Observer<Position> mPercentagePositionObserver = this::onPercentagePosition; private final Observer<Integer> mSizeTypeObserver = this::onSizeTypeChanged; private final Observer<List<AccessibilityTarget>> mTargetFeaturesObserver = this::onTargetFeaturesChanged; @@ -53,6 +64,7 @@ class MenuView extends FrameLayout { mMenuViewModel = menuViewModel; mMenuViewAppearance = menuViewAppearance; + mMenuAnimationController = new MenuAnimationController(this); mAdapter = new AccessibilityTargetAdapter(mTargetFeatures); mTargetFeaturesView = new RecyclerView(context); mTargetFeaturesView.setAdapter(mAdapter); @@ -60,16 +72,25 @@ class MenuView extends FrameLayout { setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); // Avoid drawing out of bounds of the parent view setClipToOutline(true); + loadLayoutResources(); addView(mTargetFeaturesView); } @Override + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { + inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + inoutInfo.touchableRegion.set(mBoundsInParent); + } + + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); loadLayoutResources(); + + mTargetFeaturesView.setOverScrollMode(mMenuViewAppearance.getMenuScrollMode()); } @SuppressLint("NotifyDataSetChanged") @@ -80,6 +101,10 @@ class MenuView extends FrameLayout { } private void onSizeChanged() { + mBoundsInParent.set(mBoundsInParent.left, mBoundsInParent.top, + mBoundsInParent.left + mMenuViewAppearance.getMenuWidth(), + mBoundsInParent.top + mMenuViewAppearance.getMenuHeight()); + final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams(); layoutParams.height = mMenuViewAppearance.getMenuHeight(); setLayoutParams(layoutParams); @@ -96,6 +121,18 @@ class MenuView extends FrameLayout { mMenuViewAppearance.getMenuStrokeColor()); } + private void onPercentagePosition(Position percentagePosition) { + mMenuViewAppearance.setPercentagePosition(percentagePosition); + + onPositionChanged(); + } + + private void onPositionChanged() { + final PointF position = mMenuViewAppearance.getMenuPosition(); + mMenuAnimationController.moveToPosition(position); + onBoundsInParentChanged((int) position.x, (int) position.y); + } + @SuppressLint("NotifyDataSetChanged") private void onSizeTypeChanged(int newSizeType) { mMenuViewAppearance.setSizeType(newSizeType); @@ -106,6 +143,7 @@ class MenuView extends FrameLayout { onSizeChanged(); onEdgeChanged(); + onPositionChanged(); } private void onTargetFeaturesChanged(List<AccessibilityTarget> newTargetFeatures) { @@ -113,24 +151,37 @@ class MenuView extends FrameLayout { mTargetFeatures.clear(); mTargetFeatures.addAll(newTargetFeatures); mMenuViewAppearance.setTargetFeaturesSize(mTargetFeatures.size()); + mTargetFeaturesView.setOverScrollMode(mMenuViewAppearance.getMenuScrollMode()); mAdapter.notifyDataSetChanged(); onSizeChanged(); onEdgeChanged(); + onPositionChanged(); } void show() { + mMenuViewModel.getPercentagePositionData().observeForever(mPercentagePositionObserver); mMenuViewModel.getTargetFeaturesData().observeForever(mTargetFeaturesObserver); mMenuViewModel.getSizeTypeData().observeForever(mSizeTypeObserver); setVisibility(VISIBLE); mMenuViewModel.registerContentObservers(); + getViewTreeObserver().addOnComputeInternalInsetsListener(this); + getViewTreeObserver().addOnDrawListener(mSystemGestureExcludeUpdater); } void hide() { setVisibility(GONE); + mBoundsInParent.setEmpty(); + mMenuViewModel.getPercentagePositionData().removeObserver(mPercentagePositionObserver); mMenuViewModel.getTargetFeaturesData().removeObserver(mTargetFeaturesObserver); mMenuViewModel.getSizeTypeData().removeObserver(mSizeTypeObserver); mMenuViewModel.unregisterContentObservers(); + getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater); + } + + void onBoundsInParentChanged(int newLeft, int newTop) { + mBoundsInParent.offsetTo(newLeft, newTop); } void loadLayoutResources() { @@ -141,6 +192,7 @@ class MenuView extends FrameLayout { onItemSizeChanged(); onSizeChanged(); onEdgeChanged(); + onPositionChanged(); } private InstantInsetLayerDrawable getContainerViewInsetLayer() { @@ -150,4 +202,9 @@ class MenuView extends FrameLayout { private GradientDrawable getContainerViewGradient() { return (GradientDrawable) getContainerViewInsetLayer().getDrawable(INDEX_MENU_ITEM); } + + private void updateSystemGestureExcludeRects() { + final ViewGroup parentView = (ViewGroup) getParent(); + parentView.setSystemGestureExclusionRects(Collections.singletonList(mBoundsInParent)); + } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java index b9b7732605c0..bbf967310ca3 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java @@ -16,12 +16,21 @@ package com.android.systemui.accessibility.floatingmenu; +import static android.view.View.OVER_SCROLL_ALWAYS; +import static android.view.View.OVER_SCROLL_NEVER; + import static com.android.systemui.accessibility.floatingmenu.MenuViewAppearance.MenuSizeType.SMALL; import android.annotation.IntDef; import android.content.Context; import android.content.res.Resources; +import android.graphics.Insets; +import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; import androidx.annotation.DimenRes; @@ -34,9 +43,13 @@ import java.lang.annotation.RetentionPolicy; * Provides the layout resources information of the {@link MenuView}. */ class MenuViewAppearance { + private final WindowManager mWindowManager; private final Resources mRes; + private final Position mPercentagePosition = new Position(/* percentageX= */ + 0f, /* percentageY= */ 0f); private int mTargetFeaturesSize; private int mSizeType; + private int mMargin; private int mSmallPadding; private int mLargePadding; private int mSmallIconSize; @@ -62,13 +75,15 @@ class MenuViewAppearance { int LARGE = 1; } - MenuViewAppearance(Context context) { + MenuViewAppearance(Context context, WindowManager windowManager) { + mWindowManager = windowManager; mRes = context.getResources(); update(); } void update() { + mMargin = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin); mSmallPadding = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_padding); mLargePadding = @@ -81,7 +96,7 @@ class MenuViewAppearance { mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_single_radius); mSmallMultipleRadius = mRes.getDimensionPixelSize( R.dimen.accessibility_floating_menu_small_multiple_radius); - mRadii = createRadii(getMenuRadius(mTargetFeaturesSize)); + mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(mTargetFeaturesSize)); mLargeSingleRadius = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_large_single_radius); mLargeMultipleRadius = mRes.getDimensionPixelSize( @@ -98,13 +113,48 @@ class MenuViewAppearance { void setSizeType(int sizeType) { mSizeType = sizeType; - mRadii = createRadii(getMenuRadius(mTargetFeaturesSize)); + mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(mTargetFeaturesSize)); } void setTargetFeaturesSize(int targetFeaturesSize) { mTargetFeaturesSize = targetFeaturesSize; - mRadii = createRadii(getMenuRadius(targetFeaturesSize)); + mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(targetFeaturesSize)); + } + + void setPercentagePosition(Position percentagePosition) { + mPercentagePosition.update(percentagePosition); + + mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(mTargetFeaturesSize)); + } + + Rect getMenuDraggableBounds() { + final int margin = getMenuMargin(); + final Rect draggableBounds = getWindowAvailableBounds(); + + // Initializes start position for mapping the translation of the menu view. + final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics(); + final WindowInsets windowInsets = windowMetrics.getWindowInsets(); + final Insets displayCutoutInsets = windowInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.displayCutout()); + draggableBounds.offset(-displayCutoutInsets.left, -displayCutoutInsets.top); + + draggableBounds.top += margin; + draggableBounds.right -= getMenuWidth(); + draggableBounds.bottom -= Math.min( + getWindowAvailableBounds().height() - draggableBounds.top, + calculateActualMenuHeight() + margin); + return draggableBounds; + } + + PointF getMenuPosition() { + final Rect draggableBounds = getMenuDraggableBounds(); + + return new PointF( + draggableBounds.left + + draggableBounds.width() * mPercentagePosition.getPercentageX(), + draggableBounds.top + + draggableBounds.height() * mPercentagePosition.getPercentageY()); } Drawable getMenuBackground() { @@ -115,20 +165,32 @@ class MenuViewAppearance { return mElevation; } + int getMenuWidth() { + return getMenuPadding() * 2 + getMenuIconSize(); + } + int getMenuHeight() { - return calculateActualMenuHeight(); + return Math.min(getWindowAvailableBounds().height() - mMargin * 2, + calculateActualMenuHeight()); } int getMenuIconSize() { return mSizeType == SMALL ? mSmallIconSize : mLargeIconSize; } + private int getMenuMargin() { + return mMargin; + } + int getMenuPadding() { return mSizeType == SMALL ? mSmallPadding : mLargePadding; } int[] getMenuInsets() { - return new int[]{mInset, 0, 0, 0}; + final int left = isMenuOnLeftSide() ? mInset : 0; + final int right = isMenuOnLeftSide() ? 0 : mInset; + + return new int[]{left, 0, right, 0}; } int getMenuStrokeWidth() { @@ -147,6 +209,14 @@ class MenuViewAppearance { return mSizeType == SMALL ? getSmallSize(itemCount) : getLargeSize(itemCount); } + int getMenuScrollMode() { + return hasExceededMaxWindowHeight() ? OVER_SCROLL_ALWAYS : OVER_SCROLL_NEVER; + } + + private boolean hasExceededMaxWindowHeight() { + return calculateActualMenuHeight() > getWindowAvailableBounds().height(); + } + @DimenRes private int getSmallSize(int itemCount) { return itemCount > 1 ? mSmallMultipleRadius : mSmallSingleRadius; @@ -157,8 +227,29 @@ class MenuViewAppearance { return itemCount > 1 ? mLargeMultipleRadius : mLargeSingleRadius; } - private static float[] createRadii(float radius) { - return new float[]{0.0f, 0.0f, radius, radius, radius, radius, 0.0f, 0.0f}; + private static float[] createRadii(boolean isMenuOnLeftSide, float radius) { + return isMenuOnLeftSide + ? new float[]{0.0f, 0.0f, radius, radius, radius, radius, 0.0f, 0.0f} + : new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius}; + } + + private Rect getWindowAvailableBounds() { + final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics(); + final WindowInsets windowInsets = windowMetrics.getWindowInsets(); + final Insets insets = windowInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()); + + final Rect bounds = new Rect(windowMetrics.getBounds()); + bounds.left += insets.left; + bounds.right -= insets.right; + bounds.top += insets.top; + bounds.bottom -= insets.bottom; + + return bounds; + } + + private boolean isMenuOnLeftSide() { + return mPercentagePosition.getPercentageX() < 0.5f; } private int calculateActualMenuHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java index 4ea2f7799c30..262f43b5bccb 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java @@ -19,6 +19,7 @@ package com.android.systemui.accessibility.floatingmenu; import android.annotation.IntDef; import android.annotation.SuppressLint; import android.content.Context; +import android.view.WindowManager; import android.widget.FrameLayout; import androidx.annotation.NonNull; @@ -41,11 +42,12 @@ class MenuViewLayer extends FrameLayout { int MENU_VIEW = 0; } - MenuViewLayer(@NonNull Context context) { + MenuViewLayer(@NonNull Context context, WindowManager windowManager) { super(context); final MenuViewModel menuViewModel = new MenuViewModel(context); - final MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context); + final MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context, + windowManager); mMenuView = new MenuView(context, menuViewModel, menuViewAppearance); addView(mMenuView, LayerIndex.MENU_VIEW); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java index 1e15a599f796..d2093c200ca2 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java @@ -20,6 +20,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_ import android.content.Context; import android.graphics.PixelFormat; +import android.view.WindowInsets; import android.view.WindowManager; /** @@ -33,7 +34,7 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu { MenuViewLayerController(Context context, WindowManager windowManager) { mWindowManager = windowManager; - mMenuViewLayer = new MenuViewLayer(context); + mMenuViewLayer = new MenuViewLayer(context, windowManager); } @Override @@ -68,9 +69,10 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu { WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); + params.receiveInsetsIgnoringZOrder = true; params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; params.windowAnimations = android.R.style.Animation_Translucent; - + params.setFitInsetsTypes(WindowInsets.Type.navigationBars()); return params; } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java index c3ba43950b6e..f8fd4f599796 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java @@ -33,6 +33,7 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged { private final MutableLiveData<List<AccessibilityTarget>> mTargetFeaturesData = new MutableLiveData<>(); private final MutableLiveData<Integer> mSizeTypeData = new MutableLiveData<>(); + private final MutableLiveData<Position> mPercentagePositionData = new MutableLiveData<>(); private final MenuInfoRepository mInfoRepository; MenuViewModel(Context context) { @@ -49,6 +50,11 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged { mSizeTypeData.setValue(newSizeType); } + LiveData<Position> getPercentagePositionData() { + mInfoRepository.loadMenuPosition(mPercentagePositionData::setValue); + return mPercentagePositionData; + } + LiveData<Integer> getSizeTypeData() { mInfoRepository.loadMenuSizeType(mSizeTypeData::setValue); return mSizeTypeData; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/Position.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/Position.java index 7b7eda84df4c..fc21be280dd6 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/Position.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/Position.java @@ -17,6 +17,7 @@ package com.android.systemui.accessibility.floatingmenu; import android.annotation.FloatRange; +import android.annotation.NonNull; import android.text.TextUtils; /** @@ -62,6 +63,13 @@ public class Position { } /** + * Updates the position with {@code percentagePosition}. + */ + public void update(@NonNull Position percentagePosition) { + update(percentagePosition.getPercentageX(), percentagePosition.getPercentageY()); + } + + /** * Updates the position with {@code percentageX} and {@code percentageY}. * * @param percentageX the new percentage of X-axis of the screen, from 0.0 to 1.0. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 9493975ca00f..8c7e0efee7e6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -652,17 +652,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, mUdfpsController.onAodInterrupt(screenX, screenY, major, minor); } - /** - * Cancel a fingerprint scan manually. This will get rid of the white circle on the udfps - * sensor area even if the user hasn't explicitly lifted their finger yet. - */ - public void onCancelUdfps() { - if (mUdfpsController == null) { - return; - } - mUdfpsController.onCancelUdfps(); - } - private void sendResultAndCleanUp(@DismissedReason int reason, @Nullable byte[] credentialAttestation) { if (mReceiver == null) { @@ -1021,8 +1010,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } else { Log.w(TAG, "onBiometricError callback but dialog is gone"); } - - onCancelUdfps(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 0f5a99c1596d..3273d7429d49 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -788,7 +788,7 @@ public class UdfpsController implements DozeReceiver { // ACTION_UP/ACTION_CANCEL, we need to be careful about not letting the screen // accidentally remain in high brightness mode. As a mitigation, queue a call to // cancel the fingerprint scan. - mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelUdfps, + mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::cancelAodInterrupt, AOD_INTERRUPT_TIMEOUT_MILLIS); // using a hard-coded value for major and minor until it is available from the sensor onFingerDown(requestId, screenX, screenY, minor, major); @@ -815,26 +815,22 @@ public class UdfpsController implements DozeReceiver { } /** - * Cancel UDFPS affordances - ability to hide the UDFPS overlay before the user explicitly - * lifts their finger. Generally, this should be called on errors in the authentication flow. - * - * The sensor that triggers an AOD fingerprint interrupt (see onAodInterrupt) doesn't give - * ACTION_UP/ACTION_CANCEL events, so and AOD interrupt scan needs to be cancelled manually. + * The sensor that triggers {@link #onAodInterrupt} doesn't emit ACTION_UP or ACTION_CANCEL + * events, which means the fingerprint gesture created by the AOD interrupt needs to be + * cancelled manually. * This should be called when authentication either succeeds or fails. Failing to cancel the * scan will leave the display in the UDFPS mode until the user lifts their finger. On optical * sensors, this can result in illumination persisting for longer than necessary. */ - void onCancelUdfps() { + @VisibleForTesting + void cancelAodInterrupt() { if (!mIsAodInterruptActive) { return; } if (mOverlay != null && mOverlay.getOverlayView() != null) { onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView()); } - if (mCancelAodTimeoutAction != null) { - mCancelAodTimeoutAction.run(); - mCancelAodTimeoutAction = null; - } + mCancelAodTimeoutAction = null; mIsAodInterruptActive = false; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 66a521c30f47..7d0109686351 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -21,13 +21,18 @@ import android.annotation.UiThread import android.content.Context import android.graphics.PixelFormat import android.graphics.Rect -import android.hardware.biometrics.BiometricOverlayConstants +import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP +import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER +import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR import android.hardware.biometrics.BiometricOverlayConstants.ShowReason import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.IUdfpsOverlayControllerCallback +import android.os.Build import android.os.RemoteException +import android.provider.Settings import android.util.Log import android.util.RotationUtils import android.view.LayoutInflater @@ -38,6 +43,7 @@ import android.view.WindowManager import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener import androidx.annotation.LayoutRes +import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.R import com.android.systemui.animation.ActivityLaunchAnimator @@ -54,13 +60,16 @@ import com.android.systemui.util.time.SystemClock private const val TAG = "UdfpsControllerOverlay" +@VisibleForTesting +const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui" + /** * Keeps track of the overlay state and UI resources associated with a single FingerprintService * request. This state can persist across configuration changes via the [show] and [hide] * methods. */ @UiThread -class UdfpsControllerOverlay( +class UdfpsControllerOverlay @JvmOverloads constructor( private val context: Context, fingerprintManager: FingerprintManager, private val inflater: LayoutInflater, @@ -82,7 +91,8 @@ class UdfpsControllerOverlay( @ShowReason val requestReason: Int, private val controllerCallback: IUdfpsOverlayControllerCallback, private val onTouch: (View, MotionEvent, Boolean) -> Boolean, - private val activityLaunchAnimator: ActivityLaunchAnimator + private val activityLaunchAnimator: ActivityLaunchAnimator, + private val isDebuggable: Boolean = Build.IS_DEBUGGABLE ) { /** The view, when [isShowing], or null. */ var overlayView: UdfpsView? = null @@ -102,18 +112,19 @@ class UdfpsControllerOverlay( gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS flags = (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or - WindowManager.LayoutParams.FLAG_SPLIT_TOUCH) + WindowManager.LayoutParams.FLAG_SPLIT_TOUCH) privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY // Avoid announcing window title. accessibilityTitle = " " } /** A helper if the [requestReason] was due to enrollment. */ - val enrollHelper: UdfpsEnrollHelper? = if (requestReason.isEnrollmentReason()) { - UdfpsEnrollHelper(context, fingerprintManager, requestReason) - } else { - null - } + val enrollHelper: UdfpsEnrollHelper? = + if (requestReason.isEnrollmentReason() && !shouldRemoveEnrollmentUi()) { + UdfpsEnrollHelper(context, fingerprintManager, requestReason) + } else { + null + } /** If the overlay is currently showing. */ val isShowing: Boolean @@ -129,6 +140,17 @@ class UdfpsControllerOverlay( private var touchExplorationEnabled = false + private fun shouldRemoveEnrollmentUi(): Boolean { + if (isDebuggable) { + return Settings.Global.getInt( + context.contentResolver, + SETTING_REMOVE_ENROLLMENT_UI, + 0 /* def */ + ) != 0 + } + return false + } + /** Show the overlay or return false and do nothing if it is already showing. */ @SuppressLint("ClickableViewAccessibility") fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean { @@ -183,7 +205,18 @@ class UdfpsControllerOverlay( view: UdfpsView, controller: UdfpsController ): UdfpsAnimationViewController<*>? { - return when (requestReason) { + val isEnrollment = when (requestReason) { + REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true + else -> false + } + + val filteredRequestReason = if (isEnrollment && shouldRemoveEnrollmentUi()) { + REASON_AUTH_OTHER + } else { + requestReason + } + + return when (filteredRequestReason) { REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> { UdfpsEnrollViewController( @@ -198,7 +231,7 @@ class UdfpsControllerOverlay( overlayParams.scaleFactor ) } - BiometricOverlayConstants.REASON_AUTH_KEYGUARD -> { + REASON_AUTH_KEYGUARD -> { UdfpsKeyguardViewController( view.addUdfpsView(R.layout.udfps_keyguard_view), statusBarStateController, @@ -216,7 +249,7 @@ class UdfpsControllerOverlay( activityLaunchAnimator ) } - BiometricOverlayConstants.REASON_AUTH_BP -> { + REASON_AUTH_BP -> { // note: empty controller, currently shows no visual affordance UdfpsBpViewController( view.addUdfpsView(R.layout.udfps_bp_view), @@ -226,8 +259,8 @@ class UdfpsControllerOverlay( dumpManager ) } - BiometricOverlayConstants.REASON_AUTH_OTHER, - BiometricOverlayConstants.REASON_AUTH_SETTINGS -> { + REASON_AUTH_OTHER, + REASON_AUTH_SETTINGS -> { UdfpsFpmOtherViewController( view.addUdfpsView(R.layout.udfps_fpm_other_view), statusBarStateController, @@ -440,4 +473,4 @@ private fun Int.isEnrollmentReason() = private fun Int.isImportantForAccessibility() = this == REASON_ENROLL_FIND_SENSOR || this == REASON_ENROLL_ENROLLING || - this == BiometricOverlayConstants.REASON_AUTH_BP + this == REASON_AUTH_BP diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java index 3871248eccd5..858bac30880b 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java @@ -44,9 +44,6 @@ public interface FalsingCollector { void onQsDown(); /** */ - void setQsExpanded(boolean expanded); - - /** */ boolean shouldEnforceBouncer(); /** */ diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java index 28aac051c66d..0b7d6ab5acf7 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java @@ -49,10 +49,6 @@ public class FalsingCollectorFake implements FalsingCollector { } @Override - public void setQsExpanded(boolean expanded) { - } - - @Override public boolean shouldEnforceBouncer() { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java index f5f9655ef24b..da3d293d543b 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java @@ -23,6 +23,8 @@ import android.hardware.biometrics.BiometricSourceType; import android.util.Log; import android.view.MotionEvent; +import androidx.annotation.VisibleForTesting; + import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.dagger.SysUISingleton; @@ -30,6 +32,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -133,6 +136,7 @@ class FalsingCollectorImpl implements FalsingCollector { ProximitySensor proximitySensor, StatusBarStateController statusBarStateController, KeyguardStateController keyguardStateController, + ShadeExpansionStateManager shadeExpansionStateManager, BatteryController batteryController, DockManager dockManager, @Main DelayableExecutor mainExecutor, @@ -157,6 +161,8 @@ class FalsingCollectorImpl implements FalsingCollector { mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback); + shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged); + mBatteryController.addCallback(mBatteryListener); mDockManager.addListener(mDockEventListener); } @@ -193,8 +199,8 @@ class FalsingCollectorImpl implements FalsingCollector { public void onQsDown() { } - @Override - public void setQsExpanded(boolean expanded) { + @VisibleForTesting + void onQsExpansionChanged(Boolean expanded) { if (expanded) { unregisterSensors(); } else if (mSessionStarted) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index d7638d663dc9..7e31626983e7 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -23,7 +23,8 @@ import android.service.dreams.IDreamManager; import androidx.annotation.Nullable; import com.android.internal.statusbar.IStatusBarService; -import com.android.keyguard.clock.ClockModule; +import com.android.keyguard.clock.ClockInfoModule; +import com.android.keyguard.dagger.ClockRegistryModule; import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.BootCompleteCache; import com.android.systemui.BootCompleteCacheImpl; @@ -120,7 +121,8 @@ import dagger.Provides; BiometricsModule.class, BouncerViewModule.class, ClipboardOverlayModule.class, - ClockModule.class, + ClockInfoModule.class, + ClockRegistryModule.class, CoroutinesModule.class, DreamModule.class, ControlsModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt index 5fdd198c19d7..c256e447056b 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt @@ -99,7 +99,7 @@ class FaceScanningProviderFactory @Inject constructor( } fun shouldShowFaceScanningAnim(): Boolean { - return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceScanning + return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceDetectionRunning } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index 0e1bfba8aadb..f8e2566efeff 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -224,10 +224,14 @@ class DozeLogger @Inject constructor( }) } - fun logPulseDropped(from: String, state: DozeMachine.State) { + /** + * Log why a pulse was dropped and the current doze machine state. The state can be null + * if the DozeMachine is the middle of transitioning between states. + */ + fun logPulseDropped(from: String, state: DozeMachine.State?) { buffer.log(TAG, INFO, { str1 = from - str2 = state.name + str2 = state?.name }, { "Pulse dropped, cannot pulse from=$str1 state=$str2" }) diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index ef454ffbdeb1..97a2179baa32 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -536,13 +536,13 @@ public class DozeTriggers implements DozeMachine.Part { return; } - if (!mAllowPulseTriggers || mDozeHost.isPulsePending() || !canPulse()) { + if (!mAllowPulseTriggers || mDozeHost.isPulsePending() || !canPulse(dozeState)) { if (!mAllowPulseTriggers) { mDozeLog.tracePulseDropped("requestPulse - !mAllowPulseTriggers"); } else if (mDozeHost.isPulsePending()) { mDozeLog.tracePulseDropped("requestPulse - pulsePending"); - } else if (!canPulse()) { - mDozeLog.tracePulseDropped("requestPulse", dozeState); + } else if (!canPulse(dozeState)) { + mDozeLog.tracePulseDropped("requestPulse - dozeState cannot pulse", dozeState); } runIfNotNull(onPulseSuppressedListener); return; @@ -559,15 +559,16 @@ public class DozeTriggers implements DozeMachine.Part { // not in pocket, continue pulsing final boolean isPulsePending = mDozeHost.isPulsePending(); mDozeHost.setPulsePending(false); - if (!isPulsePending || mDozeHost.isPulsingBlocked() || !canPulse()) { + if (!isPulsePending || mDozeHost.isPulsingBlocked() || !canPulse(dozeState)) { if (!isPulsePending) { mDozeLog.tracePulseDropped("continuePulseRequest - pulse no longer" + " pending, pulse was cancelled before it could start" + " transitioning to pulsing state."); } else if (mDozeHost.isPulsingBlocked()) { mDozeLog.tracePulseDropped("continuePulseRequest - pulsingBlocked"); - } else if (!canPulse()) { - mDozeLog.tracePulseDropped("continuePulseRequest", mMachine.getState()); + } else if (!canPulse(dozeState)) { + mDozeLog.tracePulseDropped("continuePulseRequest" + + " - doze state cannot pulse", dozeState); } runIfNotNull(onPulseSuppressedListener); return; @@ -582,10 +583,10 @@ public class DozeTriggers implements DozeMachine.Part { .ifPresent(uiEventEnum -> mUiEventLogger.log(uiEventEnum, getKeyguardSessionId())); } - private boolean canPulse() { - return mMachine.getState() == DozeMachine.State.DOZE - || mMachine.getState() == DozeMachine.State.DOZE_AOD - || mMachine.getState() == DozeMachine.State.DOZE_AOD_DOCKED; + private boolean canPulse(DozeMachine.State dozeState) { + return dozeState == DozeMachine.State.DOZE + || dozeState == DozeMachine.State.DOZE_AOD + || dozeState == DozeMachine.State.DOZE_AOD_DOCKED; } @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt index 478f86169718..609bd76cf210 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt @@ -24,8 +24,13 @@ import com.android.systemui.R import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL +import com.android.systemui.dump.nano.SystemUIProtoDump import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager +import com.google.protobuf.nano.MessageNano +import java.io.BufferedOutputStream +import java.io.FileDescriptor +import java.io.FileOutputStream import java.io.PrintWriter import javax.inject.Inject import javax.inject.Provider @@ -100,7 +105,7 @@ class DumpHandler @Inject constructor( /** * Dump the diagnostics! Behavior can be controlled via [args]. */ - fun dump(pw: PrintWriter, args: Array<String>) { + fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { Trace.beginSection("DumpManager#dump()") val start = SystemClock.uptimeMillis() @@ -111,10 +116,12 @@ class DumpHandler @Inject constructor( return } - when (parsedArgs.dumpPriority) { - PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs) - PRIORITY_ARG_NORMAL -> dumpNormal(pw, parsedArgs) - else -> dumpParameterized(pw, parsedArgs) + when { + parsedArgs.dumpPriority == PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs) + parsedArgs.dumpPriority == PRIORITY_ARG_NORMAL && !parsedArgs.proto -> { + dumpNormal(pw, parsedArgs) + } + else -> dumpParameterized(fd, pw, parsedArgs) } pw.println() @@ -122,7 +129,7 @@ class DumpHandler @Inject constructor( Trace.endSection() } - private fun dumpParameterized(pw: PrintWriter, args: ParsedArgs) { + private fun dumpParameterized(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) { when (args.command) { "bugreport-critical" -> dumpCritical(pw, args) "bugreport-normal" -> dumpNormal(pw, args) @@ -130,7 +137,13 @@ class DumpHandler @Inject constructor( "buffers" -> dumpBuffers(pw, args) "config" -> dumpConfig(pw) "help" -> dumpHelp(pw) - else -> dumpTargets(args.nonFlagArgs, pw, args) + else -> { + if (args.proto) { + dumpProtoTargets(args.nonFlagArgs, fd, args) + } else { + dumpTargets(args.nonFlagArgs, pw, args) + } + } } } @@ -160,6 +173,26 @@ class DumpHandler @Inject constructor( } } + private fun dumpProtoTargets( + targets: List<String>, + fd: FileDescriptor, + args: ParsedArgs + ) { + val systemUIProto = SystemUIProtoDump() + if (targets.isNotEmpty()) { + for (target in targets) { + dumpManager.dumpProtoTarget(target, systemUIProto, args.rawArgs) + } + } else { + dumpManager.dumpProtoDumpables(systemUIProto, args.rawArgs) + } + val buffer = BufferedOutputStream(FileOutputStream(fd)) + buffer.use { + it.write(MessageNano.toByteArray(systemUIProto)) + it.flush() + } + } + private fun dumpTargets( targets: List<String>, pw: PrintWriter, @@ -267,6 +300,7 @@ class DumpHandler @Inject constructor( } } } + PROTO -> pArgs.proto = true "-t", "--tail" -> { pArgs.tailLength = readArgument(iterator, arg) { it.toInt() @@ -278,6 +312,9 @@ class DumpHandler @Inject constructor( "-h", "--help" -> { pArgs.command = "help" } + // This flag is passed as part of the proto dump in Bug reports, we can ignore + // it because this is our default behavior. + "-a" -> {} else -> { throw ArgParseException("Unknown flag: $arg") } @@ -314,7 +351,7 @@ class DumpHandler @Inject constructor( const val PRIORITY_ARG_CRITICAL = "CRITICAL" const val PRIORITY_ARG_HIGH = "HIGH" const val PRIORITY_ARG_NORMAL = "NORMAL" - const val PROTO = "--sysui_proto" + const val PROTO = "--proto" } } @@ -338,6 +375,7 @@ private class ParsedArgs( var tailLength: Int = 0 var command: String? = null var listOnly = false + var proto = false } class ArgParseException(message: String) : Exception(message) diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt index dbca65122fcb..ae780896a7e2 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt @@ -18,6 +18,8 @@ package com.android.systemui.dump import android.util.ArrayMap import com.android.systemui.Dumpable +import com.android.systemui.ProtoDumpable +import com.android.systemui.dump.nano.SystemUIProtoDump import com.android.systemui.plugins.log.LogBuffer import java.io.PrintWriter import javax.inject.Inject @@ -90,7 +92,7 @@ open class DumpManager @Inject constructor() { target: String, pw: PrintWriter, args: Array<String>, - tailLength: Int + tailLength: Int, ) { for (dumpable in dumpables.values) { if (dumpable.name.endsWith(target)) { @@ -107,6 +109,36 @@ open class DumpManager @Inject constructor() { } } + @Synchronized + fun dumpProtoTarget( + target: String, + protoDump: SystemUIProtoDump, + args: Array<String> + ) { + for (dumpable in dumpables.values) { + if (dumpable.dumpable is ProtoDumpable && dumpable.name.endsWith(target)) { + dumpProtoDumpable(dumpable.dumpable, protoDump, args) + return + } + } + } + + @Synchronized + fun dumpProtoDumpables( + systemUIProtoDump: SystemUIProtoDump, + args: Array<String> + ) { + for (dumpable in dumpables.values) { + if (dumpable.dumpable is ProtoDumpable) { + dumpProtoDumpable( + dumpable.dumpable, + systemUIProtoDump, + args + ) + } + } + } + /** * Dumps all registered dumpables to [pw] */ @@ -184,6 +216,14 @@ open class DumpManager @Inject constructor() { buffer.dumpable.dump(pw, tailLength) } + private fun dumpProtoDumpable( + protoDumpable: ProtoDumpable, + systemUIProtoDump: SystemUIProtoDump, + args: Array<String> + ) { + protoDumpable.dumpProto(systemUIProtoDump, args) + } + private fun canAssignToNameLocked(name: String, newDumpable: Any): Boolean { val existingDumpable = dumpables[name]?.dumpable ?: buffers[name]?.dumpable return existingDumpable == null || newDumpable == existingDumpable @@ -195,4 +235,4 @@ private data class RegisteredDumpable<T>( val dumpable: T ) -private const val TAG = "DumpManager"
\ No newline at end of file +private const val TAG = "DumpManager" diff --git a/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java b/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java index 0a41a56b5ecb..da983ab03a1d 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java +++ b/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java @@ -51,6 +51,7 @@ public class SystemUIAuxiliaryDumpService extends Service { protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { // Simulate the NORMAL priority arg being passed to us mDumpHandler.dump( + fd, pw, new String[] { DumpHandler.PRIORITY_ARG, DumpHandler.PRIORITY_ARG_NORMAL }); } diff --git a/packages/SystemUI/src/com/android/systemui/dump/sysui.proto b/packages/SystemUI/src/com/android/systemui/dump/sysui.proto new file mode 100644 index 000000000000..cd8c08aeb2dc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dump/sysui.proto @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 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 = "proto3"; + +package com.android.systemui.dump; + +import "frameworks/base/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto"; + +option java_multiple_files = true; + +message SystemUIProtoDump { + repeated com.android.systemui.qs.QsTileState tiles = 1; +} + diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 465ebfec6d45..c1ec692d5f94 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -61,10 +61,6 @@ public class Flags { public static final ResourceBooleanFlag NOTIFICATION_DRAG_TO_CONTENTS = new ResourceBooleanFlag(108, R.bool.config_notificationToContents); - // TODO(b/254512703): Tracking Bug - public static final ReleasedFlag REMOVE_UNRANKED_NOTIFICATIONS = - new ReleasedFlag(109); - // TODO(b/254512517): Tracking Bug public static final UnreleasedFlag FSI_REQUIRES_KEYGUARD = new UnreleasedFlag(110, true); @@ -112,8 +108,9 @@ public class Flags { new ResourceBooleanFlag(205, R.bool.config_enableFaceScanningAnimation); // TODO(b/254512676): Tracking Bug - public static final UnreleasedFlag LOCKSCREEN_CUSTOM_CLOCKS = new UnreleasedFlag(207); - + public static final UnreleasedFlag LOCKSCREEN_CUSTOM_CLOCKS = + new UnreleasedFlag(207, /* teamfood = */ true); + /** * Flag to enable the usage of the new bouncer data source. This is a refactor of and * eventual replacement of KeyguardBouncer.java. @@ -157,9 +154,6 @@ public class Flags { /***************************************/ // 400 - smartspace - // TODO(b/254513080): Tracking Bug - public static final ReleasedFlag SMARTSPACE_DEDUPING = - new ReleasedFlag(400); // TODO(b/254513100): Tracking Bug public static final ReleasedFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED = @@ -213,14 +207,30 @@ public class Flags { public static final ReleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE = new ReleasedFlag(603, false); - // TODO(b/254512623): Tracking Bug + /** + * @deprecated replaced by mobile and wifi specific flags. + * + * TODO(b/254512623): Tracking Bug + */ + @Deprecated public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_BACKEND = new UnreleasedFlag(604, false); - // TODO(b/254512660): Tracking Bug + /** + * @deprecated replaced by mobile and wifi specific flags. + * + * TODO(b/254512660): Tracking Bug + */ + @Deprecated public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE_FRONTEND = new UnreleasedFlag(605, false); + public static final UnreleasedFlag NEW_STATUS_BAR_MOBILE_ICONS = + new UnreleasedFlag(606, false); + + public static final UnreleasedFlag NEW_STATUS_BAR_WIFI_ICON = + new UnreleasedFlag(607, false); + /***************************************/ // 700 - dialer/calls // TODO(b/254512734): Tracking Bug @@ -284,7 +294,7 @@ public class Flags { public static final ReleasedFlag ROUNDED_BOX_RIPPLE = new ReleasedFlag(1002); // TODO(b/254512525): Tracking Bug - public static final UnreleasedFlag REFACTORED_DOCK_SETUP = new UnreleasedFlag(1003, true); + public static final ReleasedFlag REFACTORED_DOCK_SETUP = new ReleasedFlag(1003); // 1100 - windowing @Keep diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 84bd8cec51c8..0d74dc850dda 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -401,6 +401,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private final float mWindowCornerRadius; /** + * The duration in milliseconds of the dream open animation. + */ + private final int mDreamOpenAnimationDuration; + + /** * The animation used for hiding keyguard. This is used to fetch the animation timings if * WindowManager is not providing us with them. */ @@ -751,6 +756,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, if (DEBUG) Log.d(TAG, "keyguardGone"); mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false); mKeyguardDisplayManager.hide(); + mUpdateMonitor.startBiometricWatchdog(); Trace.endSection(); } @@ -946,8 +952,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } mOccludeByDreamAnimator = ValueAnimator.ofFloat(0f, 1f); - // Use the same duration as for the UNOCCLUDE. - mOccludeByDreamAnimator.setDuration(UNOCCLUDE_ANIMATION_DURATION); + mOccludeByDreamAnimator.setDuration(mDreamOpenAnimationDuration); mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR); mOccludeByDreamAnimator.addUpdateListener( animation -> { @@ -1179,6 +1184,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mPowerButtonY = context.getResources().getDimensionPixelSize( R.dimen.physical_power_button_center_screen_location_y); mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context); + + mDreamOpenAnimationDuration = context.getResources().getInteger( + com.android.internal.R.integer.config_dreamOpenAnimationDuration); } public void userActivity() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt index 7d4db37c6b0f..2af9318d92ec 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt @@ -273,8 +273,8 @@ constructor( /** Tell the bouncer to start the pre hide animation. */ fun startDisappearAnimation(runnable: Runnable) { val finishRunnable = Runnable { - repository.setStartDisappearAnimation(null) runnable.run() + repository.setStartDisappearAnimation(null) } repository.setStartDisappearAnimation(finishRunnable) } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt index fe2eed9c6079..edf759ddfd22 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt @@ -159,7 +159,8 @@ constructor( null } } - } + }, + vibrationEffect = chipStateSender.transferStatus.vibrationEffect, ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt index f15720df5245..b96380976dec 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt @@ -16,16 +16,36 @@ package com.android.systemui.media.taptotransfer.sender -/** Represents the different possible transfer states that we could be in. */ -enum class TransferStatus { +import android.os.VibrationEffect + +/** + * Represents the different possible transfer states that we could be in and the vibration effects + * that come with updating transfer states. + * + * @property vibrationEffect an optional vibration effect when the transfer status is changed. + */ +enum class TransferStatus( + val vibrationEffect: VibrationEffect? = null, +) { /** The transfer hasn't started yet. */ - NOT_STARTED, + NOT_STARTED( + vibrationEffect = + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1.0f, 0) + .compose() + ), /** The transfer is currently ongoing but hasn't completed yet. */ - IN_PROGRESS, + IN_PROGRESS( + vibrationEffect = + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 1.0f, 0) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.7f, 70) + .compose(), + ), /** The transfer has completed successfully. */ SUCCEEDED, /** The transfer has completed with a failure. */ - FAILED, + FAILED(vibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)), /** The device is too far away to do a transfer. */ TOO_FAR, } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index ac46c85c10a4..f37d66877069 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -34,10 +34,12 @@ import com.android.internal.logging.InstanceId; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.UiEventLogger; import com.android.systemui.Dumpable; +import com.android.systemui.ProtoDumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.dump.nano.SystemUIProtoDump; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; @@ -48,6 +50,7 @@ import com.android.systemui.qs.external.TileLifecycleManager; import com.android.systemui.qs.external.TileServiceKey; import com.android.systemui.qs.external.TileServiceRequestController; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.nano.QsTileState; import com.android.systemui.settings.UserFileManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.plugins.PluginManager; @@ -59,16 +62,20 @@ import com.android.systemui.tuner.TunerService.Tunable; import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.util.settings.SecureSettings; +import org.jetbrains.annotations.NotNull; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Predicate; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Provider; @@ -82,7 +89,7 @@ import javax.inject.Provider; * This class also provides the interface for adding/removing/changing tiles. */ @SysUISingleton -public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, Dumpable { +public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable { private static final String TAG = "QSTileHost"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int MAX_QS_INSTANCE_ID = 1 << 20; @@ -671,4 +678,15 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mTiles.values().stream().filter(obj -> obj instanceof Dumpable) .forEach(o -> ((Dumpable) o).dump(pw, args)); } + + @Override + public void dumpProto(@NotNull SystemUIProtoDump systemUIProtoDump, @NotNull String[] args) { + List<QsTileState> data = mTiles.values().stream() + .map(QSTile::getState) + .map(TileStateToProtoKt::toProto) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + systemUIProtoDump.tiles = data.toArray(new QsTileState[0]); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt new file mode 100644 index 000000000000..2c8a5a4981d0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 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.qs + +import android.service.quicksettings.Tile +import android.text.TextUtils +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.external.CustomTile +import com.android.systemui.qs.nano.QsTileState +import com.android.systemui.util.nano.ComponentNameProto + +fun QSTile.State.toProto(): QsTileState? { + if (TextUtils.isEmpty(spec)) return null + val state = QsTileState() + if (spec.startsWith(CustomTile.PREFIX)) { + val protoComponentName = ComponentNameProto() + val tileComponentName = CustomTile.getComponentFromSpec(spec) + protoComponentName.packageName = tileComponentName.packageName + protoComponentName.className = tileComponentName.className + state.componentName = protoComponentName + } else { + state.spec = spec + } + state.state = + when (this.state) { + Tile.STATE_UNAVAILABLE -> QsTileState.UNAVAILABLE + Tile.STATE_INACTIVE -> QsTileState.INACTIVE + Tile.STATE_ACTIVE -> QsTileState.ACTIVE + else -> QsTileState.UNAVAILABLE + } + label?.let { state.label = it.toString() } + secondaryLabel?.let { state.secondaryLabel = it.toString() } + if (this is QSTile.BooleanState) { + state.booleanState = value + } + return state +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto b/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto new file mode 100644 index 000000000000..2a61033cb302 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 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 = "proto3"; + +package com.android.systemui.qs; + +import "frameworks/base/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto"; + +option java_multiple_files = true; + +message QsTileState { + oneof identifier { + string spec = 1; + com.android.systemui.util.ComponentNameProto component_name = 2; + } + + enum State { + UNAVAILABLE = 0; + INACTIVE = 1; + ACTIVE = 2; + } + + State state = 3; + oneof optional_boolean_state { + bool boolean_state = 4; + } + oneof optional_label { + string label = 5; + } + oneof optional_secondary_label { + string secondary_label = 6; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index fc93751b7f91..b506e9109872 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -151,6 +151,8 @@ import com.android.systemui.media.KeyguardMediaController; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.model.SysUiState; +import com.android.systemui.navigationbar.NavigationBarController; +import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.FalsingManager.FalsingTapListener; @@ -583,6 +585,7 @@ public final class NotificationPanelViewController { private final SysUiState mSysUiState; private final NotificationShadeDepthController mDepthController; + private final NavigationBarController mNavigationBarController; private final int mDisplayId; private KeyguardIndicationController mKeyguardIndicationController; @@ -861,6 +864,7 @@ public final class NotificationPanelViewController { PrivacyDotViewController privacyDotViewController, TapAgainViewController tapAgainViewController, NavigationModeController navigationModeController, + NavigationBarController navigationBarController, FragmentService fragmentService, ContentResolver contentResolver, RecordingController recordingController, @@ -954,6 +958,7 @@ public final class NotificationPanelViewController { mNotificationsQSContainerController = notificationsQSContainerController; mNotificationListContainer = notificationListContainer; mNotificationStackSizeCalculator = notificationStackSizeCalculator; + mNavigationBarController = navigationBarController; mKeyguardBottomAreaViewControllerProvider = keyguardBottomAreaViewControllerProvider; mNotificationsQSContainerController.init(); mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; @@ -1443,6 +1448,16 @@ public final class NotificationPanelViewController { mMaxAllowedKeyguardNotifications = maxAllowed; } + @VisibleForTesting + boolean getClosing() { + return mClosing; + } + + @VisibleForTesting + boolean getIsFlinging() { + return mIsFlinging; + } + private void updateMaxDisplayedNotifications(boolean recompute) { if (recompute) { setMaxDisplayedNotifications(Math.max(computeMaxKeyguardNotifications(), 1)); @@ -2671,12 +2686,16 @@ public final class NotificationPanelViewController { mQsExpanded = expanded; updateQsState(); updateExpandedHeightToMaxHeight(); - mFalsingCollector.setQsExpanded(expanded); - mCentralSurfaces.setQsExpanded(expanded); - mNotificationsQSContainerController.setQsExpanded(expanded); - mPulseExpansionHandler.setQsExpanded(expanded); - mKeyguardBypassController.setQSExpanded(expanded); - mPrivacyDotViewController.setQsExpanded(expanded); + setStatusAccessibilityImportance(expanded + ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); + updateSystemUiStateFlags(); + NavigationBarView navigationBarView = + mNavigationBarController.getNavigationBarView(mDisplayId); + if (navigationBarView != null) { + navigationBarView.onStatusBarPanelStateChanged(); + } + mShadeExpansionStateManager.onQsExpansionChanged(expanded); } } @@ -3718,6 +3737,11 @@ public final class NotificationPanelViewController { setListening(true); } + @VisibleForTesting + void setTouchSlopExceeded(boolean isTouchSlopExceeded) { + mTouchSlopExceeded = isTouchSlopExceeded; + } + public void setOverExpansion(float overExpansion) { if (overExpansion == mOverExpansion) { return; @@ -4776,6 +4800,7 @@ public final class NotificationPanelViewController { mAmbientState.setSwipingUp(false); if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop || Math.abs(y - mInitialExpandY) > mTouchSlop + || (!isFullyExpanded() && !isFullyCollapsed()) || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { mVelocityTracker.computeCurrentVelocity(1000); float vel = mVelocityTracker.getYVelocity(); @@ -5173,7 +5198,8 @@ public final class NotificationPanelViewController { */ public void updatePanelExpansionAndVisibility() { mShadeExpansionStateManager.onPanelExpansionChanged( - mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx); + mExpandedFraction, isExpanded(), + mTracking, mExpansionDragDownAmountPx); updateVisibility(); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 1d9210592b78..66a22f4ddc0d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -135,7 +135,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW DumpManager dumpManager, KeyguardStateController keyguardStateController, ScreenOffAnimationController screenOffAnimationController, - AuthController authController) { + AuthController authController, + ShadeExpansionStateManager shadeExpansionStateManager) { mContext = context; mWindowManager = windowManager; mActivityManager = activityManager; @@ -156,6 +157,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW .addCallback(mStateListener, SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER); configurationController.addCallback(this); + shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged); float desiredPreferredRefreshRate = context.getResources() .getInteger(R.integer.config_keyguardRefreshRate); @@ -607,8 +609,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW apply(mCurrentState); } - @Override - public void setQsExpanded(boolean expanded) { + private void onQsExpansionChanged(Boolean expanded) { mCurrentState.mQsExpanded = expanded; apply(mCurrentState); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index d6f0de83ecc1..73c6d507f035 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -36,17 +36,12 @@ class NotificationsQSContainerController @Inject constructor( private val navigationModeController: NavigationModeController, private val overviewProxyService: OverviewProxyService, private val largeScreenShadeHeaderController: LargeScreenShadeHeaderController, + private val shadeExpansionStateManager: ShadeExpansionStateManager, private val featureFlags: FeatureFlags, @Main private val delayableExecutor: DelayableExecutor ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController { - var qsExpanded = false - set(value) { - if (field != value) { - field = value - mView.invalidate() - } - } + private var qsExpanded = false private var splitShadeEnabled = false private var isQSDetailShowing = false private var isQSCustomizing = false @@ -71,6 +66,13 @@ class NotificationsQSContainerController @Inject constructor( taskbarVisible = visible } } + private val shadeQsExpansionListener: ShadeQsExpansionListener = + ShadeQsExpansionListener { isQsExpanded -> + if (qsExpanded != isQsExpanded) { + qsExpanded = isQsExpanded + mView.invalidate() + } + } // With certain configuration changes (like light/dark changes), the nav bar will disappear // for a bit, causing `bottomStableInsets` to be unstable for some time. Debounce the value @@ -106,6 +108,7 @@ class NotificationsQSContainerController @Inject constructor( public override fun onViewAttached() { updateResources() overviewProxyService.addCallback(taskbarVisibilityListener) + shadeExpansionStateManager.addQsExpansionListener(shadeQsExpansionListener) mView.setInsetsChangedListener(delayedInsetSetter) mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) } mView.setConfigurationChangedListener { updateResources() } @@ -113,6 +116,7 @@ class NotificationsQSContainerController @Inject constructor( override fun onViewDetached() { overviewProxyService.removeCallback(taskbarVisibilityListener) + shadeExpansionStateManager.removeQsExpansionListener(shadeQsExpansionListener) mView.removeOnInsetsChangedListener() mView.removeQSFragmentAttachedListener() mView.setConfigurationChangedListener(null) diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt index f617d471351e..7bba74a8b125 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt @@ -21,6 +21,7 @@ import android.util.Log import androidx.annotation.FloatRange import com.android.systemui.dagger.SysUISingleton import com.android.systemui.util.Compile +import java.util.concurrent.CopyOnWriteArrayList import javax.inject.Inject /** @@ -31,12 +32,14 @@ import javax.inject.Inject @SysUISingleton class ShadeExpansionStateManager @Inject constructor() { - private val expansionListeners = mutableListOf<ShadeExpansionListener>() - private val stateListeners = mutableListOf<ShadeStateListener>() + private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>() + private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>() + private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>() @PanelState private var state: Int = STATE_CLOSED @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f private var expanded: Boolean = false + private var qsExpanded: Boolean = false private var tracking: Boolean = false private var dragDownPxAmount: Float = 0f @@ -57,6 +60,15 @@ class ShadeExpansionStateManager @Inject constructor() { expansionListeners.remove(listener) } + fun addQsExpansionListener(listener: ShadeQsExpansionListener) { + qsExpansionListeners.add(listener) + listener.onQsExpansionChanged(qsExpanded) + } + + fun removeQsExpansionListener(listener: ShadeQsExpansionListener) { + qsExpansionListeners.remove(listener) + } + /** Adds a listener that will be notified when the panel state has changed. */ fun addStateListener(listener: ShadeStateListener) { stateListeners.add(listener) @@ -126,6 +138,14 @@ class ShadeExpansionStateManager @Inject constructor() { expansionListeners.forEach { it.onPanelExpansionChanged(expansionChangeEvent) } } + /** Called when the quick settings expansion changes to fully expanded or collapsed. */ + fun onQsExpansionChanged(qsExpanded: Boolean) { + this.qsExpanded = qsExpanded + + debugLog("qsExpanded=$qsExpanded") + qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) } + } + /** Updates the panel state if necessary. */ fun updateState(@PanelState state: Int) { debugLog( diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionListener.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionListener.kt new file mode 100644 index 000000000000..14882b9afd2f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionListener.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 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.shade + +/** A listener interface to be notified of expansion events for the quick settings panel. */ +fun interface ShadeQsExpansionListener { + /** + * Invoked whenever the quick settings expansion changes, when it is fully collapsed or expanded + */ + fun onQsExpansionChanged(isQsExpanded: Boolean) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 9d4a27c64e5b..4ae0f6a4a6c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -67,12 +67,15 @@ import com.android.internal.statusbar.LetterboxDetails; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.GcUtils; import com.android.internal.view.AppearanceRegion; +import com.android.systemui.dump.DumpHandler; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.tracing.ProtoTracer; +import java.io.FileDescriptor; import java.io.FileOutputStream; +import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; @@ -182,6 +185,7 @@ public class CommandQueue extends IStatusBar.Stub implements private int mLastUpdatedImeDisplayId = INVALID_DISPLAY; private ProtoTracer mProtoTracer; private final @Nullable CommandRegistry mRegistry; + private final @Nullable DumpHandler mDumpHandler; /** * These methods are called back on the main thread. @@ -471,12 +475,18 @@ public class CommandQueue extends IStatusBar.Stub implements } public CommandQueue(Context context) { - this(context, null, null); + this(context, null, null, null); } - public CommandQueue(Context context, ProtoTracer protoTracer, CommandRegistry registry) { + public CommandQueue( + Context context, + ProtoTracer protoTracer, + CommandRegistry registry, + DumpHandler dumpHandler + ) { mProtoTracer = protoTracer; mRegistry = registry; + mDumpHandler = dumpHandler; context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler); // We always have default display. setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE); @@ -1175,6 +1185,35 @@ public class CommandQueue extends IStatusBar.Stub implements } @Override + public void dumpProto(String[] args, ParcelFileDescriptor pfd) { + final FileDescriptor fd = pfd.getFileDescriptor(); + // This is mimicking Binder#dumpAsync, but on this side of the binder. Might be possible + // to just throw this work onto the handler just like the other messages + Thread thr = new Thread("Sysui.dumpProto") { + public void run() { + try { + if (mDumpHandler == null) { + return; + } + // We won't be using the PrintWriter. + OutputStream o = new OutputStream() { + @Override + public void write(int b) {} + }; + mDumpHandler.dump(fd, new PrintWriter(o), args); + } finally { + try { + // Close the file descriptor so the TransferPipe finishes its thread + pfd.close(); + } catch (Exception e) { + } + } + } + }; + thr.start(); + } + + @Override public void runGcForTest() { // Gc sysui GcUtils.runGcAndFinalizersSync(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java index 0c9e1ec1ff77..e21acb7e0f68 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java @@ -92,9 +92,6 @@ public interface NotificationShadeWindowController extends RemoteInputController /** Sets the state of whether the keyguard is fading away or not. */ default void setKeyguardFadingAway(boolean keyguardFadingAway) {} - /** Sets the state of whether the quick settings is expanded or not. */ - default void setQsExpanded(boolean expanded) {} - /** Sets the state of whether the user activities are forced or not. */ default void setForceUserActivity(boolean forceUserActivity) {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt index 8222c9d9ba59..c630feba1dcb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt @@ -39,6 +39,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView @@ -68,6 +69,7 @@ constructor( configurationController: ConfigurationController, private val statusBarStateController: StatusBarStateController, private val falsingManager: FalsingManager, + shadeExpansionStateManager: ShadeExpansionStateManager, private val lockscreenShadeTransitionController: LockscreenShadeTransitionController, private val falsingCollector: FalsingCollector, dumpManager: DumpManager @@ -126,6 +128,13 @@ constructor( initResources(context) } }) + + shadeExpansionStateManager.addQsExpansionListener { isQsExpanded -> + if (qsExpanded != isQsExpanded) { + qsExpanded = isQsExpanded + } + } + mPowerManager = context.getSystemService(PowerManager::class.java) dumpManager.registerDumpable(this) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 11e3d1773c4c..f574be056109 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -29,6 +29,7 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dump.DumpHandler; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.MediaDataManager; import com.android.systemui.plugins.ActivityStarter; @@ -181,8 +182,10 @@ public interface CentralSurfacesDependenciesModule { static CommandQueue provideCommandQueue( Context context, ProtoTracer protoTracer, - CommandRegistry registry) { - return new CommandQueue(context, protoTracer, registry); + CommandRegistry registry, + DumpHandler dumpHandler + ) { + return new CommandQueue(context, protoTracer, registry, dumpHandler); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt index d88f07ca304c..737b4812d4fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt @@ -25,11 +25,12 @@ import android.view.Gravity import android.view.View import android.widget.FrameLayout import com.android.internal.annotations.GuardedBy -import com.android.systemui.animation.Interpolators import com.android.systemui.R +import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.StatusBarState.SHADE import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener @@ -42,7 +43,6 @@ import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN import com.android.systemui.util.leak.RotationUtils.Rotation - import java.util.concurrent.Executor import javax.inject.Inject @@ -67,7 +67,8 @@ class PrivacyDotViewController @Inject constructor( private val stateController: StatusBarStateController, private val configurationController: ConfigurationController, private val contentInsetsProvider: StatusBarContentInsetsProvider, - private val animationScheduler: SystemStatusAnimationScheduler + private val animationScheduler: SystemStatusAnimationScheduler, + shadeExpansionStateManager: ShadeExpansionStateManager ) { private lateinit var tl: View private lateinit var tr: View @@ -128,6 +129,13 @@ class PrivacyDotViewController @Inject constructor( updateStatusBarState() } }) + + shadeExpansionStateManager.addQsExpansionListener { isQsExpanded -> + dlog("setQsExpanded $isQsExpanded") + synchronized(lock) { + nextViewState = nextViewState.copy(qsExpanded = isQsExpanded) + } + } } fun setUiExecutor(e: DelayableExecutor) { @@ -138,13 +146,6 @@ class PrivacyDotViewController @Inject constructor( showingListener = l } - fun setQsExpanded(expanded: Boolean) { - dlog("setQsExpanded $expanded") - synchronized(lock) { - nextViewState = nextViewState.copy(qsExpanded = expanded) - } - } - @UiThread fun setNewRotation(rot: Int) { dlog("updateRotation: $rot") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt index 36b8333688ae..2734511de78c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt @@ -28,12 +28,7 @@ class NotifPipelineFlags @Inject constructor( fun isDevLoggingEnabled(): Boolean = featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING) - fun isSmartspaceDedupingEnabled(): Boolean = - featureFlags.isEnabled(Flags.SMARTSPACE) && - featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING) - - fun removeUnrankedNotifs(): Boolean = - featureFlags.isEnabled(Flags.REMOVE_UNRANKED_NOTIFICATIONS) + fun isSmartspaceDedupingEnabled(): Boolean = featureFlags.isEnabled(Flags.SMARTSPACE) fun fullScreenIntentRequiresKeyguard(): Boolean = featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 7242506f1015..d97b712df030 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -18,8 +18,10 @@ package com.android.systemui.statusbar.notification import android.animation.ObjectAnimator import android.util.FloatProperty +import com.android.systemui.Dumpable import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionChangeEvent import com.android.systemui.shade.ShadeExpansionListener @@ -32,17 +34,20 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener +import java.io.PrintWriter import javax.inject.Inject import kotlin.math.min @SysUISingleton class NotificationWakeUpCoordinator @Inject constructor( + dumpManager: DumpManager, private val mHeadsUpManager: HeadsUpManager, private val statusBarStateController: StatusBarStateController, private val bypassController: KeyguardBypassController, private val dozeParameters: DozeParameters, private val screenOffAnimationController: ScreenOffAnimationController -) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener { +) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener, + Dumpable { private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>( "notificationVisibility") { @@ -60,6 +65,7 @@ class NotificationWakeUpCoordinator @Inject constructor( private var mLinearDozeAmount: Float = 0.0f private var mDozeAmount: Float = 0.0f + private var mDozeAmountSource: String = "init" private var mNotificationVisibleAmount = 0.0f private var mNotificationsVisible = false private var mNotificationsVisibleForExpansion = false @@ -142,6 +148,7 @@ class NotificationWakeUpCoordinator @Inject constructor( } init { + dumpManager.registerDumpable(this) mHeadsUpManager.addListener(this) statusBarStateController.addCallback(this) addListener(object : WakeUpListener { @@ -248,13 +255,14 @@ class NotificationWakeUpCoordinator @Inject constructor( // Let's notify the scroller that an animation started notifyAnimationStart(mLinearDozeAmount == 1.0f) } - setDozeAmount(linear, eased) + setDozeAmount(linear, eased, source = "StatusBar") } - fun setDozeAmount(linear: Float, eased: Float) { + fun setDozeAmount(linear: Float, eased: Float, source: String) { val changed = linear != mLinearDozeAmount mLinearDozeAmount = linear mDozeAmount = eased + mDozeAmountSource = source mStackScrollerController.setDozeAmount(mDozeAmount) updateHideAmount() if (changed && linear == 0.0f) { @@ -271,7 +279,7 @@ class NotificationWakeUpCoordinator @Inject constructor( // undefined state, so it's an indication that we should do state cleanup. We override // the doze amount to 0f (not dozing) so that the notifications are no longer hidden. // See: UnlockedScreenOffAnimationController.onFinishedWakingUp() - setDozeAmount(0f, 0f) + setDozeAmount(0f, 0f, source = "Override: Shade->Shade (lock cancelled by unlock)") } if (overrideDozeAmountIfAnimatingScreenOff(mLinearDozeAmount)) { @@ -311,12 +319,11 @@ class NotificationWakeUpCoordinator @Inject constructor( */ private fun overrideDozeAmountIfBypass(): Boolean { if (bypassController.bypassEnabled) { - var amount = 1.0f - if (statusBarStateController.state == StatusBarState.SHADE || - statusBarStateController.state == StatusBarState.SHADE_LOCKED) { - amount = 0.0f + if (statusBarStateController.state == StatusBarState.KEYGUARD) { + setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)") + } else { + setDozeAmount(0f, 0f, source = "Override: bypass (shade)") } - setDozeAmount(amount, amount) return true } return false @@ -332,7 +339,7 @@ class NotificationWakeUpCoordinator @Inject constructor( */ private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean { if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) { - setDozeAmount(1f, 1f) + setDozeAmount(1f, 1f, source = "Override: animating screen off") return true } @@ -414,6 +421,26 @@ class NotificationWakeUpCoordinator @Inject constructor( private fun shouldAnimateVisibility() = dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("mLinearDozeAmount: $mLinearDozeAmount") + pw.println("mDozeAmount: $mDozeAmount") + pw.println("mDozeAmountSource: $mDozeAmountSource") + pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount") + pw.println("mNotificationsVisible: $mNotificationsVisible") + pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion") + pw.println("mVisibilityAmount: $mVisibilityAmount") + pw.println("mLinearVisibilityAmount: $mLinearVisibilityAmount") + pw.println("pulseExpanding: $pulseExpanding") + pw.println("state: ${StatusBarState.toString(state)}") + pw.println("fullyAwake: $fullyAwake") + pw.println("wakingUp: $wakingUp") + pw.println("willWakeUp: $willWakeUp") + pw.println("collapsedEnoughToHide: $collapsedEnoughToHide") + pw.println("pulsing: $pulsing") + pw.println("notificationsFullyHidden: $notificationsFullyHidden") + pw.println("canShowPulsingHuns: $canShowPulsingHuns") + } + interface WakeUpListener { /** * Called whenever the notifications are fully hidden or shown diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 2887f975d46c..df35c9e6832a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -602,7 +602,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { mInconsistencyTracker.logNewMissingNotifications(rankingMap); mInconsistencyTracker.logNewInconsistentRankings(currentEntriesWithoutRankings, rankingMap); - if (currentEntriesWithoutRankings != null && mNotifPipelineFlags.removeUnrankedNotifs()) { + if (currentEntriesWithoutRankings != null) { for (NotificationEntry entry : currentEntriesWithoutRankings.values()) { entry.mCancellationReason = REASON_UNKNOWN; tryRemoveNotification(entry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 6e76691ae1b1..d2db6224ef52 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -407,7 +407,10 @@ public class PreparationCoordinator implements Coordinator { mLogger.logGroupInflationTookTooLong(group); return false; } - if (mInflatingNotifs.contains(group.getSummary())) { + // Only delay release if the summary is not inflated. + // TODO(253454977): Once we ensure that all other pipeline filtering and pruning has been + // done by this point, we can revert back to checking for mInflatingNotifs.contains(...) + if (group.getSummary() != null && !isInflated(group.getSummary())) { mLogger.logDelayingGroupRelease(group, group.getSummary()); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index c5a69217a1ac..c4f5a3a30608 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.notification.interruption; import static com.android.systemui.statusbar.StatusBarState.SHADE; +import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD; +import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR; import android.app.NotificationManager; import android.content.ContentResolver; @@ -32,6 +34,8 @@ import android.service.notification.StatusBarNotification; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -68,10 +72,30 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter private final NotificationInterruptLogger mLogger; private final NotifPipelineFlags mFlags; private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; + private final UiEventLogger mUiEventLogger; @VisibleForTesting protected boolean mUseHeadsUp = false; + public enum NotificationInterruptEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "FSI suppressed for suppressive GroupAlertBehavior") + FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(1235), + + @UiEvent(doc = "FSI suppressed for requiring neither HUN nor keyguard") + FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236); + + private final int mId; + + NotificationInterruptEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } + @Inject public NotificationInterruptStateProviderImpl( ContentResolver contentResolver, @@ -85,7 +109,8 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter NotificationInterruptLogger logger, @Main Handler mainHandler, NotifPipelineFlags flags, - KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) { + KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider, + UiEventLogger uiEventLogger) { mContentResolver = contentResolver; mPowerManager = powerManager; mDreamManager = dreamManager; @@ -97,6 +122,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter mLogger = logger; mFlags = flags; mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider; + mUiEventLogger = uiEventLogger; ContentObserver headsUpObserver = new ContentObserver(mainHandler) { @Override public void onChange(boolean selfChange) { @@ -203,7 +229,9 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter // b/231322873: Detect and report an event when a notification has both an FSI and a // suppressive groupAlertBehavior, and now correctly block the FSI from firing. final int uid = entry.getSbn().getUid(); + final String packageName = entry.getSbn().getPackageName(); android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior"); + mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid, packageName); mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN"); return false; } @@ -249,7 +277,9 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter // Detect the case determined by b/231322873 to launch FSI while device is in use, // as blocked by the correct implementation, and report the event. final int uid = entry.getSbn().getUid(); + final String packageName = entry.getSbn().getPackageName(); android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "no hun or keyguard"); + mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName); mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard"); return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 3d5e37376fa9..70cf56d6d12c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -262,8 +262,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn @Override void startActivity(Intent intent, boolean dismissShade, Callback callback); - void setQsExpanded(boolean expanded); - boolean isWakeUpComingFromTouch(); boolean isFalsingThresholdNeeded(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 709a434c08ac..29642beda53d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1777,18 +1777,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public void setQsExpanded(boolean expanded) { - mNotificationShadeWindowController.setQsExpanded(expanded); - mNotificationPanelViewController.setStatusAccessibilityImportance(expanded - ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); - mNotificationPanelViewController.updateSystemUiStateFlags(); - if (getNavigationBarView() != null) { - getNavigationBarView().onStatusBarPanelStateChanged(); - } - } - - @Override public boolean isWakeUpComingFromTouch() { return mWakeUpComingFromTouch; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index b987f6815000..b965ac97cc1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -26,6 +26,7 @@ import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm @@ -95,14 +96,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr var bouncerShowing: Boolean = false var altBouncerShowing: Boolean = false var launchingAffordance: Boolean = false - var qSExpanded = false - set(value) { - val changed = field != value - field = value - if (changed && !value) { - maybePerformPendingUnlock() - } - } + var qsExpanded = false @Inject constructor( @@ -111,6 +105,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr statusBarStateController: StatusBarStateController, lockscreenUserManager: NotificationLockscreenUserManager, keyguardStateController: KeyguardStateController, + shadeExpansionStateManager: ShadeExpansionStateManager, dumpManager: DumpManager ) { this.mKeyguardStateController = keyguardStateController @@ -132,6 +127,14 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr } }) + shadeExpansionStateManager.addQsExpansionListener { isQsExpanded -> + val changed = qsExpanded != isQsExpanded + qsExpanded = isQsExpanded + if (changed && !isQsExpanded) { + maybePerformPendingUnlock() + } + } + val dismissByDefault = if (context.resources.getBoolean( com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0 tunerService.addTunable(object : TunerService.Tunable { @@ -160,7 +163,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr ): Boolean { if (biometricSourceType == BiometricSourceType.FACE && bypassEnabled) { val can = canBypass() - if (!can && (isPulseExpanding || qSExpanded)) { + if (!can && (isPulseExpanding || qsExpanded)) { pendingUnlock = PendingUnlock(biometricSourceType, isStrongBiometric) } return can @@ -189,7 +192,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr altBouncerShowing -> true statusBarStateController.state != StatusBarState.KEYGUARD -> false launchingAffordance -> false - isPulseExpanding || qSExpanded -> false + isPulseExpanding || qsExpanded -> false else -> true } } @@ -214,7 +217,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr pw.println(" altBouncerShowing: $altBouncerShowing") pw.println(" isPulseExpanding: $isPulseExpanding") pw.println(" launchingAffordance: $launchingAffordance") - pw.println(" qSExpanded: $qSExpanded") + pw.println(" qSExpanded: $qsExpanded") pw.println(" hasFaceFeature: $hasFaceFeature") } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index ece7ee0ec98a..86f6ff850409 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -372,7 +372,7 @@ public interface StatusBarIconController { mIconSize = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.status_bar_icon_size); - if (statusBarPipelineFlags.isNewPipelineFrontendEnabled()) { + if (statusBarPipelineFlags.useNewMobileIcons()) { // This starts the flow for the new pipeline, and will notify us of changes mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel(); MobileIconsBinder.bind(mGroup, mMobileIconsViewModel); @@ -451,7 +451,7 @@ public interface StatusBarIconController { @VisibleForTesting protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) { final BaseStatusBarFrameLayout view; - if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) { + if (mStatusBarPipelineFlags.useNewWifiIcon()) { view = onCreateModernStatusBarWifiView(slot); // When [ModernStatusBarWifiView] is created, it will automatically apply the // correct view state so we don't need to call applyWifiState. @@ -474,9 +474,9 @@ public interface StatusBarIconController { String slot, MobileIconState state ) { - if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) { + if (mStatusBarPipelineFlags.useNewMobileIcons()) { throw new IllegalStateException("Attempting to add a mobile icon while the new " - + "pipeline is enabled is not supported"); + + "icons are enabled is not supported"); } // Use the `subId` field as a key to query for the correct context @@ -497,7 +497,7 @@ public interface StatusBarIconController { String slot, int subId ) { - if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) { + if (!mStatusBarPipelineFlags.useNewMobileIcons()) { throw new IllegalStateException("Attempting to add a mobile icon using the new" + "pipeline, but the enabled flag is false."); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java index e106b9e327ef..31e960ad7d69 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -224,9 +224,9 @@ public class StatusBarIconControllerImpl implements Tunable, */ @Override public void setMobileIcons(String slot, List<MobileIconState> iconStates) { - if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) { + if (mStatusBarPipelineFlags.useNewMobileIcons()) { Log.d(TAG, "ignoring old pipeline callbacks, because the new " - + "pipeline frontend is enabled"); + + "icons are enabled"); return; } Slot mobileSlot = mStatusBarIconList.getSlot(slot); @@ -249,9 +249,9 @@ public class StatusBarIconControllerImpl implements Tunable, @Override public void setNewMobileIconSubIds(List<Integer> subIds) { - if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) { + if (!mStatusBarPipelineFlags.useNewMobileIcons()) { Log.d(TAG, "ignoring new pipeline callback, " - + "since the frontend is disabled"); + + "since the new icons are disabled"); return; } Slot mobileSlot = mStatusBarIconList.getSlot("mobile"); 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 30591d08075a..5480f5d7489e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -60,7 +60,6 @@ import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.data.BouncerView; -import com.android.systemui.keyguard.data.BouncerViewDelegate; import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor; import com.android.systemui.keyguard.domain.interactor.BouncerInteractor; import com.android.systemui.navigationbar.NavigationBarView; @@ -136,7 +135,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController; private final BouncerCallbackInteractor mBouncerCallbackInteractor; private final BouncerInteractor mBouncerInteractor; - private final BouncerViewDelegate mBouncerViewDelegate; + private final BouncerView mBouncerView; private final Lazy<com.android.systemui.shade.ShadeController> mShadeController; private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() { @@ -327,7 +326,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardSecurityModel = keyguardSecurityModel; mBouncerCallbackInteractor = bouncerCallbackInteractor; mBouncerInteractor = bouncerInteractor; - mBouncerViewDelegate = bouncerView.getDelegate(); + mBouncerView = bouncerView; mFoldAodAnimationController = sysUIUnfoldComponent .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null); mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER); @@ -804,7 +803,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private void setDozing(boolean dozing) { if (mDozing != dozing) { mDozing = dozing; - if (dozing || mBouncer.needsFullscreenBouncer() + if (dozing || needsFullscreenBouncer() || mKeyguardStateController.isOccluded()) { reset(dozing /* hideBouncerWhenShowing */); } @@ -1081,7 +1080,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * @return whether a back press can be handled right now. */ public boolean canHandleBackPressed() { - return mBouncer.isShowing(); + return bouncerIsShowing(); } /** @@ -1124,8 +1123,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public boolean isFullscreenBouncer() { - if (mBouncerViewDelegate != null) { - return mBouncerViewDelegate.isFullScreenBouncer(); + if (mBouncerView.getDelegate() != null) { + return mBouncerView.getDelegate().isFullScreenBouncer(); } return mBouncer != null && mBouncer.isFullscreenBouncer(); } @@ -1284,15 +1283,15 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public boolean shouldDismissOnMenuPressed() { - if (mBouncerViewDelegate != null) { - return mBouncerViewDelegate.shouldDismissOnMenuPressed(); + if (mBouncerView.getDelegate() != null) { + return mBouncerView.getDelegate().shouldDismissOnMenuPressed(); } return mBouncer != null && mBouncer.shouldDismissOnMenuPressed(); } public boolean interceptMediaKey(KeyEvent event) { - if (mBouncerViewDelegate != null) { - return mBouncerViewDelegate.interceptMediaKey(event); + if (mBouncerView.getDelegate() != null) { + return mBouncerView.getDelegate().interceptMediaKey(event); } return mBouncer != null && mBouncer.interceptMediaKey(event); } @@ -1301,8 +1300,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * @return true if the pre IME back event should be handled */ public boolean dispatchBackKeyEventPreIme() { - if (mBouncerViewDelegate != null) { - return mBouncerViewDelegate.dispatchBackKeyEventPreIme(); + if (mBouncerView.getDelegate() != null) { + return mBouncerView.getDelegate().dispatchBackKeyEventPreIme(); } return mBouncer != null && mBouncer.dispatchBackKeyEventPreIme(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt index 9b8b6434827e..06cd12dd1a0d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt @@ -24,29 +24,19 @@ import javax.inject.Inject /** All flagging methods related to the new status bar pipeline (see b/238425913). */ @SysUISingleton class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) { - /** - * Returns true if we should run the new pipeline backend. - * - * The new pipeline backend hooks up to all our external callbacks, logs those callback inputs, - * and logs the output state. - */ - fun isNewPipelineBackendEnabled(): Boolean = - featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE_BACKEND) + /** True if we should display the mobile icons using the new status bar data pipeline. */ + fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS) - /** - * Returns true if we should run the new pipeline frontend *and* backend. - * - * The new pipeline frontend will use the outputted state from the new backend and will make the - * correct changes to the UI. - */ - fun isNewPipelineFrontendEnabled(): Boolean = - isNewPipelineBackendEnabled() && - featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE_FRONTEND) + /** True if we should display the wifi icon using the new status bar data pipeline. */ + fun useNewWifiIcon(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON) + + // TODO(b/238425913): Add flags to only run the mobile backend or wifi backend so we get the + // logging without getting the UI effects. /** - * Returns true if we should apply some coloring to icons that were rendered with the new + * Returns true if we should apply some coloring to the wifi icon that was rendered with the new * pipeline to help with debugging. */ - // For now, just always apply the debug coloring if we've enabled frontend rendering. - fun useNewPipelineDebugColoring(): Boolean = isNewPipelineFrontendEnabled() + // For now, just always apply the debug coloring if we've enabled the new icon. + fun useWifiDebugColoring(): Boolean = useNewWifiIcon() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index 681cf7254ae7..93448c1dee0e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -39,7 +39,6 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel import java.util.concurrent.Executor @@ -64,6 +63,9 @@ interface WifiRepository { /** Observable for the current wifi enabled status. */ val isWifiEnabled: StateFlow<Boolean> + /** Observable for the current wifi default status. */ + val isWifiDefault: StateFlow<Boolean> + /** Observable for the current wifi network. */ val wifiNetwork: StateFlow<WifiNetworkModel> @@ -103,7 +105,7 @@ class WifiRepositoryImpl @Inject constructor( merge(wifiNetworkChangeEvents, wifiStateChangeEvents) .mapLatest { wifiManager.isWifiEnabled } .distinctUntilChanged() - .logOutputChange(logger, "enabled") + .logInputChange(logger, "enabled") .stateIn( scope = scope, started = SharingStarted.WhileSubscribed(), @@ -111,6 +113,39 @@ class WifiRepositoryImpl @Inject constructor( ) } + override val isWifiDefault: StateFlow<Boolean> = conflatedCallbackFlow { + // Note: This callback doesn't do any logging because we already log every network change + // in the [wifiNetwork] callback. + val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities + ) { + // This method will always be called immediately after the network becomes the + // default, in addition to any time the capabilities change while the network is + // the default. + // If this network contains valid wifi info, then wifi is the default network. + val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities) + trySend(wifiInfo != null) + } + + override fun onLost(network: Network) { + // The system no longer has a default network, so wifi is definitely not default. + trySend(false) + } + } + + connectivityManager.registerDefaultNetworkCallback(callback) + awaitClose { connectivityManager.unregisterNetworkCallback(callback) } + } + .distinctUntilChanged() + .logInputChange(logger, "isWifiDefault") + .stateIn( + scope, + started = SharingStarted.WhileSubscribed(), + initialValue = false + ) + override val wifiNetwork: StateFlow<WifiNetworkModel> = conflatedCallbackFlow { var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt index 04b17ed2924a..3a3e611de96a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt @@ -59,6 +59,9 @@ class WifiInteractor @Inject constructor( /** Our current enabled status. */ val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled + /** Our current default status. */ + val isDefault: Flow<Boolean> = wifiRepository.isWifiDefault + /** Our current wifi network. See [WifiNetworkModel]. */ val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt index e23f8c7e97e0..cc6a375c40f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt @@ -48,7 +48,7 @@ abstract class LocationBasedWifiViewModel( /** The color that should be used to tint the icon. */ val tint: Flow<Int> = flowOf( - if (statusBarPipelineFlags.useNewPipelineDebugColoring()) { + if (statusBarPipelineFlags.useWifiDebugColoring()) { debugTint } else { DEFAULT_TINT diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt index ebbd77b72014..160c577042a4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt @@ -124,9 +124,10 @@ constructor( private val wifiIcon: StateFlow<Icon.Resource?> = combine( interactor.isEnabled, + interactor.isDefault, interactor.isForceHidden, interactor.wifiNetwork, - ) { isEnabled, isForceHidden, wifiNetwork -> + ) { isEnabled, isDefault, isForceHidden, wifiNetwork -> if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) { return@combine null } @@ -135,6 +136,7 @@ constructor( val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription()) return@combine when { + isDefault -> icon wifiConstants.alwaysShowIconIfEnabled -> icon !connectivityConstants.hasDataCapabilities -> icon wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt index 45ce687a1a4d..1a8aafb1a5f2 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt @@ -42,6 +42,7 @@ import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.media.taptotransfer.common.MediaTttUtils import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.TemporaryViewDisplayController import com.android.systemui.util.concurrency.DelayableExecutor @@ -79,6 +80,7 @@ open class ChipbarCoordinator @Inject constructor( private val falsingManager: FalsingManager, private val falsingCollector: FalsingCollector, private val viewUtil: ViewUtil, + private val vibratorHelper: VibratorHelper, ) : TemporaryViewDisplayController<ChipbarInfo, MediaTttLogger>( context, logger, @@ -154,6 +156,11 @@ open class ChipbarCoordinator @Inject constructor( ).contentDescription = "${newInfo.startIcon.contentDescription.loadContentDescription(context)} " + "${newInfo.text.loadText(context)}" + + // ---- Haptics ---- + newInfo.vibrationEffect?.let { + vibratorHelper.vibrate(it) + } } override fun animateViewIn(view: ViewGroup) { diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt index 211a66387966..57fde87114d0 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt @@ -16,6 +16,7 @@ package com.android.systemui.temporarydisplay.chipbar +import android.os.VibrationEffect import android.view.View import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text @@ -29,11 +30,13 @@ import com.android.systemui.temporarydisplay.TemporaryViewInfo * @property text the text to display. * @property endItem an optional end item to display at the end of the chipbar (on the right in LTR * locales; on the left in RTL locales). + * @property vibrationEffect an optional vibration effect when the chipbar is displayed */ data class ChipbarInfo( val startIcon: Icon, val text: Text, val endItem: ChipbarEndItem?, + val vibrationEffect: VibrationEffect? = null, ) : TemporaryViewInfo /** The possible items to display at the end of the chipbar. */ diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt index ee785b62bd50..088cd93bdf7e 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt @@ -36,9 +36,7 @@ class UserSwitcherPopupMenu( private var adapter: ListAdapter? = null init { - setBackgroundDrawable( - res.getDrawable(R.drawable.bouncer_user_switcher_popup_bg, context.getTheme()) - ) + setBackgroundDrawable(null) setModal(false) setOverlapAnchor(true) } diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index d768b6dc195a..b16dc5403a57 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -220,7 +220,12 @@ constructor( val result = withContext(backgroundDispatcher) { manager.aliveUsers } if (result != null) { - _userInfos.value = result.sortedBy { it.creationTime } + _userInfos.value = + result + // Users should be sorted by ascending creation time. + .sortedBy { it.creationTime } + // The guest user is always last, regardless of creation time. + .sortedBy { it.isGuest } } } } diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt index 0d5c64b83e6e..dda78aad54c6 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt @@ -429,6 +429,7 @@ constructor( isGuestEphemeral = currentlySelectedUserInfo.isEphemeral, isKeyguardShowing = keyguardInteractor.isKeyguardShowing(), onExitGuestUser = this::exitGuestUser, + dialogShower = dialogShower, ) ) return @@ -443,6 +444,7 @@ constructor( isGuestEphemeral = currentlySelectedUserInfo.isEphemeral, isKeyguardShowing = keyguardInteractor.isKeyguardShowing(), onExitGuestUser = this::exitGuestUser, + dialogShower = dialogShower, ) ) return @@ -477,6 +479,7 @@ constructor( userHandle = currentUser.userHandle, isKeyguardShowing = keyguardInteractor.isKeyguardShowing(), showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral, + dialogShower = dialogShower, ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt index 08d7c5a26a25..177356e6b573 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt @@ -18,14 +18,18 @@ package com.android.systemui.user.domain.model import android.os.UserHandle +import com.android.systemui.qs.user.UserSwitchDialogController /** Encapsulates a request to show a dialog. */ -sealed class ShowDialogRequestModel { +sealed class ShowDialogRequestModel( + open val dialogShower: UserSwitchDialogController.DialogShower? = null, +) { data class ShowAddUserDialog( val userHandle: UserHandle, val isKeyguardShowing: Boolean, val showEphemeralMessage: Boolean, - ) : ShowDialogRequestModel() + override val dialogShower: UserSwitchDialogController.DialogShower?, + ) : ShowDialogRequestModel(dialogShower) data class ShowUserCreationDialog( val isGuest: Boolean, @@ -37,5 +41,6 @@ sealed class ShowDialogRequestModel { val isGuestEphemeral: Boolean, val isKeyguardShowing: Boolean, val onExitGuestUser: (guestId: Int, targetId: Int, forceRemoveGuest: Boolean) -> Unit, - ) : ShowDialogRequestModel() + override val dialogShower: UserSwitchDialogController.DialogShower?, + ) : ShowDialogRequestModel(dialogShower) } diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt index 938417f9dbe3..968af59e6c45 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt @@ -18,12 +18,15 @@ package com.android.systemui.user.ui.binder import android.content.Context +import android.view.Gravity import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.LinearLayout.SHOW_DIVIDER_MIDDLE import android.widget.TextView import androidx.constraintlayout.helper.widget.Flow as FlowWidget import androidx.core.view.isVisible @@ -36,6 +39,7 @@ import com.android.systemui.R import com.android.systemui.classifier.FalsingCollector import com.android.systemui.user.UserSwitcherPopupMenu import com.android.systemui.user.UserSwitcherRootView +import com.android.systemui.user.shared.model.UserActionModel import com.android.systemui.user.ui.viewmodel.UserActionViewModel import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel import com.android.systemui.util.children @@ -168,15 +172,10 @@ object UserSwitcherViewBinder { onDismissed: () -> Unit, ): UserSwitcherPopupMenu { return UserSwitcherPopupMenu(context).apply { + this.setDropDownGravity(Gravity.END) this.anchorView = anchorView setAdapter(adapter) setOnDismissListener { onDismissed() } - setOnItemClickListener { _, _, position, _ -> - val itemPositionExcludingHeader = position - 1 - adapter.getItem(itemPositionExcludingHeader).onClicked() - dismiss() - } - show() } } @@ -186,38 +185,67 @@ object UserSwitcherViewBinder { private val layoutInflater: LayoutInflater, ) : BaseAdapter() { - private val items = mutableListOf<UserActionViewModel>() + private var sections = listOf<List<UserActionViewModel>>() override fun getCount(): Int { - return items.size + return sections.size } - override fun getItem(position: Int): UserActionViewModel { - return items[position] + override fun getItem(position: Int): List<UserActionViewModel> { + return sections[position] } override fun getItemId(position: Int): Long { - return getItem(position).viewKey + return position.toLong() } override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val view = - convertView - ?: layoutInflater.inflate( + val section = getItem(position) + val context = parent.context + val sectionView = + convertView as? LinearLayout + ?: LinearLayout(context, null).apply { + this.orientation = LinearLayout.VERTICAL + this.background = + parent.resources.getDrawable( + R.drawable.bouncer_user_switcher_popup_bg, + context.theme + ) + this.showDividers = SHOW_DIVIDER_MIDDLE + this.dividerDrawable = + context.getDrawable( + R.drawable.fullscreen_userswitcher_menu_item_divider + ) + } + sectionView.removeAllViewsInLayout() + + for (viewModel in section) { + val view = + layoutInflater.inflate( R.layout.user_switcher_fullscreen_popup_item, - parent, - false + /* parent= */ null ) - val viewModel = getItem(position) - view.requireViewById<ImageView>(R.id.icon).setImageResource(viewModel.iconResourceId) - view.requireViewById<TextView>(R.id.text).text = - view.resources.getString(viewModel.textResourceId) - return view + view + .requireViewById<ImageView>(R.id.icon) + .setImageResource(viewModel.iconResourceId) + view.requireViewById<TextView>(R.id.text).text = + view.resources.getString(viewModel.textResourceId) + view.setOnClickListener { viewModel.onClicked() } + sectionView.addView(view) + } + return sectionView } fun setItems(items: List<UserActionViewModel>) { - this.items.clear() - this.items.addAll(items) + val primarySection = + items.filter { + it.viewKey != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong() + } + val secondarySection = + items.filter { + it.viewKey == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong() + } + this.sections = listOf(primarySection, secondarySection) notifyDataSetChanged() } } diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt index f7e19c0ca810..e9217209530b 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt @@ -19,8 +19,10 @@ package com.android.systemui.user.ui.dialog import android.app.Dialog import android.content.Context +import com.android.internal.jank.InteractionJankMonitor import com.android.settingslib.users.UserCreatingDialog import com.android.systemui.CoreStartable +import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dagger.SysUISingleton @@ -71,37 +73,58 @@ constructor( } } - currentDialog = + val (dialog, dialogCuj) = when (request) { is ShowDialogRequestModel.ShowAddUserDialog -> - AddUserDialog( - context = context.get(), - userHandle = request.userHandle, - isKeyguardShowing = request.isKeyguardShowing, - showEphemeralMessage = request.showEphemeralMessage, - falsingManager = falsingManager.get(), - broadcastSender = broadcastSender.get(), - dialogLaunchAnimator = dialogLaunchAnimator.get(), + Pair( + AddUserDialog( + context = context.get(), + userHandle = request.userHandle, + isKeyguardShowing = request.isKeyguardShowing, + showEphemeralMessage = request.showEphemeralMessage, + falsingManager = falsingManager.get(), + broadcastSender = broadcastSender.get(), + dialogLaunchAnimator = dialogLaunchAnimator.get(), + ), + DialogCuj( + InteractionJankMonitor.CUJ_USER_DIALOG_OPEN, + INTERACTION_JANK_ADD_NEW_USER_TAG, + ), ) is ShowDialogRequestModel.ShowUserCreationDialog -> - UserCreatingDialog( - context.get(), - request.isGuest, + Pair( + UserCreatingDialog( + context.get(), + request.isGuest, + ), + null, ) is ShowDialogRequestModel.ShowExitGuestDialog -> - ExitGuestDialog( - context = context.get(), - guestUserId = request.guestUserId, - isGuestEphemeral = request.isGuestEphemeral, - targetUserId = request.targetUserId, - isKeyguardShowing = request.isKeyguardShowing, - falsingManager = falsingManager.get(), - dialogLaunchAnimator = dialogLaunchAnimator.get(), - onExitGuestUserListener = request.onExitGuestUser, + Pair( + ExitGuestDialog( + context = context.get(), + guestUserId = request.guestUserId, + isGuestEphemeral = request.isGuestEphemeral, + targetUserId = request.targetUserId, + isKeyguardShowing = request.isKeyguardShowing, + falsingManager = falsingManager.get(), + dialogLaunchAnimator = dialogLaunchAnimator.get(), + onExitGuestUserListener = request.onExitGuestUser, + ), + DialogCuj( + InteractionJankMonitor.CUJ_USER_DIALOG_OPEN, + INTERACTION_JANK_EXIT_GUEST_MODE_TAG, + ), ) } + currentDialog = dialog + + if (request.dialogShower != null && dialogCuj != null) { + request.dialogShower?.showDialog(dialog, dialogCuj) + } else { + dialog.show() + } - currentDialog?.show() interactor.get().onDialogShown() } } @@ -120,4 +143,9 @@ constructor( } } } + + companion object { + private const val INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user" + private const val INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode" + } } diff --git a/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto b/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto new file mode 100644 index 000000000000..b7166d96d401 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 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 = "proto3"; + +package com.android.systemui.util; + +option java_multiple_files = true; + +message ComponentNameProto { + string package_name = 1; + string class_name = 2; +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java new file mode 100644 index 000000000000..6391a2c8eff7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 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.accessibility.floatingmenu; + +import static com.google.common.truth.Truth.assertThat; + +import android.graphics.PointF; +import android.testing.AndroidTestingRunner; +import android.view.WindowManager; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for {@link MenuAnimationController}. */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class MenuAnimationControllerTest extends SysuiTestCase { + private MenuView mMenuView; + private MenuAnimationController mMenuAnimationController; + + @Before + public void setUp() throws Exception { + final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); + final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, + stubWindowManager); + final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext); + mMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance); + mMenuAnimationController = new MenuAnimationController(mMenuView); + } + + @Test + public void moveToPosition_matchPosition() { + final PointF destination = new PointF(50, 60); + + mMenuAnimationController.moveToPosition(destination); + + assertThat(mMenuView.getTranslationX()).isEqualTo(50); + assertThat(mMenuView.getTranslationY()).isEqualTo(60); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java index f782a446c627..8c8d6aca7cd7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java @@ -16,13 +16,24 @@ package com.android.systemui.accessibility.floatingmenu; +import static android.view.WindowInsets.Type.displayCutout; +import static android.view.WindowInsets.Type.systemBars; + import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.content.Context; +import android.graphics.Insets; +import android.graphics.Rect; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; +import android.view.WindowMetrics; import androidx.test.filters.SmallTest; @@ -38,6 +49,7 @@ import org.mockito.junit.MockitoRule; /** Tests for {@link MenuViewLayerController}. */ @RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class MenuViewLayerControllerTest extends SysuiTestCase { @Rule @@ -46,10 +58,20 @@ public class MenuViewLayerControllerTest extends SysuiTestCase { @Mock private WindowManager mWindowManager; + @Mock + private WindowMetrics mWindowMetrics; + private MenuViewLayerController mMenuViewLayerController; @Before public void setUp() throws Exception { + final WindowManager wm = mContext.getSystemService(WindowManager.class); + doAnswer(invocation -> wm.getMaximumWindowMetrics()).when( + mWindowManager).getMaximumWindowMetrics(); + mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); + when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics); + when(mWindowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1080, 2340)); + when(mWindowMetrics.getWindowInsets()).thenReturn(stubDisplayInsets()); mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager); } @@ -68,4 +90,14 @@ public class MenuViewLayerControllerTest extends SysuiTestCase { verify(mWindowManager).removeView(any(View.class)); } + + private WindowInsets stubDisplayInsets() { + final int stubStatusBarHeight = 118; + final int stubNavigationBarHeight = 125; + return new WindowInsets.Builder() + .setVisible(systemBars() | displayCutout(), true) + .setInsets(systemBars() | displayCutout(), + Insets.of(0, stubStatusBarHeight, 0, stubNavigationBarHeight)) + .build(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index 8883cb783438..23c6ef1338b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -26,6 +26,7 @@ import static com.google.common.truth.Truth.assertThat; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; +import android.view.WindowManager; import androidx.test.filters.SmallTest; @@ -44,7 +45,8 @@ public class MenuViewLayerTest extends SysuiTestCase { @Before public void setUp() throws Exception { - mMenuViewLayer = new MenuViewLayer(mContext); + final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); + mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java index 513044d2c20d..6549de15dad2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify; import android.app.UiModeManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.WindowManager; import androidx.test.filters.SmallTest; @@ -52,7 +53,9 @@ public class MenuViewTest extends SysuiTestCase { mNightMode = mUiModeManager.getNightMode(); mUiModeManager.setNightMode(MODE_NIGHT_YES); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext); - final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext); + final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); + final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, + stubWindowManager); mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index cd50144bf2e8..d489656559c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -26,6 +26,7 @@ import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_ import android.hardware.biometrics.BiometricOverlayConstants.ShowReason import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.IUdfpsOverlayControllerCallback +import android.provider.Settings import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater @@ -124,14 +125,18 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { whenever(udfpsEnrollView.context).thenReturn(context) } - private fun withReason(@ShowReason reason: Int, block: () -> Unit) { + private fun withReason( + @ShowReason reason: Int, + isDebuggable: Boolean = false, + block: () -> Unit + ) { controllerOverlay = UdfpsControllerOverlay( context, fingerprintManager, inflater, windowManager, accessibilityManager, statusBarStateController, shadeExpansionStateManager, statusBarKeyguardViewManager, keyguardUpdateMonitor, dialogManager, dumpManager, transitionController, configurationController, systemClock, keyguardStateController, unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason, - controllerCallback, onTouch, activityLaunchAnimator + controllerCallback, onTouch, activityLaunchAnimator, isDebuggable ) block() } @@ -151,11 +156,29 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { } @Test + fun showUdfpsOverlay_locate_withEnrollmentUiRemoved() { + Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 1) + withReason(REASON_ENROLL_FIND_SENSOR, isDebuggable = true) { + showUdfpsOverlay(isEnrollUseCase = false) + } + Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 0) + } + + @Test fun showUdfpsOverlay_enroll() = withReason(REASON_ENROLL_ENROLLING) { showUdfpsOverlay(isEnrollUseCase = true) } @Test + fun showUdfpsOverlay_enroll_withEnrollmentUiRemoved() { + Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 1) + withReason(REASON_ENROLL_ENROLLING, isDebuggable = true) { + showUdfpsOverlay(isEnrollUseCase = false) + } + Settings.Global.putInt(mContext.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, 0) + } + + @Test fun showUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { showUdfpsOverlay() } private fun withRotation(@Rotation rotation: Int, block: () -> Unit) { @@ -373,21 +396,33 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { context.resources.getStringArray(R.array.udfps_accessibility_touch_hints) val rotation = Surface.ROTATION_0 // touch at 0 degrees - assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */, - 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) - .isEqualTo(touchHints[0]) + assertThat( + controllerOverlay.onTouchOutsideOfSensorAreaImpl( + 0.0f /* x */, 0.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation + ) + ).isEqualTo(touchHints[0]) // touch at 90 degrees - assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */, - 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) - .isEqualTo(touchHints[1]) + assertThat( + controllerOverlay.onTouchOutsideOfSensorAreaImpl( + 0.0f /* x */, -1.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation + ) + ).isEqualTo(touchHints[1]) // touch at 180 degrees - assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */, - 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) - .isEqualTo(touchHints[2]) + assertThat( + controllerOverlay.onTouchOutsideOfSensorAreaImpl( + -1.0f /* x */, 0.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation + ) + ).isEqualTo(touchHints[2]) // touch at 270 degrees - assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */, - 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) - .isEqualTo(touchHints[3]) + assertThat( + controllerOverlay.onTouchOutsideOfSensorAreaImpl( + 0.0f /* x */, 1.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation + ) + ).isEqualTo(touchHints[3]) } fun testTouchOutsideAreaNoRotation90Degrees() = withReason(REASON_ENROLL_ENROLLING) { @@ -395,21 +430,33 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { context.resources.getStringArray(R.array.udfps_accessibility_touch_hints) val rotation = Surface.ROTATION_90 // touch at 0 degrees -> 90 degrees - assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */, - 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) - .isEqualTo(touchHints[1]) + assertThat( + controllerOverlay.onTouchOutsideOfSensorAreaImpl( + 0.0f /* x */, 0.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation + ) + ).isEqualTo(touchHints[1]) // touch at 90 degrees -> 180 degrees - assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */, - 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) - .isEqualTo(touchHints[2]) + assertThat( + controllerOverlay.onTouchOutsideOfSensorAreaImpl( + 0.0f /* x */, -1.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation + ) + ).isEqualTo(touchHints[2]) // touch at 180 degrees -> 270 degrees - assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */, - 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) - .isEqualTo(touchHints[3]) + assertThat( + controllerOverlay.onTouchOutsideOfSensorAreaImpl( + -1.0f /* x */, 0.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation + ) + ).isEqualTo(touchHints[3]) // touch at 270 degrees -> 0 degrees - assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */, - 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) - .isEqualTo(touchHints[0]) + assertThat( + controllerOverlay.onTouchOutsideOfSensorAreaImpl( + 0.0f /* x */, 1.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation + ) + ).isEqualTo(touchHints[0]) } fun testTouchOutsideAreaNoRotation270Degrees() = withReason(REASON_ENROLL_ENROLLING) { @@ -417,21 +464,33 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { context.resources.getStringArray(R.array.udfps_accessibility_touch_hints) val rotation = Surface.ROTATION_270 // touch at 0 degrees -> 270 degrees - assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */, - 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) - .isEqualTo(touchHints[3]) + assertThat( + controllerOverlay.onTouchOutsideOfSensorAreaImpl( + 0.0f /* x */, 0.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation + ) + ).isEqualTo(touchHints[3]) // touch at 90 degrees -> 0 degrees - assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */, - 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) - .isEqualTo(touchHints[0]) + assertThat( + controllerOverlay.onTouchOutsideOfSensorAreaImpl( + 0.0f /* x */, -1.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation + ) + ).isEqualTo(touchHints[0]) // touch at 180 degrees -> 90 degrees - assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */, - 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) - .isEqualTo(touchHints[1]) + assertThat( + controllerOverlay.onTouchOutsideOfSensorAreaImpl( + -1.0f /* x */, 0.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation + ) + ).isEqualTo(touchHints[1]) // touch at 270 degrees -> 180 degrees - assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */, - 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) - .isEqualTo(touchHints[2]) + assertThat( + controllerOverlay.onTouchOutsideOfSensorAreaImpl( + 0.0f /* x */, 1.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation + ) + ).isEqualTo(touchHints[2]) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index eff47bd2ee98..49c6fd14997e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -665,7 +665,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); when(mUdfpsView.isDisplayConfigured()).thenReturn(true); // WHEN it is cancelled - mUdfpsController.onCancelUdfps(); + mUdfpsController.cancelAodInterrupt(); // THEN the display is unconfigured verify(mUdfpsView).unconfigureDisplay(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java index 3e9cf1e51b63..fa9c41a3cbb6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java @@ -35,6 +35,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerFake; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.policy.BatteryController; @@ -71,6 +72,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase { @Mock private KeyguardStateController mKeyguardStateController; @Mock + private ShadeExpansionStateManager mShadeExpansionStateManager; + @Mock private BatteryController mBatteryController; private final DockManagerFake mDockManager = new DockManagerFake(); private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); @@ -85,7 +88,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase { mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager, mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor, - mStatusBarStateController, mKeyguardStateController, mBatteryController, + mStatusBarStateController, mKeyguardStateController, mShadeExpansionStateManager, + mBatteryController, mDockManager, mFakeExecutor, mFakeSystemClock); } @@ -137,9 +141,9 @@ public class FalsingCollectorImplTest extends SysuiTestCase { public void testUnregisterSensor_QS() { mFalsingCollector.onScreenTurningOn(); reset(mProximitySensor); - mFalsingCollector.setQsExpanded(true); + mFalsingCollector.onQsExpansionChanged(true); verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class)); - mFalsingCollector.setQsExpanded(false); + mFalsingCollector.onQsExpansionChanged(false); verify(mProximitySensor).register(any(ThresholdSensor.Listener.class)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index 781dc1550048..6091d3a93f14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -23,10 +23,10 @@ import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -88,6 +88,8 @@ public class DozeTriggersTest extends SysuiTestCase { @Mock private ProximityCheck mProximityCheck; @Mock + private DozeLog mDozeLog; + @Mock private AuthController mAuthController; @Mock private UiEventLogger mUiEventLogger; @@ -127,7 +129,7 @@ public class DozeTriggersTest extends SysuiTestCase { mTriggers = new DozeTriggers(mContext, mHost, config, dozeParameters, asyncSensorManager, wakeLock, mDockManager, mProximitySensor, - mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(), + mProximityCheck, mDozeLog, mBroadcastDispatcher, new FakeSettings(), mAuthController, mUiEventLogger, mSessionTracker, mKeyguardStateController, mDevicePostureController); mTriggers.setDozeMachine(mMachine); @@ -342,6 +344,16 @@ public class DozeTriggersTest extends SysuiTestCase { verify(mProximityCheck).destroy(); } + @Test + public void testIsExecutingTransition_dropPulse() { + when(mHost.isPulsePending()).thenReturn(false); + when(mMachine.isExecutingTransition()).thenReturn(true); + + mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, 100, 100, null); + + verify(mDozeLog).tracePulseDropped(anyString(), eq(null)); + } + private void waitForSensorManager() { mExecutor.runAllReady(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt index 65b44a14d2ad..65ae90b8f7e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt @@ -19,11 +19,17 @@ package com.android.systemui.dump import androidx.test.filters.SmallTest import com.android.systemui.CoreStartable import com.android.systemui.Dumpable +import com.android.systemui.ProtoDumpable import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq import com.google.common.truth.Truth.assertThat +import java.io.FileDescriptor +import java.io.PrintWriter +import java.io.StringWriter +import javax.inject.Provider import org.junit.Before import org.junit.Test import org.mockito.Mock @@ -31,9 +37,6 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -import java.io.PrintWriter -import java.io.StringWriter -import javax.inject.Provider @SmallTest class DumpHandlerTest : SysuiTestCase() { @@ -47,6 +50,8 @@ class DumpHandlerTest : SysuiTestCase() { @Mock private lateinit var pw: PrintWriter + @Mock + private lateinit var fd: FileDescriptor @Mock private lateinit var dumpable1: Dumpable @@ -56,6 +61,11 @@ class DumpHandlerTest : SysuiTestCase() { private lateinit var dumpable3: Dumpable @Mock + private lateinit var protoDumpable1: ProtoDumpable + @Mock + private lateinit var protoDumpable2: ProtoDumpable + + @Mock private lateinit var buffer1: LogBuffer @Mock private lateinit var buffer2: LogBuffer @@ -88,7 +98,7 @@ class DumpHandlerTest : SysuiTestCase() { // WHEN some of them are dumped explicitly val args = arrayOf("dumpable1", "dumpable3", "buffer2") - dumpHandler.dump(pw, args) + dumpHandler.dump(fd, pw, args) // THEN only the requested ones have their dump() method called verify(dumpable1).dump(pw, args) @@ -107,7 +117,7 @@ class DumpHandlerTest : SysuiTestCase() { // WHEN that module is dumped val args = arrayOf("dumpable1") - dumpHandler.dump(pw, args) + dumpHandler.dump(fd, pw, args) // THEN its dump() method is called verify(dumpable1).dump(pw, args) @@ -124,7 +134,7 @@ class DumpHandlerTest : SysuiTestCase() { // WHEN a critical dump is requested val args = arrayOf("--dump-priority", "CRITICAL") - dumpHandler.dump(pw, args) + dumpHandler.dump(fd, pw, args) // THEN all modules are dumped (but no buffers) verify(dumpable1).dump(pw, args) @@ -145,7 +155,7 @@ class DumpHandlerTest : SysuiTestCase() { // WHEN a normal dump is requested val args = arrayOf("--dump-priority", "NORMAL") - dumpHandler.dump(pw, args) + dumpHandler.dump(fd, pw, args) // THEN all buffers are dumped (but no modules) verify(dumpable1, never()).dump( @@ -168,11 +178,35 @@ class DumpHandlerTest : SysuiTestCase() { val spw = PrintWriter(stringWriter) // When a config dump is requested - dumpHandler.dump(spw, arrayOf("config")) + dumpHandler.dump(fd, spw, arrayOf("config")) assertThat(stringWriter.toString()).contains(EmptyCoreStartable::class.java.simpleName) } + @Test + fun testDumpAllProtoDumpables() { + dumpManager.registerDumpable("protoDumpable1", protoDumpable1) + dumpManager.registerDumpable("protoDumpable2", protoDumpable2) + + val args = arrayOf(DumpHandler.PROTO) + dumpHandler.dump(fd, pw, args) + + verify(protoDumpable1).dumpProto(any(), eq(args)) + verify(protoDumpable2).dumpProto(any(), eq(args)) + } + + @Test + fun testDumpSingleProtoDumpable() { + dumpManager.registerDumpable("protoDumpable1", protoDumpable1) + dumpManager.registerDumpable("protoDumpable2", protoDumpable2) + + val args = arrayOf(DumpHandler.PROTO, "protoDumpable1") + dumpHandler.dump(fd, pw, args) + + verify(protoDumpable1).dumpProto(any(), eq(args)) + verify(protoDumpable2, never()).dumpProto(any(), any()) + } + private class EmptyCoreStartable : CoreStartable { override fun start() {} } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt index f977f55db483..fdeb3f5eb857 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt @@ -22,6 +22,7 @@ import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.media.MediaRoute2Info import android.os.PowerManager +import android.os.VibrationEffect import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -41,6 +42,7 @@ import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator @@ -81,10 +83,10 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { @Mock private lateinit var logger: MediaTttLogger @Mock private lateinit var mediaTttFlags: MediaTttFlags @Mock private lateinit var packageManager: PackageManager - @Mock private lateinit var powerManager: PowerManager @Mock private lateinit var viewUtil: ViewUtil @Mock private lateinit var windowManager: WindowManager + @Mock private lateinit var vibratorHelper: VibratorHelper private lateinit var chipbarCoordinator: ChipbarCoordinator private lateinit var commandQueueCallback: CommandQueue.Callbacks private lateinit var fakeAppIconDrawable: Drawable @@ -129,6 +131,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { falsingManager, falsingCollector, viewUtil, + vibratorHelper, ) chipbarCoordinator.start() @@ -182,9 +185,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) - assertThat(uiEventLoggerFake.eventId(0)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id) + verify(vibratorHelper).vibrate(any<VibrationEffect>()) } @Test @@ -203,9 +206,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) - assertThat(uiEventLoggerFake.eventId(0)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id) + verify(vibratorHelper).vibrate(any<VibrationEffect>()) } @Test @@ -224,9 +227,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) - assertThat(uiEventLoggerFake.eventId(0)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id) + verify(vibratorHelper).vibrate(any<VibrationEffect>()) } @Test @@ -245,9 +248,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) - assertThat(uiEventLoggerFake.eventId(0)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id) + verify(vibratorHelper).vibrate(any<VibrationEffect>()) } @Test @@ -265,9 +268,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText()) assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) - assertThat(uiEventLoggerFake.eventId(0)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id) + verify(vibratorHelper, never()).vibrate(any<VibrationEffect>()) } @Test @@ -335,9 +338,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText()) assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) - assertThat(uiEventLoggerFake.eventId(0)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id) + verify(vibratorHelper, never()).vibrate(any<VibrationEffect>()) } @Test @@ -408,9 +411,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE) - assertThat(uiEventLoggerFake.eventId(0)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id) + verify(vibratorHelper).vibrate(any<VibrationEffect>()) } @Test @@ -429,9 +432,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE) assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE) - assertThat(uiEventLoggerFake.eventId(0)) .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id) + verify(vibratorHelper).vibrate(any<VibrationEffect>()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 3c58b6fc1354..c452872a527e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -52,6 +52,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.DumpManager; +import com.android.systemui.dump.nano.SystemUIProtoDump; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; @@ -114,8 +115,6 @@ public class QSTileHostTest extends SysuiTestCase { @Mock private DumpManager mDumpManager; @Mock - private QSTile.State mMockState; - @Mock private CentralSurfaces mCentralSurfaces; @Mock private QSLogger mQSLogger; @@ -195,7 +194,6 @@ public class QSTileHostTest extends SysuiTestCase { } private void setUpTileFactory() { - when(mMockState.toString()).thenReturn(MOCK_STATE_STRING); // Only create this kind of tiles when(mDefaultFactory.createTile(anyString())).thenAnswer( invocation -> { @@ -209,7 +207,11 @@ public class QSTileHostTest extends SysuiTestCase { } else if ("na".equals(spec)) { return new NotAvailableTile(mQSTileHost); } else if (CUSTOM_TILE_SPEC.equals(spec)) { - return mCustomTile; + QSTile tile = mCustomTile; + QSTile.State s = mock(QSTile.State.class); + s.spec = spec; + when(mCustomTile.getState()).thenReturn(s); + return tile; } else if ("internet".equals(spec) || "wifi".equals(spec) || "cell".equals(spec)) { @@ -647,7 +649,7 @@ public class QSTileHostTest extends SysuiTestCase { @Test public void testSetTileRemoved_removedBySystem() { int user = mUserTracker.getUserId(); - saveSetting("spec1" + CUSTOM_TILE_SPEC); + saveSetting("spec1," + CUSTOM_TILE_SPEC); // This will be done by TileServiceManager mQSTileHost.setTileAdded(CUSTOM_TILE, user, true); @@ -658,6 +660,27 @@ public class QSTileHostTest extends SysuiTestCase { .getBoolean(CUSTOM_TILE.flattenToString(), false)); } + @Test + public void testProtoDump_noTiles() { + SystemUIProtoDump proto = new SystemUIProtoDump(); + mQSTileHost.dumpProto(proto, new String[0]); + + assertEquals(0, proto.tiles.length); + } + + @Test + public void testTilesInOrder() { + saveSetting("spec1," + CUSTOM_TILE_SPEC); + + SystemUIProtoDump proto = new SystemUIProtoDump(); + mQSTileHost.dumpProto(proto, new String[0]); + + assertEquals(2, proto.tiles.length); + assertEquals("spec1", proto.tiles[0].getSpec()); + assertEquals(CUSTOM_TILE.getPackageName(), proto.tiles[1].getComponentName().packageName); + assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className); + } + private SharedPreferences getSharedPreferenecesForUser(int user) { return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user); } @@ -707,12 +730,9 @@ public class QSTileHostTest extends SysuiTestCase { @Override public State newTileState() { - return mMockState; - } - - @Override - public State getState() { - return mMockState; + State s = mock(QSTile.State.class); + when(s.toString()).thenReturn(MOCK_STATE_STRING); + return s; } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt new file mode 100644 index 000000000000..629c663943db --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt @@ -0,0 +1,104 @@ +package com.android.systemui.qs + +import android.content.ComponentName +import android.service.quicksettings.Tile +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.external.CustomTile +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class TileStateToProtoTest : SysuiTestCase() { + + companion object { + private const val TEST_LABEL = "label" + private const val TEST_SUBTITLE = "subtitle" + private const val TEST_SPEC = "spec" + private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls") + } + + @Test + fun platformTile_INACTIVE() { + val state = + QSTile.State().apply { + spec = TEST_SPEC + label = TEST_LABEL + secondaryLabel = TEST_SUBTITLE + state = Tile.STATE_INACTIVE + } + val proto = state.toProto() + + assertThat(proto).isNotNull() + assertThat(proto?.hasSpec()).isTrue() + assertThat(proto?.spec).isEqualTo(TEST_SPEC) + assertThat(proto?.hasComponentName()).isFalse() + assertThat(proto?.label).isEqualTo(TEST_LABEL) + assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE) + assertThat(proto?.state).isEqualTo(Tile.STATE_INACTIVE) + assertThat(proto?.hasBooleanState()).isFalse() + } + + @Test + fun componentTile_UNAVAILABLE() { + val state = + QSTile.State().apply { + spec = CustomTile.toSpec(TEST_COMPONENT) + label = TEST_LABEL + secondaryLabel = TEST_SUBTITLE + state = Tile.STATE_UNAVAILABLE + } + val proto = state.toProto() + + assertThat(proto).isNotNull() + assertThat(proto?.hasSpec()).isFalse() + assertThat(proto?.hasComponentName()).isTrue() + val componentName = proto?.componentName + assertThat(componentName?.packageName).isEqualTo(TEST_COMPONENT.packageName) + assertThat(componentName?.className).isEqualTo(TEST_COMPONENT.className) + assertThat(proto?.label).isEqualTo(TEST_LABEL) + assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE) + assertThat(proto?.state).isEqualTo(Tile.STATE_UNAVAILABLE) + assertThat(proto?.hasBooleanState()).isFalse() + } + + @Test + fun booleanState_ACTIVE() { + val state = + QSTile.BooleanState().apply { + spec = TEST_SPEC + label = TEST_LABEL + secondaryLabel = TEST_SUBTITLE + state = Tile.STATE_ACTIVE + value = true + } + val proto = state.toProto() + + assertThat(proto).isNotNull() + assertThat(proto?.hasSpec()).isTrue() + assertThat(proto?.spec).isEqualTo(TEST_SPEC) + assertThat(proto?.hasComponentName()).isFalse() + assertThat(proto?.label).isEqualTo(TEST_LABEL) + assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE) + assertThat(proto?.state).isEqualTo(Tile.STATE_ACTIVE) + assertThat(proto?.hasBooleanState()).isTrue() + assertThat(proto?.booleanState).isTrue() + } + + @Test + fun noSpec_returnsNull() { + val state = + QSTile.State().apply { + label = TEST_LABEL + secondaryLabel = TEST_SUBTITLE + state = Tile.STATE_ACTIVE + } + val proto = state.toProto() + + assertThat(proto).isNull() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index d095add1c660..5c7e4e9691e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -108,6 +108,7 @@ import com.android.systemui.media.KeyguardMediaController; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.model.SysUiState; +import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; @@ -254,6 +255,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private KeyguardMediaController mKeyguardMediaController; @Mock private PrivacyDotViewController mPrivacyDotViewController; @Mock private NavigationModeController mNavigationModeController; + @Mock private NavigationBarController mNavigationBarController; @Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController; @Mock private ContentResolver mContentResolver; @Mock private TapAgainViewController mTapAgainViewController; @@ -377,6 +379,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { NotificationWakeUpCoordinator coordinator = new NotificationWakeUpCoordinator( + mDumpManager, mock(HeadsUpManagerPhone.class), new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager, mInteractionJankMonitor), @@ -392,6 +395,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mConfigurationController, mStatusBarStateController, mFalsingManager, + mShadeExpansionStateManager, mLockscreenShadeTransitionController, new FalsingCollectorFake(), mDumpManager); @@ -430,6 +434,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mView.getParent()).thenReturn(mViewParent); when(mQs.getHeader()).thenReturn(mQsHeader); when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN); + when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); mMainHandler = new Handler(Looper.getMainLooper()); NotificationPanelViewController.PanelEventsEmitter panelEventsEmitter = @@ -473,6 +478,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mPrivacyDotViewController, mTapAgainViewController, mNavigationModeController, + mNavigationBarController, mFragmentService, mContentResolver, mRecordingController, @@ -757,6 +763,38 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test + public void testOnTouchEvent_expansionResumesAfterBriefTouch() { + // Start shade collapse with swipe up + onTouchEvent(MotionEvent.obtain(0L /* downTime */, + 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, + 0 /* metaState */)); + onTouchEvent(MotionEvent.obtain(0L /* downTime */, + 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */, + 0 /* metaState */)); + onTouchEvent(MotionEvent.obtain(0L /* downTime */, + 0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */, + 0 /* metaState */)); + + assertThat(mNotificationPanelViewController.getClosing()).isTrue(); + assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue(); + + // simulate touch that does not exceed touch slop + onTouchEvent(MotionEvent.obtain(2L /* downTime */, + 2L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 300f /* y */, + 0 /* metaState */)); + + mNotificationPanelViewController.setTouchSlopExceeded(false); + + onTouchEvent(MotionEvent.obtain(2L /* downTime */, + 2L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */, + 0 /* metaState */)); + + // fling should still be called after a touch that does not exceed touch slop + assertThat(mNotificationPanelViewController.getClosing()).isTrue(); + assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue(); + } + + @Test public void handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() { when(mCommandQueue.panelsEnabled()).thenReturn(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt index 12ef036d89d0..bdafc7df33bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt @@ -66,6 +66,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { @Mock private lateinit var largeScreenShadeHeaderController: LargeScreenShadeHeaderController @Mock + private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager + @Mock private lateinit var featureFlags: FeatureFlags @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener> @@ -96,6 +98,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { navigationModeController, overviewProxyService, largeScreenShadeHeaderController, + shadeExpansionStateManager, featureFlags, delayableExecutor ) @@ -380,6 +383,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { navigationModeController, overviewProxyService, largeScreenShadeHeaderController, + shadeExpansionStateManager, featureFlags, delayableExecutor ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index ad3d3d2958cb..95cf9d60b511 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -88,6 +88,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Mock private KeyguardStateController mKeyguardStateController; @Mock private ScreenOffAnimationController mScreenOffAnimationController; @Mock private AuthController mAuthController; + @Mock private ShadeExpansionStateManager mShadeExpansionStateManager; @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; @@ -103,7 +104,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, mColorExtractor, mDumpManager, mKeyguardStateController, - mScreenOffAnimationController, mAuthController) { + mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager) { @Override protected boolean isDebuggable() { return false; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt index 44cbe51a30ac..fbb8ebfb3e3b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager @@ -56,6 +57,7 @@ class PulseExpansionHandlerTest : SysuiTestCase() { private val configurationController: ConfigurationController = mock() private val statusBarStateController: StatusBarStateController = mock() private val falsingManager: FalsingManager = mock() + private val shadeExpansionStateManager: ShadeExpansionStateManager = mock() private val lockscreenShadeTransitionController: LockscreenShadeTransitionController = mock() private val falsingCollector: FalsingCollector = mock() private val dumpManager: DumpManager = mock() @@ -65,7 +67,8 @@ class PulseExpansionHandlerTest : SysuiTestCase() { fun setUp() { whenever(expandableView.collapsedHeight).thenReturn(collapsedHeight) - pulseExpansionHandler = PulseExpansionHandler( + pulseExpansionHandler = + PulseExpansionHandler( mContext, wakeUpCoordinator, bypassController, @@ -74,10 +77,11 @@ class PulseExpansionHandlerTest : SysuiTestCase() { configurationController, statusBarStateController, falsingManager, + shadeExpansionStateManager, lockscreenShadeTransitionController, falsingCollector, dumpManager - ) + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java index 4b458f5a9123..dda7fadde2d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java @@ -31,8 +31,8 @@ public class GroupEntryBuilder { private long mCreationTime = 0; @Nullable private GroupEntry mParent = GroupEntry.ROOT_ENTRY; private NotifSection mNotifSection; - private NotificationEntry mSummary = null; - private List<NotificationEntry> mChildren = new ArrayList<>(); + @Nullable private NotificationEntry mSummary = null; + private final List<NotificationEntry> mChildren = new ArrayList<>(); /** Builds a new instance of GroupEntry */ public GroupEntry build() { @@ -41,7 +41,9 @@ public class GroupEntryBuilder { ge.getAttachState().setSection(mNotifSection); ge.setSummary(mSummary); - mSummary.setParent(ge); + if (mSummary != null) { + mSummary.setParent(ge); + } for (NotificationEntry child : mChildren) { ge.addChild(child); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 851517e1e35b..3b05321e1a6b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -1498,45 +1498,8 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test - public void testMissingRankingWhenRemovalFeatureIsDisabled() { + public void testMissingRanking() { // GIVEN a pipeline with one two notifications - when(mNotifPipelineFlags.removeUnrankedNotifs()).thenReturn(false); - String key1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")).key; - String key2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")).key; - NotificationEntry entry1 = mCollectionListener.getEntry(key1); - NotificationEntry entry2 = mCollectionListener.getEntry(key2); - clearInvocations(mCollectionListener); - - // GIVEN the message for removing key1 gets does not reach NotifCollection - Ranking ranking1 = mNoMan.removeRankingWithoutEvent(key1); - // WHEN the message for removing key2 arrives - mNoMan.retractNotif(entry2.getSbn(), REASON_APP_CANCEL); - - // THEN only entry2 gets removed - verify(mCollectionListener).onEntryRemoved(eq(entry2), eq(REASON_APP_CANCEL)); - verify(mCollectionListener).onEntryCleanUp(eq(entry2)); - verify(mCollectionListener).onRankingApplied(); - verifyNoMoreInteractions(mCollectionListener); - verify(mLogger).logMissingRankings(eq(List.of(entry1)), eq(1), any()); - verify(mLogger, never()).logRecoveredRankings(any(), anyInt()); - clearInvocations(mCollectionListener, mLogger); - - // WHEN a ranking update includes key1 again - mNoMan.setRanking(key1, ranking1); - mNoMan.issueRankingUpdate(); - - // VERIFY that we do nothing but log the 'recovery' - verify(mCollectionListener).onRankingUpdate(any()); - verify(mCollectionListener).onRankingApplied(); - verifyNoMoreInteractions(mCollectionListener); - verify(mLogger, never()).logMissingRankings(any(), anyInt(), any()); - verify(mLogger).logRecoveredRankings(eq(List.of(key1)), eq(0)); - } - - @Test - public void testMissingRankingWhenRemovalFeatureIsEnabled() { - // GIVEN a pipeline with one two notifications - when(mNotifPipelineFlags.removeUnrankedNotifs()).thenReturn(true); String key1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")).key; String key2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")).key; NotificationEntry entry1 = mCollectionListener.getEntry(key1); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index f4adf6927e31..b6b0b7738997 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -181,7 +181,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Test public void testInflatesNewNotification() { // WHEN there is a new notification - mCollectionListener.onEntryAdded(mEntry); + mCollectionListener.onEntryInit(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); // THEN we inflate it @@ -194,7 +194,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Test public void testRebindsInflatedNotificationsOnUpdate() { // GIVEN an inflated notification - mCollectionListener.onEntryAdded(mEntry); + mCollectionListener.onEntryInit(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); verify(mNotifInflater).inflateViews(eq(mEntry), any(), any()); mNotifInflater.invokeInflateCallbackForEntry(mEntry); @@ -213,7 +213,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Test public void testEntrySmartReplyAdditionWillRebindViews() { // GIVEN an inflated notification - mCollectionListener.onEntryAdded(mEntry); + mCollectionListener.onEntryInit(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); verify(mNotifInflater).inflateViews(eq(mEntry), any(), any()); mNotifInflater.invokeInflateCallbackForEntry(mEntry); @@ -232,7 +232,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Test public void testEntryChangedToMinimizedSectionWillRebindViews() { // GIVEN an inflated notification - mCollectionListener.onEntryAdded(mEntry); + mCollectionListener.onEntryInit(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any()); assertFalse(mParamsCaptor.getValue().isLowPriority()); @@ -254,7 +254,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { public void testMinimizedEntryMovedIntoGroupWillRebindViews() { // GIVEN an inflated, minimized notification setSectionIsLowPriority(true); - mCollectionListener.onEntryAdded(mEntry); + mCollectionListener.onEntryInit(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any()); assertTrue(mParamsCaptor.getValue().isLowPriority()); @@ -275,7 +275,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Test public void testEntryRankChangeWillNotRebindViews() { // GIVEN an inflated notification - mCollectionListener.onEntryAdded(mEntry); + mCollectionListener.onEntryInit(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); verify(mNotifInflater).inflateViews(eq(mEntry), any(), any()); mNotifInflater.invokeInflateCallbackForEntry(mEntry); @@ -294,7 +294,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Test public void testDoesntFilterInflatedNotifs() { // GIVEN an inflated notification - mCollectionListener.onEntryAdded(mEntry); + mCollectionListener.onEntryInit(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); verify(mNotifInflater).inflateViews(eq(mEntry), any(), any()); mNotifInflater.invokeInflateCallbackForEntry(mEntry); @@ -330,9 +330,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mCollectionListener.onEntryInit(entry); } - mCollectionListener.onEntryAdded(summary); + mCollectionListener.onEntryInit(summary); for (NotificationEntry entry : children) { - mCollectionListener.onEntryAdded(entry); + mCollectionListener.onEntryInit(entry); } mBeforeFilterListener.onBeforeFinalizeFilter(List.of(groupEntry)); @@ -393,6 +393,70 @@ public class PreparationCoordinatorTest extends SysuiTestCase { } @Test + public void testNullGroupSummary() { + // GIVEN a newly-posted group with a summary and two children + final GroupEntry group = new GroupEntryBuilder() + .setCreationTime(400) + .setSummary(getNotificationEntryBuilder().setId(1).build()) + .addChild(getNotificationEntryBuilder().setId(2).build()) + .addChild(getNotificationEntryBuilder().setId(3).build()) + .build(); + fireAddEvents(List.of(group)); + final NotificationEntry child0 = group.getChildren().get(0); + final NotificationEntry child1 = group.getChildren().get(1); + mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group)); + + // WHEN the summary is pruned + new GroupEntryBuilder() + .setCreationTime(400) + .addChild(child0) + .addChild(child1) + .build(); + + // WHEN all of the children (but not the summary) finish inflating + mNotifInflater.invokeInflateCallbackForEntry(child0); + mNotifInflater.invokeInflateCallbackForEntry(child1); + + // THEN the entire group is not filtered out + assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401)); + assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401)); + } + + @Test + public void testPartiallyInflatedGroupsAreNotFilteredOutIfSummaryReinflate() { + // GIVEN a newly-posted group with a summary and two children + final String groupKey = "test_reinflate_group"; + final int summaryId = 1; + final GroupEntry group = new GroupEntryBuilder() + .setKey(groupKey) + .setCreationTime(400) + .setSummary(getNotificationEntryBuilder().setId(summaryId).setImportance(1).build()) + .addChild(getNotificationEntryBuilder().setId(2).build()) + .addChild(getNotificationEntryBuilder().setId(3).build()) + .build(); + fireAddEvents(List.of(group)); + final NotificationEntry summary = group.getSummary(); + final NotificationEntry child0 = group.getChildren().get(0); + final NotificationEntry child1 = group.getChildren().get(1); + mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group)); + + // WHEN all of the children (but not the summary) finish inflating + mNotifInflater.invokeInflateCallbackForEntry(child0); + mNotifInflater.invokeInflateCallbackForEntry(child1); + mNotifInflater.invokeInflateCallbackForEntry(summary); + + // WHEN the summary is updated and starts re-inflating + summary.setRanking(new RankingBuilder(summary.getRanking()).setImportance(4).build()); + fireUpdateEvents(summary); + mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group)); + + // THEN the entire group is still not filtered out + assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401)); + assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401)); + assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401)); + } + + @Test public void testCompletedInflatedGroupsAreReleased() { // GIVEN a newly-posted group with a summary and two children final GroupEntry group = new GroupEntryBuilder() @@ -412,7 +476,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mNotifInflater.invokeInflateCallbackForEntry(child1); mNotifInflater.invokeInflateCallbackForEntry(summary); - // THEN the entire group is still filtered out + // THEN the entire group is no longer filtered out assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401)); assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401)); assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401)); @@ -494,7 +558,11 @@ public class PreparationCoordinatorTest extends SysuiTestCase { private void fireAddEvents(NotificationEntry entry) { mCollectionListener.onEntryInit(entry); - mCollectionListener.onEntryAdded(entry); + mCollectionListener.onEntryInit(entry); + } + + private void fireUpdateEvents(NotificationEntry entry) { + mCollectionListener.onEntryUpdated(entry); } private static final String TEST_MESSAGE = "TEST_MESSAGE"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index 46f630b7db63..ea311da3e20b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -51,12 +51,14 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; +import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -97,6 +99,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { NotifPipelineFlags mFlags; @Mock KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; + UiEventLoggerFake mUiEventLoggerFake; @Mock PendingIntent mPendingIntent; @@ -107,6 +110,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false); + mUiEventLoggerFake = new UiEventLoggerFake(); + mNotifInterruptionStateProvider = new NotificationInterruptStateProviderImpl( mContext.getContentResolver(), @@ -120,7 +125,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { mLogger, mMockHandler, mFlags, - mKeyguardNotificationVisibilityProvider); + mKeyguardNotificationVisibilityProvider, + mUiEventLoggerFake); mNotifInterruptionStateProvider.mUseHeadsUp = true; } @@ -442,6 +448,13 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { verify(mLogger, never()).logNoFullscreen(any(), any()); verify(mLogger).logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN"); verify(mLogger, never()).logFullscreen(any(), any()); + + assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1); + UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0); + assertThat(fakeUiEvent.eventId).isEqualTo( + NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR.getId()); + assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid()); + assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName()); } @Test @@ -600,6 +613,13 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { verify(mLogger, never()).logNoFullscreen(any(), any()); verify(mLogger).logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard"); verify(mLogger, never()).logFullscreen(any(), any()); + + assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1); + UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0); + assertThat(fakeUiEvent.eventId).isEqualTo( + NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD.getId()); + assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid()); + assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName()); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index d4cd4a608340..6de8bd5f3670 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -80,6 +80,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.testing.FakeMetricsLogger; import com.android.internal.statusbar.IStatusBarService; @@ -323,7 +324,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mock(NotificationInterruptLogger.class), new Handler(TestableLooper.get(this).getLooper()), mock(NotifPipelineFlags.class), - mock(KeyguardNotificationVisibilityProvider.class)); + mock(KeyguardNotificationVisibilityProvider.class), + mock(UiEventLogger.class)); mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class)); mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class)); @@ -1157,7 +1159,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { NotificationInterruptLogger logger, Handler mainHandler, NotifPipelineFlags flags, - KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) { + KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider, + UiEventLogger uiEventLogger) { super( contentResolver, powerManager, @@ -1170,7 +1173,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { logger, mainHandler, flags, - keyguardNotificationVisibilityProvider + keyguardNotificationVisibilityProvider, + uiEventLogger ); mUseHeadsUp = true; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 8da8d049516e..0c35659b458a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -117,7 +117,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private BouncerCallbackInteractor mBouncerCallbackInteractor; @Mock private BouncerInteractor mBouncerInteractor; @Mock private BouncerView mBouncerView; -// @Mock private WeakReference<BouncerViewDelegate> mBouncerViewDelegateWeakReference; @Mock private BouncerViewDelegate mBouncerViewDelegate; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt index f751afc195b2..2f18ce31217e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt @@ -27,6 +27,9 @@ class FakeWifiRepository : WifiRepository { private val _isWifiEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false) override val isWifiEnabled: StateFlow<Boolean> = _isWifiEnabled + private val _isWifiDefault: MutableStateFlow<Boolean> = MutableStateFlow(false) + override val isWifiDefault: StateFlow<Boolean> = _isWifiDefault + private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> = MutableStateFlow(WifiNetworkModel.Inactive) override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork @@ -38,6 +41,10 @@ class FakeWifiRepository : WifiRepository { _isWifiEnabled.value = enabled } + fun setIsWifiDefault(default: Boolean) { + _isWifiDefault.value = default + } + fun setWifiNetwork(wifiNetworkModel: WifiNetworkModel) { _wifiNetwork.value = wifiNetworkModel } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt index 0ba0bd623c39..a64a4bd2e57a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt @@ -222,6 +222,83 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test + fun isWifiDefault_initiallyGetsDefault() = runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + assertThat(underTest.isWifiDefault.value).isFalse() + + job.cancel() + } + + @Test + fun isWifiDefault_wifiNetwork_isTrue() = runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + val wifiInfo = mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + } + + getDefaultNetworkCallback().onCapabilitiesChanged( + NETWORK, + createWifiNetworkCapabilities(wifiInfo) + ) + + assertThat(underTest.isWifiDefault.value).isTrue() + + job.cancel() + } + + @Test + fun isWifiDefault_cellularVcnNetwork_isTrue() = runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + val capabilities = mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(underTest.isWifiDefault.value).isTrue() + + job.cancel() + } + + @Test + fun isWifiDefault_cellularNotVcnNetwork_isFalse() = runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + val capabilities = mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(mock()) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + + assertThat(underTest.isWifiDefault.value).isFalse() + + job.cancel() + } + + @Test + fun isWifiDefault_wifiNetworkLost_isFalse() = runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) + + // First, add a network + getDefaultNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat(underTest.isWifiDefault.value).isTrue() + + // WHEN the network is lost + getDefaultNetworkCallback().onLost(NETWORK) + + // THEN we update to false + assertThat(underTest.isWifiDefault.value).isFalse() + + job.cancel() + } + + @Test fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) { var latest: WifiNetworkModel? = null val job = underTest @@ -745,6 +822,12 @@ class WifiRepositoryImplTest : SysuiTestCase() { return callbackCaptor.value!! } + private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback { + val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>() + verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture()) + return callbackCaptor.value!! + } + private fun createWifiNetworkCapabilities( wifiInfo: WifiInfo, isValidated: Boolean = true, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt index 39b886af1cb8..71b8bab87d19 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt @@ -178,6 +178,29 @@ class WifiInteractorTest : SysuiTestCase() { } @Test + fun isDefault_matchesRepoIsDefault() = runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest + .isDefault + .onEach { latest = it } + .launchIn(this) + + wifiRepository.setIsWifiDefault(true) + yield() + assertThat(latest).isTrue() + + wifiRepository.setIsWifiDefault(false) + yield() + assertThat(latest).isFalse() + + wifiRepository.setIsWifiDefault(true) + yield() + assertThat(latest).isTrue() + + job.cancel() + } + + @Test fun wifiNetwork_matchesRepoWifiNetwork() = runBlocking(IMMEDIATE) { val wifiNetwork = WifiNetworkModel.Active( networkId = 45, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt index e56623f9fea2..7686071206f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt @@ -88,6 +88,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase fun wifiIcon() = runBlocking(IMMEDIATE) { wifiRepository.setIsWifiEnabled(testCase.enabled) + wifiRepository.setIsWifiDefault(testCase.isDefault) connectivityRepository.setForceHiddenIcons( if (testCase.forceHidden) { setOf(ConnectivitySlot.WIFI) @@ -152,6 +153,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase val forceHidden: Boolean = false, val alwaysShowIconWhenEnabled: Boolean = false, val hasDataCapabilities: Boolean = true, + val isDefault: Boolean = false, val network: WifiNetworkModel, /** The expected output. Null if we expect the output to be null. */ @@ -162,6 +164,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase "forceHidden=$forceHidden, " + "showWhenEnabled=$alwaysShowIconWhenEnabled, " + "hasDataCaps=$hasDataCapabilities, " + + "isDefault=$isDefault, " + "network=$network) then " + "EXPECTED($expected)" } @@ -296,6 +299,46 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), ), + // isDefault = true => all Inactive and Active networks shown + TestCase( + isDefault = true, + network = WifiNetworkModel.Inactive, + expected = + Expected( + iconResource = WIFI_NO_NETWORK, + contentDescription = { context -> + "${context.getString(WIFI_NO_CONNECTION)}," + + context.getString(NO_INTERNET) + }, + description = "No network icon", + ), + ), + TestCase( + isDefault = true, + network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 3), + expected = + Expected( + iconResource = WIFI_NO_INTERNET_ICONS[3], + contentDescription = { context -> + "${context.getString(WIFI_CONNECTION_STRENGTH[3])}," + + context.getString(NO_INTERNET) + }, + description = "No internet level 3 icon", + ), + ), + TestCase( + isDefault = true, + network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1), + expected = + Expected( + iconResource = WIFI_FULL_ICONS[1], + contentDescription = { context -> + context.getString(WIFI_CONNECTION_STRENGTH[1]) + }, + description = "Full internet level 1 icon", + ), + ), + // network = CarrierMerged => not shown TestCase( network = WifiNetworkModel.CarrierMerged, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt index 3169eef83f07..79633d467b7d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt @@ -76,6 +76,8 @@ class WifiViewModelTest : SysuiTestCase() { scope.cancel() } + // See [WifiViewModelIconParameterizedTest] for additional view model tests. + // Note on testing: [WifiViewModel] exposes 3 different instances of // [LocationBasedWifiViewModel]. In practice, these 3 different instances will get the exact // same data for icon, activity, etc. flows. So, most of these tests will test just one of the diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt index fa78b3832158..9fbf159ec348 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.temporarydisplay.chipbar import android.os.PowerManager +import android.os.VibrationEffect import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -36,6 +37,7 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any @@ -66,6 +68,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() { @Mock private lateinit var falsingManager: FalsingManager @Mock private lateinit var falsingCollector: FalsingCollector @Mock private lateinit var viewUtil: ViewUtil + @Mock private lateinit var vibratorHelper: VibratorHelper private lateinit var fakeClock: FakeSystemClock private lateinit var fakeExecutor: FakeExecutor private lateinit var uiEventLoggerFake: UiEventLoggerFake @@ -92,6 +95,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() { falsingManager, falsingCollector, viewUtil, + vibratorHelper, ) underTest.start() } @@ -273,6 +277,20 @@ class ChipbarCoordinatorTest : SysuiTestCase() { } @Test + fun displayView_vibrationEffect_doubleClickEffect() { + underTest.displayView( + ChipbarInfo( + Icon.Resource(R.id.check_box, null), + Text.Loaded("text"), + endItem = null, + vibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), + ) + ) + + verify(vibratorHelper).vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)) + } + + @Test fun updateView_viewUpdated() { // First, display a view val drawable = context.getDrawable(R.drawable.ic_celebration)!! diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt index 8f32e0fd1de5..17d402319246 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt @@ -25,6 +25,7 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.view.ViewUtil @@ -41,6 +42,7 @@ class FakeChipbarCoordinator( falsingManager: FalsingManager, falsingCollector: FalsingCollector, viewUtil: ViewUtil, + vibratorHelper: VibratorHelper, ) : ChipbarCoordinator( context, @@ -53,6 +55,7 @@ class FakeChipbarCoordinator( falsingManager, falsingCollector, viewUtil, + vibratorHelper, ) { override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { // Just bypass the animation in tests diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt index d951f366c595..525d8371c9ff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt @@ -110,7 +110,7 @@ class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() { val thirdExpectedValue = setUpUsers( count = 2, - hasGuest = true, + isLastGuestUser = true, selectedIndex = 1, ) underTest.refreshUsers() @@ -121,21 +121,25 @@ class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() { } @Test - fun `refreshUsers - sorts by creation time`() = runSelfCancelingTest { + fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest { underTest = create(this) val unsortedUsers = setUpUsers( count = 3, selectedIndex = 0, + isLastGuestUser = true, + ) + unsortedUsers[0].creationTime = 999 + unsortedUsers[1].creationTime = 900 + unsortedUsers[2].creationTime = 950 + val expectedUsers = + listOf( + unsortedUsers[1], + unsortedUsers[0], + unsortedUsers[2], // last because this is the guest ) - unsortedUsers[0].creationTime = 900 - unsortedUsers[1].creationTime = 700 - unsortedUsers[2].creationTime = 999 - val expectedUsers = listOf(unsortedUsers[1], unsortedUsers[0], unsortedUsers[2]) var userInfos: List<UserInfo>? = null - var selectedUserInfo: UserInfo? = null underTest.userInfos.onEach { userInfos = it }.launchIn(this) - underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this) underTest.refreshUsers() assertThat(userInfos).isEqualTo(expectedUsers) @@ -143,14 +147,14 @@ class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() { private fun setUpUsers( count: Int, - hasGuest: Boolean = false, + isLastGuestUser: Boolean = false, selectedIndex: Int = 0, ): List<UserInfo> { val userInfos = (0 until count).map { index -> createUserInfo( index, - isGuest = hasGuest && index == count - 1, + isGuest = isLastGuestUser && index == count - 1, ) } whenever(manager.aliveUsers).thenReturn(userInfos) diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt index e80d5166d088..f682e31c0547 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt @@ -28,6 +28,7 @@ import androidx.test.filters.SmallTest import com.android.internal.R.drawable.ic_account_circle import com.android.systemui.R import com.android.systemui.common.shared.model.Text +import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.user.data.source.UserRecord import com.android.systemui.user.domain.model.ShowDialogRequestModel @@ -317,14 +318,16 @@ class UserInteractorRefactoredTest : UserInteractorTest() { keyguardRepository.setKeyguardShowing(false) var dialogRequest: ShowDialogRequestModel? = null val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) + val dialogShower: UserSwitchDialogController.DialogShower = mock() - underTest.executeAction(UserActionModel.ADD_USER) + underTest.executeAction(UserActionModel.ADD_USER, dialogShower) assertThat(dialogRequest) .isEqualTo( ShowDialogRequestModel.ShowAddUserDialog( userHandle = userInfos[0].userHandle, isKeyguardShowing = false, showEphemeralMessage = false, + dialogShower = dialogShower, ) ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 09da52e7685c..fa7ebf6a2449 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -80,6 +80,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; +import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; @@ -91,6 +92,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.ShadeController; +import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; @@ -190,6 +192,8 @@ public class BubblesTest extends SysuiTestCase { private NotificationShadeWindowView mNotificationShadeWindowView; @Mock private AuthController mAuthController; + @Mock + private ShadeExpansionStateManager mShadeExpansionStateManager; private SysUiState mSysUiState; private boolean mSysUiStateBubblesExpanded; @@ -290,7 +294,7 @@ public class BubblesTest extends SysuiTestCase { mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController, mColorExtractor, mDumpManager, mKeyguardStateController, - mScreenOffAnimationController, mAuthController); + mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager); mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); mNotificationShadeWindowController.attach(); @@ -343,7 +347,8 @@ public class BubblesTest extends SysuiTestCase { mock(NotificationInterruptLogger.class), mock(Handler.class), mock(NotifPipelineFlags.class), - mock(KeyguardNotificationVisibilityProvider.class) + mock(KeyguardNotificationVisibilityProvider.class), + mock(UiEventLogger.class) ); when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor); mBubbleController = new TestableBubbleController( diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java index 9635faf6e858..e5316bc83a12 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java @@ -22,6 +22,7 @@ import android.os.Handler; import android.os.PowerManager; import android.service.dreams.IDreamManager; +import com.android.internal.logging.UiEventLogger; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider; @@ -46,7 +47,8 @@ public class TestableNotificationInterruptStateProviderImpl NotificationInterruptLogger logger, Handler mainHandler, NotifPipelineFlags flags, - KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) { + KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider, + UiEventLogger uiEventLogger) { super(contentResolver, powerManager, dreamManager, @@ -58,7 +60,8 @@ public class TestableNotificationInterruptStateProviderImpl logger, mainHandler, flags, - keyguardNotificationVisibilityProvider); + keyguardNotificationVisibilityProvider, + uiEventLogger); mUseHeadsUp = true; } } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index a185b585aeda..346fc6c4ab96 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -872,6 +872,33 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } @Override + public void setAppWidgetHidden(String callingPackage, int hostId) { + final int userId = UserHandle.getCallingUserId(); + + if (DEBUG) { + Slog.i(TAG, "setAppWidgetHidden() " + userId); + } + + mSecurityPolicy.enforceCallFromPackage(callingPackage); + + synchronized (mLock) { + ensureGroupStateLoadedLocked(userId, /* enforceUserUnlockingOrUnlocked */false); + + HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage); + Host host = lookupHostLocked(id); + + if (host != null) { + try { + mAppOpsManagerInternal.updateAppWidgetVisibility(host.getWidgetUids(), false); + } catch (NullPointerException e) { + Slog.e(TAG, "setAppWidgetHidden(): Getting host uids: " + host.toString(), e); + throw e; + } + } + } + } + + @Override public void deleteAppWidgetId(String callingPackage, int appWidgetId) { final int userId = UserHandle.getCallingUserId(); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 7e2e932f04ef..63f81822e867 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -13373,27 +13373,19 @@ public class ActivityManagerService extends IActivityManager.Stub int callingPid; boolean instantApp; synchronized(this) { - if (caller != null) { - callerApp = getRecordForAppLOSP(caller); - if (callerApp == null) { - throw new SecurityException( - "Unable to find app for caller " + caller - + " (pid=" + Binder.getCallingPid() - + ") when registering receiver " + receiver); - } - if (callerApp.info.uid != SYSTEM_UID - && !callerApp.getPkgList().containsKey(callerPackage) - && !"android".equals(callerPackage)) { - throw new SecurityException("Given caller package " + callerPackage - + " is not running in process " + callerApp); - } - callingUid = callerApp.info.uid; - callingPid = callerApp.getPid(); - } else { - callerPackage = null; - callingUid = Binder.getCallingUid(); - callingPid = Binder.getCallingPid(); + callerApp = getRecordForAppLOSP(caller); + if (callerApp == null) { + Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller); + return null; + } + if (callerApp.info.uid != SYSTEM_UID + && !callerApp.getPkgList().containsKey(callerPackage) + && !"android".equals(callerPackage)) { + throw new SecurityException("Given caller package " + callerPackage + + " is not running in process " + callerApp); } + callingUid = callerApp.info.uid; + callingPid = callerApp.getPid(); instantApp = isInstantApp(callerApp, callerPackage, callingUid); userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true, diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index a4a1c2f0d87c..28a81e6ee145 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -167,7 +167,7 @@ public class BroadcastConstants { */ public long DELAY_NORMAL_MILLIS = DEFAULT_DELAY_NORMAL_MILLIS; private static final String KEY_DELAY_NORMAL_MILLIS = "bcast_delay_normal_millis"; - private static final long DEFAULT_DELAY_NORMAL_MILLIS = 10_000 * Build.HW_TIMEOUT_MULTIPLIER; + private static final long DEFAULT_DELAY_NORMAL_MILLIS = 1_000; /** * For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts @@ -175,7 +175,7 @@ public class BroadcastConstants { */ public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS; private static final String KEY_DELAY_CACHED_MILLIS = "bcast_delay_cached_millis"; - private static final long DEFAULT_DELAY_CACHED_MILLIS = 30_000 * Build.HW_TIMEOUT_MULTIPLIER; + private static final long DEFAULT_DELAY_CACHED_MILLIS = 10_000; /** * For {@link BroadcastQueueModernImpl}: Maximum number of complete diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index b57e3f9ed837..868c3ae6da50 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -147,12 +147,16 @@ class BroadcastProcessQueue { private int mCountOrdered; private int mCountAlarm; private int mCountPrioritized; + private int mCountInteractive; + private int mCountResultTo; + private int mCountInstrumented; private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE; private @Reason int mRunnableAtReason = REASON_EMPTY; private boolean mRunnableAtInvalidated; private boolean mProcessCached; + private boolean mProcessInstrumented; private String mCachedToString; private String mCachedToShortString; @@ -297,6 +301,18 @@ class BroadcastProcessQueue { } /** + * Update the actively running "warm" process for this process. + */ + public void setProcess(@Nullable ProcessRecord app) { + this.app = app; + if (app != null) { + setProcessInstrumented(app.getActiveInstrumentation() != null); + } else { + setProcessInstrumented(false); + } + } + + /** * Update if this process is in the "cached" state, typically signaling that * broadcast dispatch should be paused or delayed. */ @@ -308,6 +324,18 @@ class BroadcastProcessQueue { } /** + * Update if this process is in the "instrumented" state, typically + * signaling that broadcast dispatch should bypass all pauses or delays, to + * avoid holding up test suites. + */ + public void setProcessInstrumented(boolean instrumented) { + if (mProcessInstrumented != instrumented) { + mProcessInstrumented = instrumented; + invalidateRunnableAt(); + } + } + + /** * Return if we know of an actively running "warm" process for this queue. */ public boolean isProcessWarm() { @@ -315,13 +343,12 @@ class BroadcastProcessQueue { } public int getPreferredSchedulingGroupLocked() { - if (mCountForeground > 0 || mCountOrdered > 0 || mCountAlarm > 0) { - // We have an important broadcast somewhere down the queue, so + if (mCountForeground > 0) { + // We have a foreground broadcast somewhere down the queue, so // boost priority until we drain them all return ProcessList.SCHED_GROUP_DEFAULT; - } else if ((mActive != null) - && (mActive.isForeground() || mActive.ordered || mActive.alarm)) { - // We have an important broadcast right now, so boost priority + } else if ((mActive != null) && mActive.isForeground()) { + // We have a foreground broadcast right now, so boost priority return ProcessList.SCHED_GROUP_DEFAULT; } else if (!isIdle()) { return ProcessList.SCHED_GROUP_BACKGROUND; @@ -389,6 +416,15 @@ class BroadcastProcessQueue { if (record.prioritized) { mCountPrioritized++; } + if (record.interactive) { + mCountInteractive++; + } + if (record.resultTo != null) { + mCountResultTo++; + } + if (record.callerInstrumented) { + mCountInstrumented++; + } invalidateRunnableAt(); } @@ -408,6 +444,15 @@ class BroadcastProcessQueue { if (record.prioritized) { mCountPrioritized--; } + if (record.interactive) { + mCountInteractive--; + } + if (record.resultTo != null) { + mCountResultTo--; + } + if (record.callerInstrumented) { + mCountInstrumented--; + } invalidateRunnableAt(); } @@ -553,25 +598,33 @@ class BroadcastProcessQueue { } static final int REASON_EMPTY = 0; - static final int REASON_CONTAINS_FOREGROUND = 1; - static final int REASON_CONTAINS_ORDERED = 2; - static final int REASON_CONTAINS_ALARM = 3; - static final int REASON_CONTAINS_PRIORITIZED = 4; - static final int REASON_CACHED = 5; - static final int REASON_NORMAL = 6; - static final int REASON_MAX_PENDING = 7; - static final int REASON_BLOCKED = 8; + static final int REASON_CACHED = 1; + static final int REASON_NORMAL = 2; + static final int REASON_MAX_PENDING = 3; + static final int REASON_BLOCKED = 4; + static final int REASON_INSTRUMENTED = 5; + static final int REASON_CONTAINS_FOREGROUND = 10; + static final int REASON_CONTAINS_ORDERED = 11; + static final int REASON_CONTAINS_ALARM = 12; + static final int REASON_CONTAINS_PRIORITIZED = 13; + static final int REASON_CONTAINS_INTERACTIVE = 14; + static final int REASON_CONTAINS_RESULT_TO = 15; + static final int REASON_CONTAINS_INSTRUMENTED = 16; @IntDef(flag = false, prefix = { "REASON_" }, value = { REASON_EMPTY, - REASON_CONTAINS_FOREGROUND, - REASON_CONTAINS_ORDERED, - REASON_CONTAINS_ALARM, - REASON_CONTAINS_PRIORITIZED, REASON_CACHED, REASON_NORMAL, REASON_MAX_PENDING, REASON_BLOCKED, + REASON_INSTRUMENTED, + REASON_CONTAINS_FOREGROUND, + REASON_CONTAINS_ORDERED, + REASON_CONTAINS_ALARM, + REASON_CONTAINS_PRIORITIZED, + REASON_CONTAINS_INTERACTIVE, + REASON_CONTAINS_RESULT_TO, + REASON_CONTAINS_INSTRUMENTED, }) @Retention(RetentionPolicy.SOURCE) public @interface Reason {} @@ -579,14 +632,18 @@ class BroadcastProcessQueue { static @NonNull String reasonToString(@Reason int reason) { switch (reason) { case REASON_EMPTY: return "EMPTY"; - case REASON_CONTAINS_FOREGROUND: return "CONTAINS_FOREGROUND"; - case REASON_CONTAINS_ORDERED: return "CONTAINS_ORDERED"; - case REASON_CONTAINS_ALARM: return "CONTAINS_ALARM"; - case REASON_CONTAINS_PRIORITIZED: return "CONTAINS_PRIORITIZED"; case REASON_CACHED: return "CACHED"; case REASON_NORMAL: return "NORMAL"; case REASON_MAX_PENDING: return "MAX_PENDING"; case REASON_BLOCKED: return "BLOCKED"; + case REASON_INSTRUMENTED: return "INSTRUMENTED"; + case REASON_CONTAINS_FOREGROUND: return "CONTAINS_FOREGROUND"; + case REASON_CONTAINS_ORDERED: return "CONTAINS_ORDERED"; + case REASON_CONTAINS_ALARM: return "CONTAINS_ALARM"; + case REASON_CONTAINS_PRIORITIZED: return "CONTAINS_PRIORITIZED"; + case REASON_CONTAINS_INTERACTIVE: return "CONTAINS_INTERACTIVE"; + case REASON_CONTAINS_RESULT_TO: return "CONTAINS_RESULT_TO"; + case REASON_CONTAINS_INSTRUMENTED: return "CONTAINS_INSTRUMENTED"; default: return Integer.toString(reason); } } @@ -631,6 +688,18 @@ class BroadcastProcessQueue { } else if (mCountPrioritized > 0) { mRunnableAt = runnableAt; mRunnableAtReason = REASON_CONTAINS_PRIORITIZED; + } else if (mCountInteractive > 0) { + mRunnableAt = runnableAt; + mRunnableAtReason = REASON_CONTAINS_INTERACTIVE; + } else if (mCountResultTo > 0) { + mRunnableAt = runnableAt; + mRunnableAtReason = REASON_CONTAINS_RESULT_TO; + } else if (mCountInstrumented > 0) { + mRunnableAt = runnableAt; + mRunnableAtReason = REASON_CONTAINS_INSTRUMENTED; + } else if (mProcessInstrumented) { + mRunnableAt = runnableAt; + mRunnableAtReason = REASON_INSTRUMENTED; } else if (mProcessCached) { mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS; mRunnableAtReason = REASON_CACHED; diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index e421c61a2bd6..db3ef3d51b16 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -441,7 +441,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // relevant per-process queue final BroadcastProcessQueue queue = getProcessQueue(app); if (queue != null) { - queue.app = app; + queue.setProcess(app); } boolean didSomething = false; @@ -478,7 +478,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // relevant per-process queue final BroadcastProcessQueue queue = getProcessQueue(app); if (queue != null) { - queue.app = null; + queue.setProcess(null); } if ((mRunningColdStart != null) && (mRunningColdStart == queue)) { @@ -816,19 +816,21 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } final BroadcastRecord r = queue.getActive(); - r.resultCode = resultCode; - r.resultData = resultData; - r.resultExtras = resultExtras; - if (!r.isNoAbort()) { - r.resultAbort = resultAbort; - } - - // When the caller aborted an ordered broadcast, we mark all remaining - // receivers as skipped - if (r.ordered && r.resultAbort) { - for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) { - setDeliveryState(null, null, r, i, r.receivers.get(i), - BroadcastRecord.DELIVERY_SKIPPED); + if (r.ordered) { + r.resultCode = resultCode; + r.resultData = resultData; + r.resultExtras = resultExtras; + if (!r.isNoAbort()) { + r.resultAbort = resultAbort; + } + + // When the caller aborted an ordered broadcast, we mark all + // remaining receivers as skipped + if (r.resultAbort) { + for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) { + setDeliveryState(null, null, r, i, r.receivers.get(i), + BroadcastRecord.DELIVERY_SKIPPED); + } } } @@ -925,7 +927,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { notifyFinishReceiver(queue, r, index, receiver); // When entire ordered broadcast finished, deliver final result - if (r.ordered && (r.terminalCount == r.receivers.size())) { + final boolean recordFinished = (r.terminalCount == r.receivers.size()); + if (recordFinished) { scheduleResultTo(r); } @@ -1217,7 +1220,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) { if (!queue.isProcessWarm()) { - queue.app = mService.getProcessRecordLocked(queue.processName, queue.uid); + queue.setProcess(mService.getProcessRecordLocked(queue.processName, queue.uid)); } } diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 87c87e296538..d7dc8b80931b 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -78,6 +78,7 @@ final class BroadcastRecord extends Binder { final int callingPid; // the pid of who sent this final int callingUid; // the uid of who sent this final boolean callerInstantApp; // caller is an Instant App? + final boolean callerInstrumented; // caller is being instrumented final boolean ordered; // serialize the send to receivers? final boolean sticky; // originated from existing sticky data? final boolean alarm; // originated from an alarm triggering? @@ -365,6 +366,8 @@ final class BroadcastRecord extends Binder { callingPid = _callingPid; callingUid = _callingUid; callerInstantApp = _callerInstantApp; + callerInstrumented = (_callerApp != null) + ? (_callerApp.getActiveInstrumentation() != null) : false; resolvedType = _resolvedType; requiredPermissions = _requiredPermissions; excludedPermissions = _excludedPermissions; @@ -411,6 +414,7 @@ final class BroadcastRecord extends Binder { callingPid = from.callingPid; callingUid = from.callingUid; callerInstantApp = from.callerInstantApp; + callerInstrumented = from.callerInstrumented; ordered = from.ordered; sticky = from.sticky; initialSticky = from.initialSticky; diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java index 1370fd83f6a8..da7781add8c6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java @@ -21,6 +21,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.hardware.biometrics.BiometricConstants; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; @@ -293,4 +294,30 @@ public abstract class BaseClientMonitor implements IBinder.DeathRecipient { + ", requestId=" + getRequestId() + ", userId=" + getTargetUserId() + "}"; } + + /** + * Cancels this ClientMonitor + */ + public void cancel() { + cancelWithoutStarting(mCallback); + } + + /** + * Cancels this ClientMonitor without starting + * @param callback + */ + public void cancelWithoutStarting(@NonNull ClientMonitorCallback callback) { + Slog.d(TAG, "cancelWithoutStarting: " + this); + + final int errorCode = BiometricConstants.BIOMETRIC_ERROR_CANCELED; + try { + ClientMonitorCallbackConverter listener = getListener(); + if (listener != null) { + listener.onError(getSensorId(), getCookie(), errorCode, 0 /* vendorCode */); + } + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke sendError", e); + } + callback.onClientFinished(this, true /* success */); + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 9317c4ec12b5..fb978b2ba4b9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -543,4 +543,37 @@ public class BiometricScheduler { mPendingOperations.clear(); mCurrentOperation = null; } + + /** + * Marks all pending operations as canceling and cancels the current + * operation. + */ + private void clearScheduler() { + if (mCurrentOperation == null) { + return; + } + for (BiometricSchedulerOperation pendingOperation : mPendingOperations) { + Slog.d(getTag(), "[Watchdog cancelling pending] " + + pendingOperation.getClientMonitor()); + pendingOperation.markCanceling(); + } + Slog.d(getTag(), "[Watchdog cancelling current] " + + mCurrentOperation.getClientMonitor()); + mCurrentOperation.cancel(mHandler, getInternalCallback()); + } + + /** + * Start the timeout for the watchdog. + */ + public void startWatchdog() { + if (mCurrentOperation == null) { + return; + } + final BiometricSchedulerOperation mOperation = mCurrentOperation; + mHandler.postDelayed(() -> { + if (mOperation == mCurrentOperation) { + clearScheduler(); + } + }, 10000); + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java index ef2931ff5850..dacec38b0e7e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java @@ -267,7 +267,7 @@ public class BiometricSchedulerOperation { /** Flags this operation as canceled, if possible, but does not cancel it until started. */ public boolean markCanceling() { - if (mState == STATE_WAITING_IN_QUEUE && isInterruptable()) { + if (mState == STATE_WAITING_IN_QUEUE) { mState = STATE_WAITING_IN_QUEUE_CANCELING; return true; } @@ -287,10 +287,6 @@ public class BiometricSchedulerOperation { } final int currentState = mState; - if (!isInterruptable()) { - Slog.w(TAG, "Cannot cancel - operation not interruptable: " + this); - return; - } if (currentState == STATE_STARTED_CANCELING) { Slog.w(TAG, "Cannot cancel - already invoked for operation: " + this); return; @@ -301,10 +297,10 @@ public class BiometricSchedulerOperation { || currentState == STATE_WAITING_IN_QUEUE_CANCELING || currentState == STATE_WAITING_FOR_COOKIE) { Slog.d(TAG, "[Cancelling] Current client (without start): " + mClientMonitor); - ((Interruptable) mClientMonitor).cancelWithoutStarting(getWrappedCallback(callback)); + mClientMonitor.cancelWithoutStarting(getWrappedCallback(callback)); } else { Slog.d(TAG, "[Cancelling] Current client: " + mClientMonitor); - ((Interruptable) mClientMonitor).cancel(); + mClientMonitor.cancel(); } // forcibly finish this client if the HAL does not acknowledge within the timeout diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 271bce9890c6..2761ec04aa7e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -183,6 +183,18 @@ public class FaceService extends SystemService { receiver, opPackageName, disabledFeatures, previewSurface, debugConsent); } + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override + public void scheduleWatchdog() { + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for scheduling watchdog"); + return; + } + + provider.second.scheduleWatchdog(provider.first); + } + @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC) @Override // Binder call public long enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java index 4efaedbd5530..85f95cec8377 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java @@ -128,4 +128,10 @@ public interface ServiceProvider extends BiometricServiceProvider<FaceSensorProp @NonNull String opPackageName); void dumpHal(int sensorId, @NonNull FileDescriptor fd, @NonNull String[] args); + + /** + * Schedules watchdog for canceling hung operations + * @param sensorId sensor ID of the associated operation + */ + default void scheduleWatchdog(int sensorId) {} } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index b60f9d80d425..c12994c993e6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -52,6 +52,7 @@ import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.InvalidationRequesterClient; @@ -661,4 +662,14 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { void setTestHalEnabled(boolean enabled) { mTestHalEnabled = enabled; } + + @Override + public void scheduleWatchdog(int sensorId) { + Slog.d(getTag(), "Starting watchdog for face"); + final BiometricScheduler biometricScheduler = mSensors.get(sensorId).getScheduler(); + if (biometricScheduler == null) { + return; + } + biometricScheduler.startWatchdog(); + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 7e2742edd47a..b0dc28ddce96 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -879,6 +879,18 @@ public class FingerprintService extends SystemService { provider.onPowerPressed(); } } + + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override + public void scheduleWatchdog() { + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); + if (provider == null) { + Slog.w(TAG, "Null provider for scheduling watchdog"); + return; + } + + provider.second.scheduleWatchdog(provider.first); + } }; public FingerprintService(Context context) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index 9075e7ec2080..0c29f5615b4c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -140,4 +140,10 @@ public interface ServiceProvider extends @NonNull ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, @NonNull String opPackageName); + + /** + * Schedules watchdog for canceling hung operations + * @param sensorId sensor ID of the associated operation + */ + default void scheduleWatchdog(int sensorId) {} } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 650894db431a..17ba07f2c2bd 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -59,6 +59,7 @@ import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -779,4 +780,14 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } return null; } + + @Override + public void scheduleWatchdog(int sensorId) { + Slog.d(getTag(), "Starting watchdog for fingerprint"); + final BiometricScheduler biometricScheduler = mSensors.get(sensorId).getScheduler(); + if (biometricScheduler == null) { + return; + } + biometricScheduler.startWatchdog(); + } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 19069ea891a4..5eb15e09f09e 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1356,11 +1356,19 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { - final int displayId = createVirtualDisplayLocked(callback, projection, callingUid, - packageName, surface, flags, virtualDisplayConfig); + final int displayId = + createVirtualDisplayLocked( + callback, + projection, + callingUid, + packageName, + virtualDevice, + surface, + flags, + virtualDisplayConfig); if (displayId != Display.INVALID_DISPLAY && virtualDevice != null && dwpc != null) { - mDisplayWindowPolicyControllers.put(displayId, - Pair.create(virtualDevice, dwpc)); + mDisplayWindowPolicyControllers.put( + displayId, Pair.create(virtualDevice, dwpc)); } return displayId; } @@ -1369,12 +1377,20 @@ public final class DisplayManagerService extends SystemService { } } - private int createVirtualDisplayLocked(IVirtualDisplayCallback callback, - IMediaProjection projection, int callingUid, String packageName, Surface surface, - int flags, VirtualDisplayConfig virtualDisplayConfig) { + private int createVirtualDisplayLocked( + IVirtualDisplayCallback callback, + IMediaProjection projection, + int callingUid, + String packageName, + IVirtualDevice virtualDevice, + Surface surface, + int flags, + VirtualDisplayConfig virtualDisplayConfig) { if (mVirtualDisplayAdapter == null) { - Slog.w(TAG, "Rejecting request to create private virtual display " - + "because the virtual display adapter is not available."); + Slog.w( + TAG, + "Rejecting request to create private virtual display " + + "because the virtual display adapter is not available."); return -1; } @@ -1385,6 +1401,19 @@ public final class DisplayManagerService extends SystemService { return -1; } + // If the display is to be added to a device display group, we need to make the + // LogicalDisplayMapper aware of the link between the new display and its associated virtual + // device before triggering DISPLAY_DEVICE_EVENT_ADDED. + if (virtualDevice != null && (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) == 0) { + try { + final int virtualDeviceId = virtualDevice.getDeviceId(); + mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice( + device, virtualDeviceId); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + // DisplayDevice events are handled manually for Virtual Displays. // TODO: multi-display Fix this so that generic add/remove events are not handled in a // different code path for virtual displays. Currently this happens so that we can @@ -1393,8 +1422,7 @@ public final class DisplayManagerService extends SystemService { // called on the DisplayThread (which we don't want to wait for?). // One option would be to actually wait here on the binder thread // to be notified when the virtual display is created (or failed). - mDisplayDeviceRepo.onDisplayDeviceEvent(device, - DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED); + mDisplayDeviceRepo.onDisplayDeviceEvent(device, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED); final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); if (display != null) { diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 70c9e23c6af8..cb97e2832854 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -28,6 +28,7 @@ import android.os.PowerManager; import android.os.SystemClock; import android.os.SystemProperties; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -123,6 +124,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { /** Map of all display groups indexed by display group id. */ private final SparseArray<DisplayGroup> mDisplayGroups = new SparseArray<>(); + /** + * Map of display groups which are linked to virtual devices (all displays in the group are + * linked to that device). Keyed by virtual device unique id. + */ + private final SparseIntArray mDeviceDisplayGroupIds = new SparseIntArray(); + private final DisplayDeviceRepository mDisplayDeviceRepo; private final DeviceStateToLayoutMap mDeviceStateToLayoutMap; private final Listener mListener; @@ -157,6 +164,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { */ private final SparseIntArray mDisplayGroupsToUpdate = new SparseIntArray(); + /** + * ArrayMap of display device unique ID to virtual device ID. Used in {@link + * #updateLogicalDisplaysLocked} to establish which Virtual Devices own which Virtual Displays. + */ + private final ArrayMap<String, Integer> mVirtualDeviceDisplayMapping = new ArrayMap<>(); + private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; private Layout mCurrentLayout = null; private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; @@ -362,6 +375,19 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mDeviceStateToLayoutMap.dumpLocked(ipw); } + /** + * Creates an association between a displayDevice and a virtual device. Any displays associated + * with this virtual device will be grouped together in a single {@link DisplayGroup} unless + * created with {@link Display.FLAG_OWN_DISPLAY_GROUP}. + * + * @param displayDevice the displayDevice to be linked + * @param virtualDeviceUniqueId the unique ID of the virtual device. + */ + void associateDisplayDeviceWithVirtualDevice( + DisplayDevice displayDevice, int virtualDeviceUniqueId) { + mVirtualDeviceDisplayMapping.put(displayDevice.getUniqueId(), virtualDeviceUniqueId); + } + void setDeviceStateLocked(int state, boolean isOverrideActive) { Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState + ", interactive=" + mInteractive); @@ -556,6 +582,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked(); + // Remove any virtual device mapping which exists for the display. + mVirtualDeviceDisplayMapping.remove(device.getUniqueId()); + if (layoutDisplay.getAddress().equals(deviceInfo.address)) { layout.removeDisplayLocked(DEFAULT_DISPLAY); @@ -749,24 +778,44 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // We wait until we sent the EVENT_REMOVED event before actually removing the // group. mDisplayGroups.delete(id); + // Remove possible reference to the removed group. + int deviceIndex = mDeviceDisplayGroupIds.indexOfValue(id); + if (deviceIndex >= 0) { + mDeviceDisplayGroupIds.removeAt(deviceIndex); + } } } } private void assignDisplayGroupLocked(LogicalDisplay display) { final int displayId = display.getDisplayIdLocked(); + final String primaryDisplayUniqueId = display.getPrimaryDisplayDeviceLocked().getUniqueId(); + final Integer linkedDeviceUniqueId = + mVirtualDeviceDisplayMapping.get(primaryDisplayUniqueId); // Get current display group data int groupId = getDisplayGroupIdFromDisplayIdLocked(displayId); + Integer deviceDisplayGroupId = null; + if (linkedDeviceUniqueId != null + && mDeviceDisplayGroupIds.indexOfKey(linkedDeviceUniqueId) > 0) { + deviceDisplayGroupId = mDeviceDisplayGroupIds.get(linkedDeviceUniqueId); + } final DisplayGroup oldGroup = getDisplayGroupLocked(groupId); // Get the new display group if a change is needed final DisplayInfo info = display.getDisplayInfoLocked(); final boolean needsOwnDisplayGroup = (info.flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0; final boolean hasOwnDisplayGroup = groupId != Display.DEFAULT_DISPLAY_GROUP; + final boolean needsDeviceDisplayGroup = + !needsOwnDisplayGroup && linkedDeviceUniqueId != null; + final boolean hasDeviceDisplayGroup = + deviceDisplayGroupId != null && groupId == deviceDisplayGroupId; if (groupId == Display.INVALID_DISPLAY_GROUP - || hasOwnDisplayGroup != needsOwnDisplayGroup) { - groupId = assignDisplayGroupIdLocked(needsOwnDisplayGroup); + || hasOwnDisplayGroup != needsOwnDisplayGroup + || hasDeviceDisplayGroup != needsDeviceDisplayGroup) { + groupId = + assignDisplayGroupIdLocked( + needsOwnDisplayGroup, needsDeviceDisplayGroup, linkedDeviceUniqueId); } // Create a new group if needed @@ -931,7 +980,17 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { display.setPhase(phase); } - private int assignDisplayGroupIdLocked(boolean isOwnDisplayGroup) { + private int assignDisplayGroupIdLocked( + boolean isOwnDisplayGroup, boolean isDeviceDisplayGroup, Integer linkedDeviceUniqueId) { + if (isDeviceDisplayGroup && linkedDeviceUniqueId != null) { + int deviceDisplayGroupId = mDeviceDisplayGroupIds.get(linkedDeviceUniqueId); + // A value of 0 indicates that no device display group was found. + if (deviceDisplayGroupId == 0) { + deviceDisplayGroupId = mNextNonDefaultGroupId++; + mDeviceDisplayGroupIds.put(linkedDeviceUniqueId, deviceDisplayGroupId); + } + return deviceDisplayGroupId; + } return isOwnDisplayGroup ? mNextNonDefaultGroupId++ : Display.DEFAULT_DISPLAY_GROUP; } diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index 819b719dd22e..cd9ef0915741 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -42,6 +42,8 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Iterator; import java.util.NoSuchElementException; /** @@ -62,8 +64,6 @@ final class DreamController { private final Handler mHandler; private final Listener mListener; private final ActivityTaskManager mActivityTaskManager; - private long mDreamStartTime; - private String mSavedStopReason; private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); @@ -74,21 +74,15 @@ final class DreamController { private DreamRecord mCurrentDream; - private final Runnable mStopUnconnectedDreamRunnable = new Runnable() { - @Override - public void run() { - if (mCurrentDream != null && mCurrentDream.mBound && !mCurrentDream.mConnected) { - Slog.w(TAG, "Bound dream did not connect in the time allotted"); - stopDream(true /*immediate*/, "slow to connect"); - } - } - }; + // Whether a dreaming started intent has been broadcast. + private boolean mSentStartBroadcast = false; - private final Runnable mStopStubbornDreamRunnable = () -> { - Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted"); - stopDream(true /*immediate*/, "slow to finish"); - mSavedStopReason = null; - }; + // When a new dream is started and there is an existing dream, the existing dream is allowed to + // live a little longer until the new dream is started, for a smoother transition. This dream is + // stopped as soon as the new dream is started, and this list is cleared. Usually there should + // only be one previous dream while waiting for a new dream to start, but we store a list to + // proof the edge case of multiple previous dreams. + private final ArrayList<DreamRecord> mPreviousDreams = new ArrayList<>(); public DreamController(Context context, Handler handler, Listener listener) { mContext = context; @@ -110,18 +104,17 @@ final class DreamController { pw.println(" mUserId=" + mCurrentDream.mUserId); pw.println(" mBound=" + mCurrentDream.mBound); pw.println(" mService=" + mCurrentDream.mService); - pw.println(" mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast); pw.println(" mWakingGently=" + mCurrentDream.mWakingGently); } else { pw.println(" mCurrentDream: null"); } + + pw.println(" mSentStartBroadcast=" + mSentStartBroadcast); } public void startDream(Binder token, ComponentName name, boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock, ComponentName overlayComponentName, String reason) { - stopDream(true /*immediate*/, "starting new dream"); - Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream"); try { // Close the notification shade. No need to send to all, but better to be explicit. @@ -131,9 +124,12 @@ final class DreamController { + ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze + ", userId=" + userId + ", reason='" + reason + "'"); + if (mCurrentDream != null) { + mPreviousDreams.add(mCurrentDream); + } mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock); - mDreamStartTime = SystemClock.elapsedRealtime(); + mCurrentDream.mDreamStartTime = SystemClock.elapsedRealtime(); MetricsLogger.visible(mContext, mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); @@ -156,31 +152,49 @@ final class DreamController { } mCurrentDream.mBound = true; - mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT); + mHandler.postDelayed(mCurrentDream.mStopUnconnectedDreamRunnable, + DREAM_CONNECTION_TIMEOUT); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } } + /** + * Stops dreaming. + * + * The current dream, if any, and any unstopped previous dreams are stopped. The device stops + * dreaming. + */ public void stopDream(boolean immediate, String reason) { - if (mCurrentDream == null) { + stopPreviousDreams(); + stopDreamInstance(immediate, reason, mCurrentDream); + } + + /** + * Stops the given dream instance. + * + * The device may still be dreaming afterwards if there are other dreams running. + */ + private void stopDreamInstance(boolean immediate, String reason, DreamRecord dream) { + if (dream == null) { return; } Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream"); try { if (!immediate) { - if (mCurrentDream.mWakingGently) { + if (dream.mWakingGently) { return; // already waking gently } - if (mCurrentDream.mService != null) { + if (dream.mService != null) { // Give the dream a moment to wake up and finish itself gently. - mCurrentDream.mWakingGently = true; + dream.mWakingGently = true; try { - mSavedStopReason = reason; - mCurrentDream.mService.wakeUp(); - mHandler.postDelayed(mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT); + dream.mStopReason = reason; + dream.mService.wakeUp(); + mHandler.postDelayed(dream.mStopStubbornDreamRunnable, + DREAM_FINISH_TIMEOUT); return; } catch (RemoteException ex) { // oh well, we tried, finish immediately instead @@ -188,56 +202,76 @@ final class DreamController { } } - final DreamRecord oldDream = mCurrentDream; - mCurrentDream = null; - Slog.i(TAG, "Stopping dream: name=" + oldDream.mName - + ", isPreviewMode=" + oldDream.mIsPreviewMode - + ", canDoze=" + oldDream.mCanDoze - + ", userId=" + oldDream.mUserId + Slog.i(TAG, "Stopping dream: name=" + dream.mName + + ", isPreviewMode=" + dream.mIsPreviewMode + + ", canDoze=" + dream.mCanDoze + + ", userId=" + dream.mUserId + ", reason='" + reason + "'" - + (mSavedStopReason == null ? "" : "(from '" + mSavedStopReason + "')")); + + (dream.mStopReason == null ? "" : "(from '" + + dream.mStopReason + "')")); MetricsLogger.hidden(mContext, - oldDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); + dream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); MetricsLogger.histogram(mContext, - oldDream.mCanDoze ? "dozing_minutes" : "dreaming_minutes" , - (int) ((SystemClock.elapsedRealtime() - mDreamStartTime) / (1000L * 60L))); + dream.mCanDoze ? "dozing_minutes" : "dreaming_minutes", + (int) ((SystemClock.elapsedRealtime() - dream.mDreamStartTime) / (1000L + * 60L))); - mHandler.removeCallbacks(mStopUnconnectedDreamRunnable); - mHandler.removeCallbacks(mStopStubbornDreamRunnable); - mSavedStopReason = null; + mHandler.removeCallbacks(dream.mStopUnconnectedDreamRunnable); + mHandler.removeCallbacks(dream.mStopStubbornDreamRunnable); - if (oldDream.mSentStartBroadcast) { - mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL); - } - - if (oldDream.mService != null) { + if (dream.mService != null) { try { - oldDream.mService.detach(); + dream.mService.detach(); } catch (RemoteException ex) { // we don't care; this thing is on the way out } try { - oldDream.mService.asBinder().unlinkToDeath(oldDream, 0); + dream.mService.asBinder().unlinkToDeath(dream, 0); } catch (NoSuchElementException ex) { // don't care } - oldDream.mService = null; + dream.mService = null; } - if (oldDream.mBound) { - mContext.unbindService(oldDream); + if (dream.mBound) { + mContext.unbindService(dream); } - oldDream.releaseWakeLockIfNeeded(); + dream.releaseWakeLockIfNeeded(); + + // Current dream stopped, device no longer dreaming. + if (dream == mCurrentDream) { + mCurrentDream = null; + + if (mSentStartBroadcast) { + mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL); + } - mActivityTaskManager.removeRootTasksWithActivityTypes(new int[] {ACTIVITY_TYPE_DREAM}); + mActivityTaskManager.removeRootTasksWithActivityTypes( + new int[] {ACTIVITY_TYPE_DREAM}); - mHandler.post(() -> mListener.onDreamStopped(oldDream.mToken)); + mListener.onDreamStopped(dream.mToken); + } } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } } + /** + * Stops all previous dreams, if any. + */ + private void stopPreviousDreams() { + if (mPreviousDreams.isEmpty()) { + return; + } + + // Using an iterator because mPreviousDreams is modified while the iteration is in process. + for (final Iterator<DreamRecord> it = mPreviousDreams.iterator(); it.hasNext(); ) { + stopDreamInstance(true /*immediate*/, "stop previous dream", it.next()); + it.remove(); + } + } + private void attach(IDreamService service) { try { service.asBinder().linkToDeath(mCurrentDream, 0); @@ -251,9 +285,9 @@ final class DreamController { mCurrentDream.mService = service; - if (!mCurrentDream.mIsPreviewMode) { + if (!mCurrentDream.mIsPreviewMode && !mSentStartBroadcast) { mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL); - mCurrentDream.mSentStartBroadcast = true; + mSentStartBroadcast = true; } } @@ -275,10 +309,35 @@ final class DreamController { public boolean mBound; public boolean mConnected; public IDreamService mService; - public boolean mSentStartBroadcast; - + private String mStopReason; + private long mDreamStartTime; public boolean mWakingGently; + private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded; + private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded; + + private final Runnable mStopUnconnectedDreamRunnable = () -> { + if (mBound && !mConnected) { + Slog.w(TAG, "Bound dream did not connect in the time allotted"); + stopDream(true /*immediate*/, "slow to connect" /*reason*/); + } + }; + + private final Runnable mStopStubbornDreamRunnable = () -> { + Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted"); + stopDream(true /*immediate*/, "slow to finish" /*reason*/); + mStopReason = null; + }; + + private final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() { + // May be called on any thread. + @Override + public void sendResult(Bundle data) { + mHandler.post(mStopPreviousDreamsIfNeeded); + mHandler.post(mReleaseWakeLockIfNeeded); + } + }; + DreamRecord(Binder token, ComponentName name, boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) { mToken = token; @@ -289,7 +348,9 @@ final class DreamController { mWakeLock = wakeLock; // Hold the lock while we're waiting for the service to connect and start dreaming. // Released after the service has started dreaming, we stop dreaming, or it timed out. - mWakeLock.acquire(); + if (mWakeLock != null) { + mWakeLock.acquire(); + } mHandler.postDelayed(mReleaseWakeLockIfNeeded, 10000); } @@ -329,6 +390,12 @@ final class DreamController { }); } + void stopPreviousDreamsIfNeeded() { + if (mCurrentDream == DreamRecord.this) { + stopPreviousDreams(); + } + } + void releaseWakeLockIfNeeded() { if (mWakeLock != null) { mWakeLock.release(); @@ -336,15 +403,5 @@ final class DreamController { mHandler.removeCallbacks(mReleaseWakeLockIfNeeded); } } - - final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded; - - final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() { - // May be called on any thread. - @Override - public void sendResult(Bundle data) throws RemoteException { - mHandler.post(mReleaseWakeLockIfNeeded); - } - }; } } diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 951a8a249f87..6e2ccebb6ff4 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -493,8 +493,6 @@ public final class DreamManagerService extends SystemService { return; } - stopDreamLocked(true /*immediate*/, "starting new dream"); - Slog.i(TAG, "Entering dreamland."); mCurrentDream = new DreamRecord(name, userId, isPreviewMode, canDoze); diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java index 324eefc809e8..06ee7c4d6c28 100644 --- a/services/core/java/com/android/server/input/BatteryController.java +++ b/services/core/java/com/android/server/input/BatteryController.java @@ -44,6 +44,8 @@ import java.io.PrintWriter; import java.util.Arrays; import java.util.Objects; import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; /** * A thread-safe component of {@link InputManagerService} responsible for managing the battery state @@ -98,8 +100,12 @@ final class BatteryController { } public void systemRunning() { - Objects.requireNonNull(mContext.getSystemService(InputManager.class)) - .registerInputDeviceListener(mInputDeviceListener, mHandler); + final InputManager inputManager = + Objects.requireNonNull(mContext.getSystemService(InputManager.class)); + inputManager.registerInputDeviceListener(mInputDeviceListener, mHandler); + for (int deviceId : inputManager.getInputDeviceIds()) { + mInputDeviceListener.onInputDeviceAdded(deviceId); + } } /** @@ -165,19 +171,20 @@ final class BatteryController { } } - @GuardedBy("mLock") - private void notifyAllListenersForDeviceLocked(State state) { - if (DEBUG) Slog.d(TAG, "Notifying all listeners of battery state: " + state); - mListenerRecords.forEach((pid, listenerRecord) -> { - if (listenerRecord.mMonitoredDevices.contains(state.deviceId)) { - notifyBatteryListener(listenerRecord, state); - } - }); + private void notifyAllListenersForDevice(State state) { + synchronized (mLock) { + if (DEBUG) Slog.d(TAG, "Notifying all listeners of battery state: " + state); + mListenerRecords.forEach((pid, listenerRecord) -> { + if (listenerRecord.mMonitoredDevices.contains(state.deviceId)) { + notifyBatteryListener(listenerRecord, state); + } + }); + } } @GuardedBy("mLock") private void updatePollingLocked(boolean delayStart) { - if (mDeviceMonitors.isEmpty() || !mIsInteractive) { + if (!mIsInteractive || !anyOf(mDeviceMonitors, DeviceMonitor::requiresPolling)) { // Stop polling. mIsPolling = false; mHandler.removeCallbacks(this::handlePollEvent); @@ -192,6 +199,13 @@ final class BatteryController { mHandler.postDelayed(this::handlePollEvent, delayStart ? POLLING_PERIOD_MILLIS : 0); } + private String getInputDeviceName(int deviceId) { + final InputDevice device = + Objects.requireNonNull(mContext.getSystemService(InputManager.class)) + .getInputDevice(deviceId); + return device != null ? device.getName() : "<none>"; + } + private boolean hasBattery(int deviceId) { final InputDevice device = Objects.requireNonNull(mContext.getSystemService(InputManager.class)) @@ -199,6 +213,13 @@ final class BatteryController { return device != null && device.hasBattery(); } + private boolean isUsiDevice(int deviceId) { + final InputDevice device = + Objects.requireNonNull(mContext.getSystemService(InputManager.class)) + .getInputDevice(deviceId); + return device != null && device.supportsUsi(); + } + @GuardedBy("mLock") private DeviceMonitor getDeviceMonitorOrThrowLocked(int deviceId) { return Objects.requireNonNull(mDeviceMonitors.get(deviceId), @@ -252,8 +273,10 @@ final class BatteryController { if (!hasRegisteredListenerForDeviceLocked(deviceId)) { // There are no more listeners monitoring this device. final DeviceMonitor monitor = getDeviceMonitorOrThrowLocked(deviceId); - monitor.stopMonitoring(); - mDeviceMonitors.remove(deviceId); + if (!monitor.isPersistent()) { + monitor.onMonitorDestroy(); + mDeviceMonitors.remove(deviceId); + } } if (listenerRecord.mMonitoredDevices.isEmpty()) { @@ -298,9 +321,7 @@ final class BatteryController { if (monitor == null) { return; } - if (monitor.updateBatteryState(eventTime)) { - notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting()); - } + monitor.onUEvent(eventTime); } } @@ -310,14 +331,7 @@ final class BatteryController { return; } final long eventTime = SystemClock.uptimeMillis(); - mDeviceMonitors.forEach((deviceId, monitor) -> { - // Re-acquire lock in the lambda to silence error-prone build warnings. - synchronized (mLock) { - if (monitor.updateBatteryState(eventTime)) { - notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting()); - } - } - }); + mDeviceMonitors.forEach((deviceId, monitor) -> monitor.onPoll(eventTime)); mHandler.postDelayed(this::handlePollEvent, POLLING_PERIOD_MILLIS); } } @@ -329,15 +343,11 @@ final class BatteryController { final DeviceMonitor monitor = mDeviceMonitors.get(deviceId); if (monitor == null) { // The input device's battery is not being monitored by any listener. - return queryBatteryStateFromNative(deviceId, updateTime); + return queryBatteryStateFromNative(deviceId, updateTime, hasBattery(deviceId)); } // Force the battery state to update, and notify listeners if necessary. - final boolean stateChanged = monitor.updateBatteryState(updateTime); - final State state = monitor.getBatteryStateForReporting(); - if (stateChanged) { - notifyAllListenersForDeviceLocked(state); - } - return state; + monitor.onPoll(updateTime); + return monitor.getBatteryStateForReporting(); } } @@ -379,7 +389,14 @@ final class BatteryController { private final InputManager.InputDeviceListener mInputDeviceListener = new InputManager.InputDeviceListener() { @Override - public void onInputDeviceAdded(int deviceId) {} + public void onInputDeviceAdded(int deviceId) { + synchronized (mLock) { + if (isUsiDevice(deviceId) && !mDeviceMonitors.containsKey(deviceId)) { + // Start monitoring USI device immediately. + mDeviceMonitors.put(deviceId, new UsiDeviceMonitor(deviceId)); + } + } + } @Override public void onInputDeviceRemoved(int deviceId) {} @@ -392,9 +409,7 @@ final class BatteryController { return; } final long eventTime = SystemClock.uptimeMillis(); - if (monitor.updateBatteryState(eventTime)) { - notifyAllListenersForDeviceLocked(monitor.getBatteryStateForReporting()); - } + monitor.onConfiguration(eventTime); } } }; @@ -422,8 +437,7 @@ final class BatteryController { } // Queries the battery state of an input device from native code. - private State queryBatteryStateFromNative(int deviceId, long updateTime) { - final boolean isPresent = hasBattery(deviceId); + private State queryBatteryStateFromNative(int deviceId, long updateTime, boolean isPresent) { return new State( deviceId, updateTime, @@ -434,8 +448,9 @@ final class BatteryController { // Holds the state of an InputDevice for which battery changes are currently being monitored. private class DeviceMonitor { - @NonNull - private State mState; + private final State mState; + // Represents whether the input device has a sysfs battery node. + protected boolean mHasBattery = false; @Nullable private UEventBatteryListener mUEventBatteryListener; @@ -445,26 +460,32 @@ final class BatteryController { // Load the initial battery state and start monitoring. final long eventTime = SystemClock.uptimeMillis(); - updateBatteryState(eventTime); + configureDeviceMonitor(eventTime); } - // Returns true if the battery state changed since the last time it was updated. - public boolean updateBatteryState(long updateTime) { - mState.updateTime = updateTime; - - final State updatedState = queryBatteryStateFromNative(mState.deviceId, updateTime); - if (mState.equals(updatedState)) { - return false; + private void processChangesAndNotify(long eventTime, Consumer<Long> changes) { + final State oldState = getBatteryStateForReporting(); + changes.accept(eventTime); + final State newState = getBatteryStateForReporting(); + if (!oldState.equals(newState)) { + notifyAllListenersForDevice(newState); } - if (mState.isPresent != updatedState.isPresent) { - if (updatedState.isPresent) { + } + + public void onConfiguration(long eventTime) { + processChangesAndNotify(eventTime, this::configureDeviceMonitor); + } + + private void configureDeviceMonitor(long eventTime) { + if (mHasBattery != hasBattery(mState.deviceId)) { + mHasBattery = !mHasBattery; + if (mHasBattery) { startMonitoring(); } else { stopMonitoring(); } + updateBatteryStateFromNative(eventTime); } - mState = updatedState; - return true; } private void startMonitoring() { @@ -483,19 +504,44 @@ final class BatteryController { mUEventBatteryListener, "DEVPATH=" + formatDevPath(batteryPath)); } - private String formatDevPath(String path) { + private String formatDevPath(@NonNull String path) { // Remove the "/sys" prefix if it has one. return path.startsWith("/sys") ? path.substring(4) : path; } - // This must be called when the device is no longer being monitored. - public void stopMonitoring() { + private void stopMonitoring() { if (mUEventBatteryListener != null) { mUEventManager.removeListener(mUEventBatteryListener); mUEventBatteryListener = null; } } + // This must be called when the device is no longer being monitored. + public void onMonitorDestroy() { + stopMonitoring(); + } + + private void updateBatteryStateFromNative(long eventTime) { + mState.updateIfChanged( + queryBatteryStateFromNative(mState.deviceId, eventTime, mHasBattery)); + } + + public void onPoll(long eventTime) { + processChangesAndNotify(eventTime, this::updateBatteryStateFromNative); + } + + public void onUEvent(long eventTime) { + processChangesAndNotify(eventTime, this::updateBatteryStateFromNative); + } + + public boolean requiresPolling() { + return true; + } + + public boolean isPersistent() { + return false; + } + // Returns the current battery state that can be used to notify listeners BatteryController. public State getBatteryStateForReporting() { return new State(mState); @@ -503,8 +549,31 @@ final class BatteryController { @Override public String toString() { - return "state=" + mState - + ", uEventListener=" + (mUEventBatteryListener != null ? "added" : "none"); + return "DeviceId=" + mState.deviceId + + ", Name='" + getInputDeviceName(mState.deviceId) + "'" + + ", NativeBattery=" + mState + + ", UEventListener=" + (mUEventBatteryListener != null ? "added" : "none"); + } + } + + // Battery monitoring logic that is specific to stylus devices that support the + // Universal Stylus Initiative (USI) protocol. + private class UsiDeviceMonitor extends DeviceMonitor { + + UsiDeviceMonitor(int deviceId) { + super(deviceId); + } + + @Override + public boolean requiresPolling() { + // Do not poll the battery state for USI devices. + return false; + } + + @Override + public boolean isPersistent() { + // Do not remove the battery monitor for USI devices. + return true; } } @@ -548,18 +617,33 @@ final class BatteryController { private static class State extends IInputDeviceBatteryState { State(int deviceId) { - initialize(deviceId, 0 /*updateTime*/, false /*isPresent*/, BatteryState.STATUS_UNKNOWN, - Float.NaN /*capacity*/); + reset(deviceId); } State(IInputDeviceBatteryState s) { - initialize(s.deviceId, s.updateTime, s.isPresent, s.status, s.capacity); + copyFrom(s); } State(int deviceId, long updateTime, boolean isPresent, int status, float capacity) { initialize(deviceId, updateTime, isPresent, status, capacity); } + // Updates this from other if there is a difference between them, ignoring the updateTime. + public void updateIfChanged(IInputDeviceBatteryState other) { + if (!equalsIgnoringUpdateTime(other)) { + copyFrom(other); + } + } + + private void reset(int deviceId) { + initialize(deviceId, 0 /*updateTime*/, false /*isPresent*/, BatteryState.STATUS_UNKNOWN, + Float.NaN /*capacity*/); + } + + private void copyFrom(IInputDeviceBatteryState s) { + initialize(s.deviceId, s.updateTime, s.isPresent, s.status, s.capacity); + } + private void initialize(int deviceId, long updateTime, boolean isPresent, int status, float capacity) { this.deviceId = deviceId; @@ -569,11 +653,34 @@ final class BatteryController { this.capacity = capacity; } + private boolean equalsIgnoringUpdateTime(IInputDeviceBatteryState other) { + long updateTime = this.updateTime; + this.updateTime = other.updateTime; + boolean eq = this.equals(other); + this.updateTime = updateTime; + return eq; + } + @Override public String toString() { - return "BatteryState{deviceId=" + deviceId + ", updateTime=" + updateTime - + ", isPresent=" + isPresent + ", status=" + status + ", capacity=" + capacity - + " }"; + if (!isPresent) { + return "State{<not present>}"; + } + return "State{time=" + updateTime + + ", isPresent=" + isPresent + + ", status=" + status + + ", capacity=" + capacity + + "}"; + } + } + + // Check if any value in an ArrayMap matches the predicate in an optimized way. + private static <K, V> boolean anyOf(ArrayMap<K, V> arrayMap, Predicate<V> test) { + for (int i = 0; i < arrayMap.size(); i++) { + if (test.test(arrayMap.valueAt(i))) { + return true; + } } + return false; } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index d39b64936bb3..a6fac4d60fe7 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -114,6 +114,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Rect; +import android.hardware.SensorPrivacyManager; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.hardware.hdmi.HdmiAudioSystemClient; @@ -391,6 +392,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { IStatusBarService mStatusBarService; StatusBarManagerInternal mStatusBarManagerInternal; AudioManagerInternal mAudioManagerInternal; + SensorPrivacyManager mSensorPrivacyManager; DisplayManager mDisplayManager; DisplayManagerInternal mDisplayManagerInternal; boolean mPreloadedRecentApps; @@ -1912,6 +1914,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + mSensorPrivacyManager = mContext.getSystemService(SensorPrivacyManager.class); mDisplayManager = mContext.getSystemService(DisplayManager.class); mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mPackageManager = mContext.getPackageManager(); @@ -3079,6 +3082,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { return key_not_consumed; } + private void toggleMicrophoneMuteFromKey() { + if (mSensorPrivacyManager.supportsSensorToggle( + SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, + SensorPrivacyManager.Sensors.MICROPHONE)) { + boolean isEnabled = mSensorPrivacyManager.isSensorPrivacyEnabled( + SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, + SensorPrivacyManager.Sensors.MICROPHONE); + mSensorPrivacyManager.setSensorPrivacy(SensorPrivacyManager.Sensors.MICROPHONE, + !isEnabled); + } + } + /** * TV only: recognizes a remote control gesture for capturing a bug report. */ @@ -4011,11 +4026,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; } + case KeyEvent.KEYCODE_MUTE: + result &= ~ACTION_PASS_TO_USER; + if (down && event.getRepeatCount() == 0) { + toggleMicrophoneMuteFromKey(); + } + break; case KeyEvent.KEYCODE_MEDIA_PLAY: case KeyEvent.KEYCODE_MEDIA_PAUSE: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_HEADSETHOOK: - case KeyEvent.KEYCODE_MUTE: case KeyEvent.KEYCODE_MEDIA_STOP: case KeyEvent.KEYCODE_MEDIA_NEXT: case KeyEvent.KEYCODE_MEDIA_PREVIOUS: @@ -4193,7 +4213,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mRequestedOrSleepingDefaultDisplay) { mCameraGestureTriggeredDuringGoingToSleep = true; // Wake device up early to prevent display doing redundant turning off/on stuff. - wakeUpFromPowerKey(event.getDownTime()); + wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromPowerKey, + PowerManager.WAKE_REASON_CAMERA_LAUNCH, + "android.policy:CAMERA_GESTURE_PREVENT_LOCK"); } return true; } @@ -4726,11 +4748,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } mDefaultDisplayRotation.updateOrientationListener(); reportScreenStateToVrManager(false); - if (mCameraGestureTriggeredDuringGoingToSleep) { - wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromPowerKey, - PowerManager.WAKE_REASON_CAMERA_LAUNCH, - "com.android.systemui:CAMERA_GESTURE_PREVENT_LOCK"); - } } } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 4784723b7735..d8b1120c624d 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -42,8 +42,6 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.SynchronousUserSwitchObserver; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledSince; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -64,7 +62,6 @@ import android.os.BatteryManager; import android.os.BatteryManagerInternal; import android.os.BatterySaverPolicyConfig; import android.os.Binder; -import android.os.Build; import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; @@ -127,7 +124,6 @@ import com.android.server.UiThread; import com.android.server.UserspaceRebootLogger; import com.android.server.Watchdog; import com.android.server.am.BatteryStatsService; -import com.android.server.compat.PlatformCompat; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; import com.android.server.policy.WindowManagerPolicy; @@ -284,17 +280,6 @@ public final class PowerManagerService extends SystemService */ private static final long ENHANCED_DISCHARGE_PREDICTION_BROADCAST_MIN_DELAY_MS = 60 * 1000L; - /** - * Apps targeting Android U and above need to define - * {@link android.Manifest.permission#TURN_SCREEN_ON} in their manifest for - * {@link android.os.PowerManager#ACQUIRE_CAUSES_WAKEUP} to have any effect. - * Note that most applications should use {@link android.R.attr#turnScreenOn} or - * {@link android.app.Activity#setTurnScreenOn(boolean)} instead, as this prevents the - * previous foreground app from being resumed first when the screen turns on. - */ - @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - public static final long REQUIRE_TURN_SCREEN_ON_PERMISSION = 216114297L; /** Reason ID for holding display suspend blocker. */ private static final String HOLDING_DISPLAY_SUSPEND_BLOCKER = "holding display"; @@ -318,7 +303,6 @@ public final class PowerManagerService extends SystemService private final SystemPropertiesWrapper mSystemProperties; private final Clock mClock; private final Injector mInjector; - private final PlatformCompat mPlatformCompat; private AppOpsManager mAppOpsManager; private LightsManager mLightsManager; @@ -1012,11 +996,6 @@ public final class PowerManagerService extends SystemService public void set(String key, String val) { SystemProperties.set(key, val); } - - @Override - public boolean getBoolean(String key, boolean def) { - return SystemProperties.getBoolean(key, def); - } }; } @@ -1053,10 +1032,6 @@ public final class PowerManagerService extends SystemService AppOpsManager createAppOpsManager(Context context) { return context.getSystemService(AppOpsManager.class); } - - PlatformCompat createPlatformCompat(Context context) { - return context.getSystemService(PlatformCompat.class); - } } final Constants mConstants; @@ -1114,8 +1089,6 @@ public final class PowerManagerService extends SystemService mAppOpsManager = injector.createAppOpsManager(mContext); - mPlatformCompat = injector.createPlatformCompat(mContext); - mPowerGroupWakefulnessChangeListener = new PowerGroupWakefulnessChangeListener(); // Save brightness values: @@ -1626,28 +1599,14 @@ public final class PowerManagerService extends SystemService } if (mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON, opUid, opPackageName) == AppOpsManager.MODE_ALLOWED) { - if (mPlatformCompat.isChangeEnabledByPackageName(REQUIRE_TURN_SCREEN_ON_PERMISSION, - opPackageName, UserHandle.getUserId(opUid))) { - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.TURN_SCREEN_ON) - == PackageManager.PERMISSION_GRANTED) { - if (DEBUG_SPEW) { - Slog.d(TAG, "Allowing device wake-up from app " + opPackageName); - } - return true; - } - } else { - // android.permission.TURN_SCREEN_ON has only been introduced in Android U, only - // check for appOp for apps targeting lower SDK versions - if (DEBUG_SPEW) { - Slog.d(TAG, "Allowing device wake-up from app with " - + "REQUIRE_TURN_SCREEN_ON_PERMISSION disabled " + opPackageName); - } + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.TURN_SCREEN_ON) + == PackageManager.PERMISSION_GRANTED) { + Slog.i(TAG, "Allowing device wake-up from app " + opPackageName); return true; } } - if (PowerProperties.permissionless_turn_screen_on().orElse(true)) { - Slog.d(TAG, "Device wake-up will be denied without android.permission.TURN_SCREEN_ON"); + if (PowerProperties.permissionless_turn_screen_on().orElse(false)) { + Slog.d(TAG, "Device wake-up allowed by debug.power.permissionless_turn_screen_on"); return true; } Slog.w(TAG, "Not allowing device wake-up for " + opPackageName); diff --git a/services/core/java/com/android/server/power/SystemPropertiesWrapper.java b/services/core/java/com/android/server/power/SystemPropertiesWrapper.java index c68f9c63b13b..1acf798eb099 100644 --- a/services/core/java/com/android/server/power/SystemPropertiesWrapper.java +++ b/services/core/java/com/android/server/power/SystemPropertiesWrapper.java @@ -48,19 +48,4 @@ interface SystemPropertiesWrapper { * SELinux. libc will log the underlying reason. */ void set(@NonNull String key, @Nullable String val); - - /** - * Get the value for the given {@code key}, returned as a boolean. - * Values 'n', 'no', '0', 'false' or 'off' are considered false. - * Values 'y', 'yes', '1', 'true' or 'on' are considered true. - * (case sensitive). - * If the key does not exist, or has any other value, then the default - * result is returned. - * - * @param key the key to lookup - * @param def a default value to return - * @return the key parsed as a boolean, or def if the key isn't found or is - * not able to be parsed as a boolean. - */ - boolean getBoolean(@NonNull String key, boolean def); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 7ccf85f34737..d378b11f02bf 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -2272,6 +2272,25 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + boolean proto = false; + for (int i = 0; i < args.length; i++) { + if ("--proto".equals(args[i])) { + proto = true; + } + } + if (proto) { + if (mBar == null) return; + try (TransferPipe tp = new TransferPipe()) { + // Sending the command to the remote, which needs to execute async to avoid blocking + // See Binder#dumpAsync() for inspiration + mBar.dumpProto(args, tp.getWriteFd()); + // Times out after 5s + tp.go(fd); + } catch (Throwable t) { + Slog.e(TAG, "Error sending command to IStatusBar", t); + } + return; + } synchronized (mLock) { for (int i = 0; i < mDisplayUiState.size(); i++) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 885968f619f6..391d081ce995 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -22,7 +22,6 @@ import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -5787,12 +5786,10 @@ class Task extends TaskFragment { return false; } - // Existing Tasks can be reused if a new root task will be created anyway, or for the - // Dream - because there can only ever be one DreamActivity. + // Existing Tasks can be reused if a new root task will be created anyway. final int windowingMode = getWindowingMode(); final int activityType = getActivityType(); - return DisplayContent.alwaysCreateRootTask(windowingMode, activityType) - || activityType == ACTIVITY_TYPE_DREAM; + return DisplayContent.alwaysCreateRootTask(windowingMode, activityType); } void addChild(WindowContainer child, final boolean toTop, boolean showForAllUsers) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 9e449aedf0d9..b74fedf3ff46 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1792,7 +1792,8 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartStatusBarManagerService"); try { statusBar = new StatusBarManagerService(context); - ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar); + ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar, false, + DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO); } catch (Throwable e) { reportWtf("starting StatusBarManagerService", e); } diff --git a/services/tests/mockingservicestests/OWNERS b/services/tests/mockingservicestests/OWNERS index 2bb16496e0f0..4dda51f2004f 100644 --- a/services/tests/mockingservicestests/OWNERS +++ b/services/tests/mockingservicestests/OWNERS @@ -1,5 +1,8 @@ include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS + +# Game Platform per-file FakeGameClassifier.java = file:/GAME_MANAGER_OWNERS per-file FakeGameServiceProviderInstance = file:/GAME_MANAGER_OWNERS per-file FakeServiceConnector.java = file:/GAME_MANAGER_OWNERS per-file Game* = file:/GAME_MANAGER_OWNERS +per-file res/xml/game_manager* = file:/GAME_MANAGER_OWNERS diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index c12544897941..d9a26c68f3ed 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -549,12 +549,6 @@ public class BroadcastQueueTest { receivers, false, null, null, userId); } - private BroadcastRecord makeOrderedBroadcastRecord(Intent intent, ProcessRecord callerApp, - List<Object> receivers, IIntentReceiver orderedResultTo, Bundle orderedExtras) { - return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(), - receivers, true, orderedResultTo, orderedExtras, UserHandle.USER_SYSTEM); - } - private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp, BroadcastOptions options, List<Object> receivers) { return makeBroadcastRecord(intent, callerApp, options, @@ -562,12 +556,24 @@ public class BroadcastQueueTest { } private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp, + List<Object> receivers, IIntentReceiver resultTo) { + return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(), + receivers, false, resultTo, null, UserHandle.USER_SYSTEM); + } + + private BroadcastRecord makeOrderedBroadcastRecord(Intent intent, ProcessRecord callerApp, + List<Object> receivers, IIntentReceiver resultTo, Bundle resultExtras) { + return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(), + receivers, true, resultTo, resultExtras, UserHandle.USER_SYSTEM); + } + + private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp, BroadcastOptions options, List<Object> receivers, boolean ordered, - IIntentReceiver orderedResultTo, Bundle orderedExtras, int userId) { + IIntentReceiver resultTo, Bundle resultExtras, int userId) { return new BroadcastRecord(mQueue, intent, callerApp, callerApp.info.packageName, null, callerApp.getPid(), callerApp.info.uid, false, null, null, null, null, - AppOpsManager.OP_NONE, options, receivers, callerApp, orderedResultTo, - Activity.RESULT_OK, null, orderedExtras, ordered, false, false, userId, false, null, + AppOpsManager.OP_NONE, options, receivers, callerApp, resultTo, + Activity.RESULT_OK, null, resultExtras, ordered, false, false, userId, false, null, false, null); } @@ -1347,6 +1353,26 @@ public class BroadcastQueueTest { } /** + * Verify that we deliver results for unordered broadcasts. + */ + @Test + public void testUnordered_ResultTo() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final IApplicationThread callerThread = callerApp.getThread(); + + final IIntentReceiver resultTo = mock(IIntentReceiver.class); + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)), resultTo)); + + waitForIdle(); + verify(callerThread).scheduleRegisteredReceiver(any(), argThat(filterEquals(airplane)), + eq(Activity.RESULT_OK), any(), any(), eq(false), + anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt()); + } + + /** * Verify that we're not surprised by a process attempting to finishing a * broadcast when none is in progress. */ diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index eb1314194aa3..ffacbf331d89 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -16,7 +16,7 @@ package com.android.server.biometrics.sensors; -import static android.testing.TestableLooper.RunWithLooper; +import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; @@ -24,8 +24,10 @@ import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -35,6 +37,7 @@ import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; import android.content.Context; +import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.IBiometricService; import android.os.Binder; @@ -63,28 +66,26 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.function.Supplier; @Presubmit @SmallTest @RunWith(AndroidTestingRunner.class) -@RunWithLooper(setAsMainLooper = true) +@TestableLooper.RunWithLooper(setAsMainLooper = true) public class BiometricSchedulerTest { private static final String TAG = "BiometricSchedulerTest"; private static final int TEST_SENSOR_ID = 1; private static final int LOG_NUM_RECENT_OPERATIONS = 2; - + @Rule + public final TestableContext mContext = + new TestableContext(InstrumentationRegistry.getContext(), null); private BiometricScheduler mScheduler; private IBinder mToken; - @Mock private IBiometricService mBiometricService; - @Rule - public final TestableContext mContext = - new TestableContext(InstrumentationRegistry.getContext(), null); - @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -323,7 +324,7 @@ public class BiometricSchedulerTest { client1.getCallback().onClientFinished(client1, true /* success */); waitForIdle(); verify(callback).onError(anyInt(), anyInt(), - eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED), + eq(BIOMETRIC_ERROR_CANCELED), eq(0) /* vendorCode */); assertNull(mScheduler.getCurrentClient()); assertTrue(client1.isAlreadyDone()); @@ -484,7 +485,7 @@ public class BiometricSchedulerTest { mScheduler.scheduleClientMonitor(interrupter); waitForIdle(); - verify((Interruptable) interruptableMonitor).cancel(); + verify(interruptableMonitor).cancel(); mScheduler.getInternalCallback().onClientFinished(interruptableMonitor, true /* success */); } @@ -500,7 +501,7 @@ public class BiometricSchedulerTest { mScheduler.scheduleClientMonitor(interrupter); waitForIdle(); - verify((Interruptable) interruptableMonitor, never()).cancel(); + verify(interruptableMonitor, never()).cancel(); } @Test @@ -514,21 +515,180 @@ public class BiometricSchedulerTest { assertTrue(client.mDestroyed); } + @Test + public void testClearBiometricQueue_clearsHungAuthOperation() { + // Creating a hung client + final TestableLooper looper = TestableLooper.get(this); + final Supplier<Object> lazyDaemon1 = () -> mock(Object.class); + final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext, + lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */); + final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class); + + mScheduler.scheduleClientMonitor(client1, callback1); + waitForIdle(); + + mScheduler.startWatchdog(); + waitForIdle(); + + //Checking client is hung + verify(callback1).onClientStarted(client1); + verify(callback1, never()).onClientFinished(any(), anyBoolean()); + assertNotNull(mScheduler.mCurrentOperation); + assertEquals(0, mScheduler.getCurrentPendingCount()); + + looper.moveTimeForward(10000); + waitForIdle(); + looper.moveTimeForward(3000); + waitForIdle(); + + // The hung client did not honor this operation, verify onError and authenticated + // were never called. + assertFalse(client1.mOnErrorCalled); + assertFalse(client1.mAuthenticateCalled); + verify(callback1).onClientFinished(client1, false /* success */); + assertNull(mScheduler.mCurrentOperation); + assertEquals(0, mScheduler.getCurrentPendingCount()); + } + + @Test + public void testAuthWorks_afterClearBiometricQueue() { + // Creating a hung client + final TestableLooper looper = TestableLooper.get(this); + final Supplier<Object> lazyDaemon1 = () -> mock(Object.class); + final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext, + lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */); + final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class); + + mScheduler.scheduleClientMonitor(client1, callback1); + + assertEquals(client1, mScheduler.mCurrentOperation.getClientMonitor()); + assertEquals(0, mScheduler.getCurrentPendingCount()); + + //Checking client is hung + waitForIdle(); + verify(callback1, never()).onClientFinished(any(), anyBoolean()); + + //Start watchdog + mScheduler.startWatchdog(); + waitForIdle(); + + // The watchdog should kick off the cancellation + looper.moveTimeForward(10000); + waitForIdle(); + // After 10 seconds the HAL has 3 seconds to respond to a cancel + looper.moveTimeForward(3000); + waitForIdle(); + + // The hung client did not honor this operation, verify onError and authenticated + // were never called. + assertFalse(client1.mOnErrorCalled); + assertFalse(client1.mAuthenticateCalled); + verify(callback1).onClientFinished(client1, false /* success */); + assertEquals(0, mScheduler.getCurrentPendingCount()); + assertNull(mScheduler.mCurrentOperation); + + + //Run additional auth client + final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, + lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */); + final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class); + + mScheduler.scheduleClientMonitor(client2, callback2); + + assertEquals(client2, mScheduler.mCurrentOperation.getClientMonitor()); + assertEquals(0, mScheduler.getCurrentPendingCount()); + + //Start watchdog + mScheduler.startWatchdog(); + waitForIdle(); + mScheduler.scheduleClientMonitor(mock(BaseClientMonitor.class), + mock(ClientMonitorCallback.class)); + waitForIdle(); + + //Ensure auth client passes + verify(callback2).onClientStarted(client2); + client2.getCallback().onClientFinished(client2, true); + waitForIdle(); + + looper.moveTimeForward(10000); + waitForIdle(); + // After 10 seconds the HAL has 3 seconds to respond to a cancel + looper.moveTimeForward(3000); + waitForIdle(); + + //Asserting auth client passes + assertTrue(client2.isAlreadyDone()); + assertNotNull(mScheduler.mCurrentOperation); + } + + @Test + public void testClearBiometricQueue_doesNotClearOperationsWhenQueueNotStuck() { + //Creating clients + final TestableLooper looper = TestableLooper.get(this); + final Supplier<Object> lazyDaemon1 = () -> mock(Object.class); + final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext, + lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */); + final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class); + + mScheduler.scheduleClientMonitor(client1, callback1); + //Start watchdog + mScheduler.startWatchdog(); + waitForIdle(); + mScheduler.scheduleClientMonitor(mock(BaseClientMonitor.class), + mock(ClientMonitorCallback.class)); + mScheduler.scheduleClientMonitor(mock(BaseClientMonitor.class), + mock(ClientMonitorCallback.class)); + waitForIdle(); + + assertEquals(client1, mScheduler.mCurrentOperation.getClientMonitor()); + assertEquals(2, mScheduler.getCurrentPendingCount()); + verify(callback1, never()).onClientFinished(any(), anyBoolean()); + verify(callback1).onClientStarted(client1); + + //Client finishes successfully + client1.getCallback().onClientFinished(client1, true); + waitForIdle(); + + // The watchdog should kick off the cancellation + looper.moveTimeForward(10000); + waitForIdle(); + // After 10 seconds the HAL has 3 seconds to respond to a cancel + looper.moveTimeForward(3000); + waitForIdle(); + + //Watchdog does not clear pending operations + assertEquals(1, mScheduler.getCurrentPendingCount()); + assertNotNull(mScheduler.mCurrentOperation); + + } + private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception { return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer)); } + private void waitForIdle() { + TestableLooper.get(this).processAllMessages(); + } + private static class TestAuthenticationClient extends AuthenticationClient<Object> { boolean mStartedHal = false; boolean mStoppedHal = false; boolean mDestroyed = false; int mNumCancels = 0; + boolean mAuthenticateCalled = false; + boolean mOnErrorCalled = false; - public TestAuthenticationClient(@NonNull Context context, + TestAuthenticationClient(@NonNull Context context, @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener) { + this(context, lazyDaemon, token, listener, 1 /* cookie */); + } + + TestAuthenticationClient(@NonNull Context context, + @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter listener, int cookie) { super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */, - false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */, + false /* restricted */, TAG, cookie, false /* requireConfirmation */, TEST_SENSOR_ID, mock(BiometricLogger.class), mock(BiometricContext.class), true /* isStrongBiometric */, null /* taskStackListener */, mock(LockoutTracker.class), false /* isKeyguard */, @@ -546,7 +706,19 @@ public class BiometricSchedulerTest { } @Override - protected void handleLifecycleAfterAuth(boolean authenticated) {} + protected void handleLifecycleAfterAuth(boolean authenticated) { + } + + @Override + public void onAuthenticated(BiometricAuthenticator.Identifier identifier, + boolean authenticated, ArrayList<Byte> hardwareAuthToken) { + mAuthenticateCalled = true; + } + + @Override + protected void onErrorInternal(int errorCode, int vendorCode, boolean finish) { + mOnErrorCalled = true; + } @Override public boolean wasUserDetected() { @@ -651,8 +823,4 @@ public class BiometricSchedulerTest { mDestroyed = true; } } - - private void waitForIdle() { - TestableLooper.get(this).processAllMessages(); - } } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index c5ba360b6170..02bbe658f9b2 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -587,9 +587,9 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final int keyCode = KeyEvent.KEYCODE_A; final int action = VirtualKeyEvent.ACTION_UP; - mInputController.mInputDeviceDescriptors.put(BINDER, - new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 1, - /* displayId= */ 1, PHYS, DEVICE_ID)); + mInputController.addDeviceForTesting(BINDER, fd, /* type= */1, /* displayId= */ 1, PHYS, + DEVICE_ID); + mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode) .setAction(action).build()); verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action); @@ -612,9 +612,8 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK; final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; - mInputController.mInputDeviceDescriptors.put(BINDER, - new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1, PHYS, DEVICE_ID)); + mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS, + DEVICE_ID); doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() .setButtonCode(buttonCode) @@ -627,9 +626,8 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK; final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; - mInputController.mInputDeviceDescriptors.put(BINDER, - new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1, PHYS, DEVICE_ID)); + mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS, + DEVICE_ID); assertThrows( IllegalStateException.class, () -> @@ -653,9 +651,8 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final float x = -0.2f; final float y = 0.7f; - mInputController.mInputDeviceDescriptors.put(BINDER, - new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1, PHYS, DEVICE_ID)); + mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS, + DEVICE_ID); doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder() .setRelativeX(x).setRelativeY(y).build()); @@ -667,9 +664,8 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final float x = -0.2f; final float y = 0.7f; - mInputController.mInputDeviceDescriptors.put(BINDER, - new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1, PHYS, DEVICE_ID)); + mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS, + DEVICE_ID); assertThrows( IllegalStateException.class, () -> @@ -694,9 +690,8 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final float x = 0.5f; final float y = 1f; - mInputController.mInputDeviceDescriptors.put(BINDER, - new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1, PHYS, DEVICE_ID)); + mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS, + DEVICE_ID); doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() .setXAxisMovement(x) @@ -709,9 +704,8 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final float x = 0.5f; final float y = 1f; - mInputController.mInputDeviceDescriptors.put(BINDER, - new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1, PHYS, DEVICE_ID)); + mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS, + DEVICE_ID); assertThrows( IllegalStateException.class, () -> @@ -742,9 +736,8 @@ public class VirtualDeviceManagerServiceTest { final float x = 100.5f; final float y = 200.5f; final int action = VirtualTouchEvent.ACTION_UP; - mInputController.mInputDeviceDescriptors.put(BINDER, - new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3, - /* displayId= */ 1, PHYS, DEVICE_ID)); + mInputController.addDeviceForTesting(BINDER, fd, /* type= */3, /* displayId= */ 1, PHYS, + DEVICE_ID); mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x) .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build()); verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN, @@ -761,9 +754,8 @@ public class VirtualDeviceManagerServiceTest { final int action = VirtualTouchEvent.ACTION_UP; final float pressure = 1.0f; final float majorAxisSize = 10.0f; - mInputController.mInputDeviceDescriptors.put(BINDER, - new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3, - /* displayId= */ 1, PHYS, DEVICE_ID)); + mInputController.addDeviceForTesting(BINDER, fd, /* type= */3, /* displayId= */ 1, PHYS, + DEVICE_ID); mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x) .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType) .setPressure(pressure).setMajorAxisSize(majorAxisSize).build()); diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index 6860abf40b56..062bde8f080b 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.display; import static android.Manifest.permission.ADD_TRUSTED_DISPLAY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP; import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX; @@ -660,6 +661,117 @@ public class DisplayManagerServiceTest { firstDisplayId); } + /** Tests that the virtual device is created in a device display group. */ + @Test + public void createVirtualDisplay_addsDisplaysToDeviceDisplayGroups() throws Exception { + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + + registerDefaultDisplays(displayManager); + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + + when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)) + .thenReturn(PackageManager.PERMISSION_DENIED); + + IVirtualDevice virtualDevice = mock(IVirtualDevice.class); + when(mMockVirtualDeviceManagerInternal.isValidVirtualDevice(virtualDevice)) + .thenReturn(true); + when(virtualDevice.getDeviceId()).thenReturn(1); + + // Create a first virtual display. A display group should be created for this display on the + // virtual device. + final VirtualDisplayConfig.Builder builder1 = + new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320) + .setUniqueId("uniqueId --- device display group 1"); + + int displayId1 = + localService.createVirtualDisplay( + builder1.build(), + mMockAppToken /* callback */, + virtualDevice /* virtualDeviceToken */, + mock(DisplayWindowPolicyController.class), + PACKAGE_NAME); + int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId; + + // Create a second virtual display. This should be added to the previously created display + // group. + final VirtualDisplayConfig.Builder builder2 = + new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320) + .setUniqueId("uniqueId --- device display group 1"); + + int displayId2 = + localService.createVirtualDisplay( + builder2.build(), + mMockAppToken /* callback */, + virtualDevice /* virtualDeviceToken */, + mock(DisplayWindowPolicyController.class), + PACKAGE_NAME); + int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId; + + assertEquals( + "Both displays should be added to the same displayGroup.", + displayGroupId1, + displayGroupId2); + } + + /** + * Tests that the virtual display is not added to the device display group when + * VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is set. + */ + @Test + public void createVirtualDisplay_addsDisplaysToOwnDisplayGroups() throws Exception { + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + + registerDefaultDisplays(displayManager); + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + + when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)) + .thenReturn(PackageManager.PERMISSION_DENIED); + + IVirtualDevice virtualDevice = mock(IVirtualDevice.class); + when(mMockVirtualDeviceManagerInternal.isValidVirtualDevice(virtualDevice)) + .thenReturn(true); + when(virtualDevice.getDeviceId()).thenReturn(1); + + // Create a first virtual display. A display group should be created for this display on the + // virtual device. + final VirtualDisplayConfig.Builder builder1 = + new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320) + .setUniqueId("uniqueId --- device display group 1"); + + int displayId1 = + localService.createVirtualDisplay( + builder1.build(), + mMockAppToken /* callback */, + virtualDevice /* virtualDeviceToken */, + mock(DisplayWindowPolicyController.class), + PACKAGE_NAME); + int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId; + + // Create a second virtual display. With the flag VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP, + // the display should not be added to the previously created display group. + final VirtualDisplayConfig.Builder builder2 = + new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320) + .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) + .setUniqueId("uniqueId --- device display group 1"); + + int displayId2 = + localService.createVirtualDisplay( + builder2.build(), + mMockAppToken /* callback */, + virtualDevice /* virtualDeviceToken */, + mock(DisplayWindowPolicyController.class), + PACKAGE_NAME); + int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId; + + assertNotEquals( + "Display 1 should be in the device display group and display 2 in its own display" + + " group.", + displayGroupId1, + displayGroupId2); + } + @Test public void testGetDisplayIdToMirror() throws Exception { DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java index 0b33c30fd7e8..657bda633ab5 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -369,6 +369,98 @@ public class LogicalDisplayMapperTest { } @Test + public void testDevicesAreAddedToDeviceDisplayGroups() { + // Create the default internal display of the device. + LogicalDisplay defaultDisplay = + add( + createDisplayDevice( + Display.TYPE_INTERNAL, + 600, + 800, + DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY)); + + // Create 3 virtual displays associated with a first virtual device. + int deviceId1 = 1; + TestDisplayDevice display1 = + createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display1", 600, 800, 0); + mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display1, deviceId1); + LogicalDisplay virtualDevice1Display1 = add(display1); + + TestDisplayDevice display2 = + createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display2", 600, 800, 0); + mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display2, deviceId1); + LogicalDisplay virtualDevice1Display2 = add(display2); + + TestDisplayDevice display3 = + createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display3", 600, 800, 0); + mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display3, deviceId1); + LogicalDisplay virtualDevice1Display3 = add(display3); + + // Create another 3 virtual displays associated with a second virtual device. + int deviceId2 = 2; + TestDisplayDevice display4 = + createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice2Display1", 600, 800, 0); + mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display4, deviceId2); + LogicalDisplay virtualDevice2Display1 = add(display4); + + TestDisplayDevice display5 = + createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice2Display2", 600, 800, 0); + mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display5, deviceId2); + LogicalDisplay virtualDevice2Display2 = add(display5); + + // The final display is created with FLAG_OWN_DISPLAY_GROUP set. + TestDisplayDevice display6 = + createDisplayDevice( + Display.TYPE_VIRTUAL, + "virtualDevice2Display3", + 600, + 800, + DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP); + mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display6, deviceId2); + LogicalDisplay virtualDevice2Display3 = add(display6); + + // Verify that the internal display is in the default display group. + assertEquals( + DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(defaultDisplay))); + + // Verify that all the displays for virtual device 1 are in the same (non-default) display + // group. + int virtualDevice1DisplayGroupId = + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked( + id(virtualDevice1Display1)); + assertNotEquals(DEFAULT_DISPLAY_GROUP, virtualDevice1DisplayGroupId); + assertEquals( + virtualDevice1DisplayGroupId, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked( + id(virtualDevice1Display2))); + assertEquals( + virtualDevice1DisplayGroupId, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked( + id(virtualDevice1Display3))); + + // The first 2 displays for virtual device 2 should be in the same non-default group. + int virtualDevice2DisplayGroupId = + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked( + id(virtualDevice2Display1)); + assertNotEquals(DEFAULT_DISPLAY_GROUP, virtualDevice2DisplayGroupId); + assertEquals( + virtualDevice2DisplayGroupId, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked( + id(virtualDevice2Display2))); + // virtualDevice2Display3 was created with FLAG_OWN_DISPLAY_GROUP and shouldn't be grouped + // with other displays of this device or be in the default display group. + assertNotEquals( + virtualDevice2DisplayGroupId, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked( + id(virtualDevice2Display3))); + assertNotEquals( + DEFAULT_DISPLAY_GROUP, + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked( + id(virtualDevice2Display3))); + } + + @Test public void testDeviceShouldBeWoken() { assertTrue(mLogicalDisplayMapper.shouldDeviceBeWoken(DEVICE_STATE_OPEN, DEVICE_STATE_CLOSED, @@ -416,14 +508,22 @@ public class LogicalDisplayMapperTest { ///////////////// private TestDisplayDevice createDisplayDevice(int type, int width, int height, int flags) { - return createDisplayDevice(new TestUtils.TestDisplayAddress(), type, width, height, flags); + return createDisplayDevice( + new TestUtils.TestDisplayAddress(), /* uniqueId */ "", type, width, height, flags); + } + + private TestDisplayDevice createDisplayDevice( + int type, String uniqueId, int width, int height, int flags) { + return createDisplayDevice( + new TestUtils.TestDisplayAddress(), uniqueId, type, width, height, flags); } private TestDisplayDevice createDisplayDevice( - DisplayAddress address, int type, int width, int height, int flags) { + DisplayAddress address, String uniqueId, int type, int width, int height, int flags) { TestDisplayDevice device = new TestDisplayDevice(); DisplayDeviceInfo displayDeviceInfo = device.getSourceInfo(); displayDeviceInfo.type = type; + displayDeviceInfo.uniqueId = uniqueId; displayDeviceInfo.width = width; displayDeviceInfo.height = height; displayDeviceInfo.flags = flags; diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java new file mode 100644 index 000000000000..303a370b0ba9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2022 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.dreams; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Context; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.IRemoteCallback; +import android.os.RemoteException; +import android.os.test.TestLooper; +import android.service.dreams.IDreamService; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +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; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DreamControllerTest { + @Mock + private DreamController.Listener mListener; + @Mock + private Context mContext; + @Mock + private IBinder mIBinder; + @Mock + private IDreamService mIDreamService; + + @Captor + private ArgumentCaptor<ServiceConnection> mServiceConnectionACaptor; + @Captor + private ArgumentCaptor<IRemoteCallback> mRemoteCallbackCaptor; + + private final TestLooper mLooper = new TestLooper(); + private final Handler mHandler = new Handler(mLooper.getLooper()); + + private DreamController mDreamController; + + private Binder mToken; + private ComponentName mDreamName; + private ComponentName mOverlayName; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + when(mIDreamService.asBinder()).thenReturn(mIBinder); + when(mIBinder.queryLocalInterface(anyString())).thenReturn(mIDreamService); + when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true); + + mToken = new Binder(); + mDreamName = ComponentName.unflattenFromString("dream"); + mOverlayName = ComponentName.unflattenFromString("dream_overlay"); + mDreamController = new DreamController(mContext, mHandler, mListener); + } + + @Test + public void startDream_attachOnServiceConnected() throws RemoteException { + // Call dream controller to start dreaming. + mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, + 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); + + // Mock service connected. + final ServiceConnection serviceConnection = captureServiceConnection(); + serviceConnection.onServiceConnected(mDreamName, mIBinder); + mLooper.dispatchAll(); + + // Verify that dream service is called to attach. + verify(mIDreamService).attach(eq(mToken), eq(false) /*doze*/, any()); + } + + @Test + public void startDream_startASecondDream_detachOldDreamOnceNewDreamIsStarted() + throws RemoteException { + // Start first dream. + mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, + 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); + captureServiceConnection().onServiceConnected(mDreamName, mIBinder); + mLooper.dispatchAll(); + clearInvocations(mContext); + + // Set up second dream. + final Binder newToken = new Binder(); + final ComponentName newDreamName = ComponentName.unflattenFromString("new_dream"); + final ComponentName newOverlayName = ComponentName.unflattenFromString("new_dream_overlay"); + final IDreamService newDreamService = mock(IDreamService.class); + final IBinder newBinder = mock(IBinder.class); + when(newDreamService.asBinder()).thenReturn(newBinder); + when(newBinder.queryLocalInterface(anyString())).thenReturn(newDreamService); + + // Start second dream. + mDreamController.startDream(newToken, newDreamName, false /*isPreview*/, false /*doze*/, + 0 /*userId*/, null /*wakeLock*/, newOverlayName, "test" /*reason*/); + captureServiceConnection().onServiceConnected(newDreamName, newBinder); + mLooper.dispatchAll(); + + // Mock second dream started. + verify(newDreamService).attach(eq(newToken), eq(false) /*doze*/, + mRemoteCallbackCaptor.capture()); + mRemoteCallbackCaptor.getValue().sendResult(null /*data*/); + mLooper.dispatchAll(); + + // Verify that the first dream is called to detach. + verify(mIDreamService).detach(); + } + + @Test + public void stopDream_detachFromService() throws RemoteException { + // Start dream. + mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, + 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); + captureServiceConnection().onServiceConnected(mDreamName, mIBinder); + mLooper.dispatchAll(); + + // Stop dream. + mDreamController.stopDream(true /*immediate*/, "test stop dream" /*reason*/); + + // Verify that dream service is called to detach. + verify(mIDreamService).detach(); + } + + private ServiceConnection captureServiceConnection() { + verify(mContext).bindServiceAsUser(any(), mServiceConnectionACaptor.capture(), anyInt(), + any()); + return mServiceConnectionACaptor.getValue(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt index 65076a372b0b..b095a506af21 100644 --- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt +++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt @@ -32,6 +32,7 @@ import android.os.test.TestLooper import android.platform.test.annotations.Presubmit import android.view.InputDevice import androidx.test.InstrumentationRegistry +import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS import com.android.server.input.BatteryController.UEventManager import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener import org.hamcrest.Description @@ -42,6 +43,8 @@ import org.hamcrest.TypeSafeMatcher import org.hamcrest.core.IsEqual.equalTo import org.junit.After import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.Before import org.junit.Rule @@ -63,14 +66,20 @@ import org.mockito.hamcrest.MockitoHamcrest import org.mockito.junit.MockitoJUnit import org.mockito.verification.VerificationMode -private fun createInputDevice(deviceId: Int, hasBattery: Boolean = true): InputDevice = +private fun createInputDevice( + deviceId: Int, + hasBattery: Boolean = true, + supportsUsi: Boolean = false, + generation: Int = -1, +): InputDevice = InputDevice.Builder() .setId(deviceId) .setName("Device $deviceId") .setDescriptor("descriptor $deviceId") .setExternal(true) .setHasBattery(hasBattery) - .setGeneration(0) + .setSupportsUsi(supportsUsi) + .setGeneration(generation) .build() // Returns a matcher that helps match member variables of a class. @@ -118,7 +127,10 @@ private fun matchesState( return Matchers.allOf(batteryStateMatchers) } -// Helper used to verify interactions with a mocked battery listener. +private fun isInvalidBatteryState(deviceId: Int): Matcher<IInputDeviceBatteryState> = + matchesState(deviceId, isPresent = false, status = STATUS_UNKNOWN, capacity = Float.NaN) + +// Helpers used to verify interactions with a mocked battery listener. private fun IInputDeviceBatteryListener.verifyNotified( deviceId: Int, mode: VerificationMode = times(1), @@ -127,8 +139,21 @@ private fun IInputDeviceBatteryListener.verifyNotified( capacity: Float? = null, eventTime: Long? = null ) { - verify(this, mode).onBatteryStateChanged( - MockitoHamcrest.argThat(matchesState(deviceId, isPresent, status, capacity, eventTime))) + verifyNotified(matchesState(deviceId, isPresent, status, capacity, eventTime), mode) +} + +private fun IInputDeviceBatteryListener.verifyNotified( + matcher: Matcher<IInputDeviceBatteryState>, + mode: VerificationMode = times(1) +) { + verify(this, mode).onBatteryStateChanged(MockitoHamcrest.argThat(matcher)) +} + +private fun createMockListener(): IInputDeviceBatteryListener { + val listener = mock(IInputDeviceBatteryListener::class.java) + val binder = mock(Binder::class.java) + `when`(listener.asBinder()).thenReturn(binder) + return listener } /** @@ -143,6 +168,8 @@ class BatteryControllerTests { const val PID = 42 const val DEVICE_ID = 13 const val SECOND_DEVICE_ID = 11 + const val USI_DEVICE_ID = 101 + const val SECOND_USI_DEVICE_ID = 102 const val TIMESTAMP = 123456789L } @@ -168,10 +195,11 @@ class BatteryControllerTests { testLooper = TestLooper() val inputManager = InputManager.resetInstance(iInputManager) `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager) - `when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID, SECOND_DEVICE_ID)) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(createInputDevice(DEVICE_ID)) - `when`(iInputManager.getInputDevice(SECOND_DEVICE_ID)) - .thenReturn(createInputDevice(SECOND_DEVICE_ID)) + `when`(iInputManager.inputDeviceIds).then { + deviceGenerationMap.keys.toIntArray() + } + addInputDevice(DEVICE_ID) + addInputDevice(SECOND_DEVICE_ID) batteryController = BatteryController(context, native, testLooper.looper, uEventManager) batteryController.systemRunning() @@ -180,10 +208,30 @@ class BatteryControllerTests { devicesChangedListener = listenerCaptor.value } - private fun notifyDeviceChanged(deviceId: Int) { - deviceGenerationMap[deviceId] = deviceGenerationMap[deviceId]?.plus(1) ?: 1 + private fun notifyDeviceChanged( + deviceId: Int, + hasBattery: Boolean = true, + supportsUsi: Boolean = false + ) { + val generation = deviceGenerationMap[deviceId]?.plus(1) + ?: throw IllegalArgumentException("Device $deviceId was never added!") + deviceGenerationMap[deviceId] = generation + + `when`(iInputManager.getInputDevice(deviceId)) + .thenReturn(createInputDevice(deviceId, hasBattery, supportsUsi, generation)) val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) } - devicesChangedListener.onInputDevicesChanged(list.toIntArray()) + if (::devicesChangedListener.isInitialized) { + devicesChangedListener.onInputDevicesChanged(list.toIntArray()) + } + } + + private fun addInputDevice( + deviceId: Int, + hasBattery: Boolean = true, + supportsUsi: Boolean = false + ) { + deviceGenerationMap[deviceId] = 0 + notifyDeviceChanged(deviceId, hasBattery, supportsUsi) } @After @@ -191,13 +239,6 @@ class BatteryControllerTests { InputManager.clearInstance() } - private fun createMockListener(): IInputDeviceBatteryListener { - val listener = mock(IInputDeviceBatteryListener::class.java) - val binder = mock(Binder::class.java) - `when`(listener.asBinder()).thenReturn(binder) - return listener - } - @Test fun testRegisterAndUnregisterBinderLifecycle() { val listener = createMockListener() @@ -303,19 +344,14 @@ class BatteryControllerTests { listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.78f) // If the battery presence for the InputDevice changes, the listener is notified. - `when`(iInputManager.getInputDevice(DEVICE_ID)) - .thenReturn(createInputDevice(DEVICE_ID, hasBattery = false)) - notifyDeviceChanged(DEVICE_ID) + notifyDeviceChanged(DEVICE_ID, hasBattery = false) testLooper.dispatchNext() - listener.verifyNotified(DEVICE_ID, isPresent = false, status = STATUS_UNKNOWN, - capacity = Float.NaN) + listener.verifyNotified(isInvalidBatteryState(DEVICE_ID)) // Since the battery is no longer present, the UEventListener should be removed. verify(uEventManager).removeListener(uEventListener.value) // If the battery becomes present again, the listener is notified. - `when`(iInputManager.getInputDevice(DEVICE_ID)) - .thenReturn(createInputDevice(DEVICE_ID, hasBattery = true)) - notifyDeviceChanged(DEVICE_ID) + notifyDeviceChanged(DEVICE_ID, hasBattery = true) testLooper.dispatchNext() listener.verifyNotified(DEVICE_ID, mode = times(2), status = STATUS_CHARGING, capacity = 0.78f) @@ -340,9 +376,17 @@ class BatteryControllerTests { // Move the time forward so that the polling period has elapsed. // The listener should be notified. - testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS - 1) + testLooper.moveTimeForward(POLLING_PERIOD_MILLIS - 1) + assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle) testLooper.dispatchNext() listener.verifyNotified(DEVICE_ID, capacity = 0.80f) + + // Move the time forward so that another polling period has elapsed. + // The battery should still be polled, but there is no change so listeners are not notified. + testLooper.moveTimeForward(POLLING_PERIOD_MILLIS) + assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle) + testLooper.dispatchNext() + listener.verifyNotified(DEVICE_ID, mode = times(1), capacity = 0.80f) } @Test @@ -357,7 +401,8 @@ class BatteryControllerTests { // The battery state changed, but we should not be polling for battery changes when the // device is not interactive. `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(80) - testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS) + testLooper.moveTimeForward(POLLING_PERIOD_MILLIS) + assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle) testLooper.dispatchAll() listener.verifyNotified(DEVICE_ID, mode = never(), capacity = 0.80f) @@ -368,7 +413,8 @@ class BatteryControllerTests { // Ensure that we continue to poll for battery changes. `when`(native.getBatteryCapacity(DEVICE_ID)).thenReturn(90) - testLooper.moveTimeForward(BatteryController.POLLING_PERIOD_MILLIS) + testLooper.moveTimeForward(POLLING_PERIOD_MILLIS) + assertTrue("There should be a polling callbacks posted to the handler", testLooper.isIdle) testLooper.dispatchNext() listener.verifyNotified(DEVICE_ID, capacity = 0.90f) } @@ -398,4 +444,44 @@ class BatteryControllerTests { matchesState(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f)) listener.verifyNotified(DEVICE_ID, status = STATUS_CHARGING, capacity = 0.80f) } + + @Test + fun testUsiDeviceIsMonitoredPersistently() { + `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device") + addInputDevice(USI_DEVICE_ID, supportsUsi = true) + testLooper.dispatchNext() + + // Even though there is no listener added for this device, it is being monitored. + val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java) + verify(uEventManager) + .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device")) + + // Add and remove a listener for the device. + val listener = createMockListener() + batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID) + batteryController.unregisterBatteryListener(USI_DEVICE_ID, listener, PID) + + // The device is still being monitored. + verify(uEventManager, never()).removeListener(uEventListener.value) + } + + @Test + fun testNoPollingWhenUsiDevicesAreMonitored() { + `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device") + addInputDevice(USI_DEVICE_ID, supportsUsi = true) + testLooper.dispatchNext() + `when`(native.getBatteryDevicePath(SECOND_USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device2") + addInputDevice(SECOND_USI_DEVICE_ID, supportsUsi = true) + testLooper.dispatchNext() + + testLooper.moveTimeForward(POLLING_PERIOD_MILLIS) + assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle) + + // Add a listener. + val listener = createMockListener() + batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID) + + testLooper.moveTimeForward(POLLING_PERIOD_MILLIS) + assertFalse("There should be no polling callbacks posted to the handler", testLooper.isIdle) + } } diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index fe4db3a758e3..db2630e2683c 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -87,7 +87,6 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.compat.PlatformCompat; import com.android.server.lights.LightsManager; import com.android.server.policy.WindowManagerPolicy; import com.android.server.power.PowerManagerService.BatteryReceiver; @@ -147,7 +146,6 @@ public class PowerManagerServiceTest { @Mock private SystemPropertiesWrapper mSystemPropertiesMock; @Mock private AppOpsManager mAppOpsManagerMock; @Mock private LowPowerStandbyController mLowPowerStandbyControllerMock; - @Mock private PlatformCompat mPlatformCompat; @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock; @@ -321,11 +319,6 @@ public class PowerManagerServiceTest { AppOpsManager createAppOpsManager(Context context) { return mAppOpsManagerMock; } - - @Override - PlatformCompat createPlatformCompat(Context context) { - return mPlatformCompat; - } }); return mService; } @@ -505,9 +498,6 @@ public class PowerManagerServiceTest { String packageName = "pkg.name"; when(mAppOpsManagerMock.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON, Binder.getCallingUid(), packageName)).thenReturn(MODE_ALLOWED); - when(mPlatformCompat.isChangeEnabledByPackageName( - eq(PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION), anyString(), - anyInt())).thenReturn(true); when(mContextSpy.checkCallingOrSelfPermission( android.Manifest.permission.TURN_SCREEN_ON)).thenReturn( PackageManager.PERMISSION_GRANTED); @@ -532,23 +522,6 @@ public class PowerManagerServiceTest { null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null); assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */); - - // Verify that on older platforms only the appOp is necessary and the permission isn't - // checked - when(mPlatformCompat.isChangeEnabledByPackageName( - eq(PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION), anyString(), - anyInt())).thenReturn(false); - when(mContextSpy.checkCallingOrSelfPermission( - android.Manifest.permission.TURN_SCREEN_ON)).thenReturn( - PackageManager.PERMISSION_DENIED); - forceSleep(); - assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP); - - flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP; - mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName, - null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null); - assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); - mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */); } @Test @@ -568,7 +541,7 @@ public class PowerManagerServiceTest { int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP; mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName, null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null); - if (PowerProperties.permissionless_turn_screen_on().orElse(true)) { + if (PowerProperties.permissionless_turn_screen_on().orElse(false)) { assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); } else { assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP); @@ -577,9 +550,6 @@ public class PowerManagerServiceTest { when(mAppOpsManagerMock.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON, Binder.getCallingUid(), packageName)).thenReturn(MODE_ALLOWED); - when(mPlatformCompat.isChangeEnabledByPackageName( - eq(PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION), anyString(), - anyInt())).thenReturn(true); when(mContextSpy.checkCallingOrSelfPermission( android.Manifest.permission.TURN_SCREEN_ON)).thenReturn( PackageManager.PERMISSION_DENIED); @@ -589,7 +559,7 @@ public class PowerManagerServiceTest { flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP; mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName, null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null); - if (PowerProperties.permissionless_turn_screen_on().orElse(true)) { + if (PowerProperties.permissionless_turn_screen_on().orElse(false)) { assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); } else { assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP); |