Merge "Use Launcher's widget picker to add widgets to the hub." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 705a4df..efd8578 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -430,10 +430,7 @@
aconfig_declarations {
name: "com.android.media.flags.bettertogether-aconfig",
package: "com.android.media.flags",
- srcs: [
- "media/java/android/media/flags/media_better_together.aconfig",
- "media/java/android/media/flags/fade_manager_configuration.aconfig",
- ],
+ srcs: ["media/java/android/media/flags/media_better_together.aconfig"],
}
java_aconfig_library {
diff --git a/Android.bp b/Android.bp
index 96d26cf..bb93048 100644
--- a/Android.bp
+++ b/Android.bp
@@ -97,6 +97,7 @@
// AIDL sources from external directories
":android.hardware.biometrics.common-V4-java-source",
":android.hardware.biometrics.fingerprint-V3-java-source",
+ ":android.hardware.biometrics.face-V4-java-source",
":android.hardware.gnss-V2-java-source",
":android.hardware.graphics.common-V3-java-source",
":android.hardware.keymaster-V4-java-source",
@@ -206,11 +207,13 @@
// framework_srcs. These have no or very limited dependency to the framework.
java_library {
name: "framework-internal-utils",
+ defaults: [
+ "android.hardware.power-java_static",
+ ],
static_libs: [
"apex_aidl_interface-java",
"packagemanager_aidl-java",
"framework-protos",
- "libtombstone_proto_java",
"updatable-driver-protos",
"ota_metadata_proto_java",
"android.hidl.base-V1.0-java",
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index d03bbd2..e7adf20 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -34,6 +34,7 @@
":ipconnectivity-proto-src",
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
+ ":libtombstone_proto-src",
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
],
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 03f3f0f..f330ad1 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -65,7 +65,7 @@
// depend on it.
java_genrule {
name: "framework-minus-apex.ravenwood",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
cmd: "cp $(in) $(out)",
srcs: [
":framework-minus-apex.ravenwood-base{ravenwood.jar}",
diff --git a/STABILITY_OWNERS b/STABILITY_OWNERS
deleted file mode 100644
index a7ecb4d..0000000
--- a/STABILITY_OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-gaillard@google.com
-
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 83db4cb..900c902 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1828,7 +1828,9 @@
/* system_measured_source_download_bytes */0,
/* system_measured_source_upload_bytes */ 0,
/* system_measured_calling_download_bytes */0,
- /* system_measured_calling_upload_bytes */ 0);
+ /* system_measured_calling_upload_bytes */ 0,
+ jobStatus.getJob().getIntervalMillis(),
+ jobStatus.getJob().getFlexMillis());
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2269,7 +2271,9 @@
/* system_measured_source_download_bytes */ 0,
/* system_measured_source_upload_bytes */ 0,
/* system_measured_calling_download_bytes */0,
- /* system_measured_calling_upload_bytes */ 0);
+ /* system_measured_calling_upload_bytes */ 0,
+ cancelled.getJob().getIntervalMillis(),
+ cancelled.getJob().getFlexMillis());
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 6449edc..3addf9f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -534,7 +534,9 @@
/* system_measured_source_download_bytes */ 0,
/* system_measured_source_upload_bytes */ 0,
/* system_measured_calling_download_bytes */ 0,
- /* system_measured_calling_upload_bytes */ 0);
+ /* system_measured_calling_upload_bytes */ 0,
+ job.getJob().getIntervalMillis(),
+ job.getJob().getFlexMillis());
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1616,7 +1618,9 @@
TrafficStats.getUidRxBytes(completedJob.getUid())
- mInitialDownloadedBytesFromCalling,
TrafficStats.getUidTxBytes(completedJob.getUid())
- - mInitialUploadedBytesFromCalling);
+ - mInitialUploadedBytesFromCalling,
+ completedJob.getJob().getIntervalMillis(),
+ completedJob.getJob().getFlexMillis());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index 913a76a..4d4e340 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -591,6 +591,16 @@
if (idle) {
newBucket = IDLE_BUCKET_CUTOFF;
reason = REASON_MAIN_FORCED_BY_USER;
+ final AppUsageHistory appHistory = getAppUsageHistory(packageName, userId,
+ elapsedRealtime);
+ // Wipe all expiry times that could raise the bucket on reevaluation.
+ if (appHistory.bucketExpiryTimesMs != null) {
+ for (int i = appHistory.bucketExpiryTimesMs.size() - 1; i >= 0; --i) {
+ if (appHistory.bucketExpiryTimesMs.keyAt(i) < newBucket) {
+ appHistory.bucketExpiryTimesMs.removeAt(i);
+ }
+ }
+ }
} else {
newBucket = STANDBY_BUCKET_ACTIVE;
// This is to pretend that the app was just used, don't freeze the state anymore.
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index bd5c006..e589c21 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -59,6 +59,7 @@
import static com.android.server.usage.AppIdleHistory.STANDBY_BUCKET_UNKNOWN;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -2146,6 +2147,15 @@
}
}
+ /**
+ * Flush the handler.
+ * Returns true if successfully flushed within the timeout, otherwise return false.
+ */
+ @VisibleForTesting
+ boolean flushHandler(@DurationMillisLong long timeoutMillis) {
+ return mHandler.runWithScissors(() -> {}, timeoutMillis);
+ }
+
@Override
public void flushToDisk() {
synchronized (mAppIdleLock) {
diff --git a/core/api/current.txt b/core/api/current.txt
index 12a6f74..008521a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -35874,19 +35874,19 @@
field public static final String NAMESPACE = "data2";
}
- public static final class ContactsContract.CommonDataKinds.Im implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
- method public static CharSequence getProtocolLabel(android.content.res.Resources, int, CharSequence);
- method public static int getProtocolLabelResource(int);
- method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
- method public static int getTypeLabelResource(int);
- field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/im";
- field public static final String CUSTOM_PROTOCOL = "data6";
+ @Deprecated public static final class ContactsContract.CommonDataKinds.Im implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
+ method @Deprecated public static CharSequence getProtocolLabel(android.content.res.Resources, int, CharSequence);
+ method @Deprecated public static int getProtocolLabelResource(int);
+ method @Deprecated public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
+ method @Deprecated public static int getTypeLabelResource(int);
+ field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/im";
+ field @Deprecated public static final String CUSTOM_PROTOCOL = "data6";
field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
field public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
field public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
- field public static final String PROTOCOL = "data5";
+ field @Deprecated public static final String PROTOCOL = "data5";
field @Deprecated public static final int PROTOCOL_AIM = 0; // 0x0
- field public static final int PROTOCOL_CUSTOM = -1; // 0xffffffff
+ field @Deprecated public static final int PROTOCOL_CUSTOM = -1; // 0xffffffff
field @Deprecated public static final int PROTOCOL_GOOGLE_TALK = 5; // 0x5
field @Deprecated public static final int PROTOCOL_ICQ = 6; // 0x6
field @Deprecated public static final int PROTOCOL_JABBER = 7; // 0x7
@@ -35895,9 +35895,9 @@
field @Deprecated public static final int PROTOCOL_QQ = 4; // 0x4
field @Deprecated public static final int PROTOCOL_SKYPE = 3; // 0x3
field @Deprecated public static final int PROTOCOL_YAHOO = 2; // 0x2
- field public static final int TYPE_HOME = 1; // 0x1
- field public static final int TYPE_OTHER = 3; // 0x3
- field public static final int TYPE_WORK = 2; // 0x2
+ field @Deprecated public static final int TYPE_HOME = 1; // 0x1
+ field @Deprecated public static final int TYPE_OTHER = 3; // 0x3
+ field @Deprecated public static final int TYPE_WORK = 2; // 0x2
}
public static final class ContactsContract.CommonDataKinds.Nickname implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
@@ -36012,17 +36012,17 @@
field public static final int TYPE_SPOUSE = 14; // 0xe
}
- public static final class ContactsContract.CommonDataKinds.SipAddress implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
- method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
- method public static int getTypeLabelResource(int);
- field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address";
+ @Deprecated public static final class ContactsContract.CommonDataKinds.SipAddress implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
+ method @Deprecated public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
+ method @Deprecated public static int getTypeLabelResource(int);
+ field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address";
field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
field public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
field public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
- field public static final String SIP_ADDRESS = "data1";
- field public static final int TYPE_HOME = 1; // 0x1
- field public static final int TYPE_OTHER = 3; // 0x3
- field public static final int TYPE_WORK = 2; // 0x2
+ field @Deprecated public static final String SIP_ADDRESS = "data1";
+ field @Deprecated public static final int TYPE_HOME = 1; // 0x1
+ field @Deprecated public static final int TYPE_OTHER = 3; // 0x3
+ field @Deprecated public static final int TYPE_WORK = 2; // 0x2
}
public static final class ContactsContract.CommonDataKinds.StructuredName implements android.provider.ContactsContract.DataColumnsWithJoins {
@@ -43403,6 +43403,7 @@
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT = "parameters_used_for_ntn_lte_signal_bar_int";
field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.hide_prefer_3g_item") public static final String KEY_PREFER_3G_VISIBILITY_BOOL = "prefer_3g_visibility_bool";
field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT = "premium_capability_maximum_daily_notification_count_int";
field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT = "premium_capability_maximum_monthly_notification_count_int";
field public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG = "premium_capability_network_setup_time_millis_long";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8ce3a8d..51e61e6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3350,7 +3350,7 @@
}
@FlaggedApi("android.companion.virtual.flags.virtual_camera") public interface VirtualCameraCallback {
- method public void onProcessCaptureRequest(int, long, @Nullable android.companion.virtual.camera.VirtualCameraMetadata);
+ method public default void onProcessCaptureRequest(int, long);
method public void onStreamClosed(int);
method public void onStreamConfigured(int, @NonNull android.view.Surface, @NonNull android.companion.virtual.camera.VirtualCameraStreamConfig);
}
@@ -3371,12 +3371,6 @@
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
}
- @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraMetadata implements android.os.Parcelable {
- method public int describeContents();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraMetadata> CREATOR;
- }
-
@FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraStreamConfig implements android.os.Parcelable {
ctor public VirtualCameraStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int);
method public int describeContents();
@@ -6478,6 +6472,7 @@
method public void clearAudioServerStateCallback();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int dispatchAudioFocusChangeWithFade(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy, @NonNull java.util.List<android.media.AudioFocusInfo>, @Nullable android.media.FadeManagerConfiguration);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getActiveAssistantServicesUids();
method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getAssistantServicesUids();
@@ -6677,6 +6672,69 @@
field public static final int CONTENT_ID_NONE = 0; // 0x0
}
+ @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") public final class FadeManagerConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.media.AudioAttributes> getAudioAttributesWithVolumeShaperConfigs();
+ method public long getFadeInDelayForOffenders();
+ method public long getFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method public long getFadeInDurationForUsage(int);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeInVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeInVolumeShaperConfigForUsage(int);
+ method public long getFadeOutDurationForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method public long getFadeOutDurationForUsage(int);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeOutVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeOutVolumeShaperConfigForUsage(int);
+ method public int getFadeState();
+ method @NonNull public java.util.List<java.lang.Integer> getFadeableUsages();
+ method @NonNull public java.util.List<android.media.AudioAttributes> getUnfadeableAudioAttributes();
+ method @NonNull public java.util.List<java.lang.Integer> getUnfadeableContentTypes();
+ method @NonNull public java.util.List<java.lang.Integer> getUnfadeablePlayerTypes();
+ method @NonNull public java.util.List<java.lang.Integer> getUnfadeableUids();
+ method public boolean isAudioAttributesUnfadeable(@NonNull android.media.AudioAttributes);
+ method public boolean isContentTypeUnfadeable(int);
+ method public boolean isFadeEnabled();
+ method public boolean isPlayerTypeUnfadeable(int);
+ method public boolean isUidUnfadeable(int);
+ method public boolean isUsageFadeable(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.FadeManagerConfiguration> CREATOR;
+ field public static final long DURATION_NOT_SET = 0L; // 0x0L
+ field public static final int FADE_STATE_DISABLED = 0; // 0x0
+ field public static final int FADE_STATE_ENABLED_AUTO = 2; // 0x2
+ field public static final int FADE_STATE_ENABLED_DEFAULT = 1; // 0x1
+ field public static final String TAG = "FadeManagerConfiguration";
+ field public static final int VOLUME_SHAPER_SYSTEM_FADE_ID = 2; // 0x2
+ }
+
+ public static final class FadeManagerConfiguration.Builder {
+ ctor public FadeManagerConfiguration.Builder();
+ ctor public FadeManagerConfiguration.Builder(long, long);
+ ctor public FadeManagerConfiguration.Builder(@NonNull android.media.FadeManagerConfiguration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addFadeableUsage(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableContentType(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableUid(int);
+ method @NonNull public android.media.FadeManagerConfiguration build();
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearFadeableUsage(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableContentType(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableUid(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDelayForOffenders(long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForUsage(int, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInVolumeShaperConfigForUsage(int, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutDurationForAudioAttributes(@NonNull android.media.AudioAttributes, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutDurationForUsage(int, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutVolumeShaperConfigForUsage(int, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeState(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeableUsages(@NonNull java.util.List<java.lang.Integer>);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableAudioAttributes(@NonNull java.util.List<android.media.AudioAttributes>);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableContentTypes(@NonNull java.util.List<java.lang.Integer>);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableUids(@NonNull java.util.List<java.lang.Integer>);
+ }
+
public class HwAudioSource {
method public boolean isPlaying();
method public void start();
@@ -6895,15 +6953,18 @@
public class AudioPolicy {
method public int attachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>);
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int clearFadeManagerConfigurationForFocusLoss();
method public android.media.AudioRecord createAudioRecordSink(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException;
method public android.media.AudioTrack createAudioTrackSource(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException;
method public int detachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>);
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public android.media.FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss();
method public int getFocusDuckingBehavior();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioFocusInfo> getFocusStack();
method public int getStatus();
method public boolean removeUidDeviceAffinity(int);
method public boolean removeUserIdDeviceAffinity(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean sendFocusLoss(@NonNull android.media.AudioFocusInfo) throws java.lang.IllegalStateException;
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int setFadeManagerConfigurationForFocusLoss(@NonNull android.media.FadeManagerConfiguration);
method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setRegistration(String);
method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 42daea2..aaeba66 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -369,11 +369,14 @@
}
public class NotificationManager {
+ method @FlaggedApi("android.app.modes_api") @NonNull public String addAutomaticZenRule(@NonNull android.app.AutomaticZenRule, boolean);
method public void cleanUpCallersAfter(long);
method public android.content.ComponentName getEffectsSuppressor();
method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
+ method @FlaggedApi("android.app.modes_api") public boolean removeAutomaticZenRule(@NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean);
+ method @FlaggedApi("android.app.modes_api") public boolean updateAutomaticZenRule(@NonNull String, @NonNull android.app.AutomaticZenRule, boolean);
method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel);
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index ea9bb39..37692d3 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -537,8 +537,8 @@
/**
* Returns whether the given user requires credential entry at this time. This is used to
- * intercept activity launches for locked work apps due to work challenge being triggered or
- * when the profile user is yet to be unlocked.
+ * intercept activity launches for apps corresponding to locked profiles due to separate
+ * challenge being triggered or when the profile user is yet to be unlocked.
*/
public abstract boolean shouldConfirmCredentials(@UserIdInt int userId);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 87c86df..287d2bd 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2909,7 +2909,7 @@
try {
return mPM.setPackagesSuspendedAsUser(packageNames, suspended, appExtras,
launcherExtras, dialogInfo, flags, mContext.getOpPackageName(),
- getUserId());
+ UserHandle.myUserId() /* suspendingUserId */, getUserId() /* targetUserId */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 9438571..b7db5f5 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -167,7 +167,7 @@
void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter);
int getInterruptionFilterFromListener(in INotificationListener token);
void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim);
- void setInterruptionFilter(String pkg, int interruptionFilter);
+ void setInterruptionFilter(String pkg, int interruptionFilter, boolean fromUser);
void updateNotificationChannelGroupFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user, in NotificationChannelGroup group);
void updateNotificationChannelFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user, in NotificationChannel channel);
@@ -205,11 +205,11 @@
@UnsupportedAppUsage
ZenModeConfig getZenModeConfig();
NotificationManager.Policy getConsolidatedNotificationPolicy();
- oneway void setZenMode(int mode, in Uri conditionId, String reason);
+ oneway void setZenMode(int mode, in Uri conditionId, String reason, boolean fromUser);
oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
boolean isNotificationPolicyAccessGranted(String pkg);
NotificationManager.Policy getNotificationPolicy(String pkg);
- void setNotificationPolicy(String pkg, in NotificationManager.Policy policy);
+ void setNotificationPolicy(String pkg, in NotificationManager.Policy policy, boolean fromUser);
boolean isNotificationPolicyAccessGrantedForPackage(String pkg);
void setNotificationPolicyAccessGranted(String pkg, boolean granted);
void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted);
@@ -217,12 +217,12 @@
Map<String, AutomaticZenRule> getAutomaticZenRules();
// TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined.
List<ZenModeConfig.ZenRule> getZenRules();
- String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg);
- boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule);
- boolean removeAutomaticZenRule(String id);
- boolean removeAutomaticZenRules(String packageName);
+ String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg, boolean fromUser);
+ boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule, boolean fromUser);
+ boolean removeAutomaticZenRule(String id, boolean fromUser);
+ boolean removeAutomaticZenRules(String packageName, boolean fromUser);
int getRuleInstanceCount(in ComponentName owner);
- void setAutomaticZenRuleState(String id, in Condition condition);
+ void setAutomaticZenRuleState(String id, in Condition condition, boolean fromUser);
byte[] getBackupPayload(int user);
void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a510c77..476232c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7733,11 +7733,12 @@
} else if (mPictureIcon.getType() == Icon.TYPE_BITMAP) {
// If the icon contains a bitmap, use the old extra so that listeners which look
// for that extra can still find the picture. Don't include the new extra in
- // that case, to avoid duplicating data.
+ // that case, to avoid duplicating data. Leave the unused extra set to null to avoid
+ // crashing apps that came to expect it to be present but null.
extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap());
- extras.remove(EXTRA_PICTURE_ICON);
+ extras.putParcelable(EXTRA_PICTURE_ICON, null);
} else {
- extras.remove(EXTRA_PICTURE);
+ extras.putParcelable(EXTRA_PICTURE, null);
extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon);
}
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index d23b16d..f76a45b 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1184,14 +1184,20 @@
*/
@UnsupportedAppUsage
public void setZenMode(int mode, Uri conditionId, String reason) {
+ setZenMode(mode, conditionId, reason, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ public void setZenMode(int mode, Uri conditionId, String reason, boolean fromUser) {
INotificationManager service = getService();
try {
- service.setZenMode(mode, conditionId, reason);
+ service.setZenMode(mode, conditionId, reason, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+
/**
* @hide
*/
@@ -1325,9 +1331,19 @@
* @return The id of the newly created rule; null if the rule could not be created.
*/
public String addAutomaticZenRule(AutomaticZenRule automaticZenRule) {
+ return addAutomaticZenRule(automaticZenRule, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @NonNull
+ public String addAutomaticZenRule(@NonNull AutomaticZenRule automaticZenRule,
+ boolean fromUser) {
INotificationManager service = getService();
try {
- return service.addAutomaticZenRule(automaticZenRule, mContext.getPackageName());
+ return service.addAutomaticZenRule(automaticZenRule,
+ mContext.getPackageName(), fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1347,9 +1363,17 @@
* @return Whether the rule was successfully updated.
*/
public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule) {
+ return updateAutomaticZenRule(id, automaticZenRule, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public boolean updateAutomaticZenRule(@NonNull String id,
+ @NonNull AutomaticZenRule automaticZenRule, boolean fromUser) {
INotificationManager service = getService();
try {
- return service.updateAutomaticZenRule(id, automaticZenRule);
+ return service.updateAutomaticZenRule(id, automaticZenRule, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1367,9 +1391,20 @@
* @param condition The new state of this rule
*/
public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition) {
+ if (Flags.modesApi()) {
+ setAutomaticZenRuleState(id, condition,
+ /* fromUser= */ condition.source == Condition.SOURCE_USER_ACTION);
+ } else {
+ setAutomaticZenRuleState(id, condition, /* fromUser= */ false);
+ }
+ }
+
+ /** @hide */
+ public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition,
+ boolean fromUser) {
INotificationManager service = getService();
try {
- service.setAutomaticZenRuleState(id, condition);
+ service.setAutomaticZenRuleState(id, condition, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1388,9 +1423,16 @@
* @return Whether the rule was successfully deleted.
*/
public boolean removeAutomaticZenRule(String id) {
+ return removeAutomaticZenRule(id, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public boolean removeAutomaticZenRule(@NonNull String id, boolean fromUser) {
INotificationManager service = getService();
try {
- return service.removeAutomaticZenRule(id);
+ return service.removeAutomaticZenRule(id, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1402,9 +1444,14 @@
* @hide
*/
public boolean removeAutomaticZenRules(String packageName) {
+ return removeAutomaticZenRules(packageName, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ public boolean removeAutomaticZenRules(String packageName, boolean fromUser) {
INotificationManager service = getService();
try {
- return service.removeAutomaticZenRules(packageName);
+ return service.removeAutomaticZenRules(packageName, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1685,10 +1732,15 @@
*/
// TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public void setNotificationPolicy(@NonNull Policy policy) {
+ setNotificationPolicy(policy, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ public void setNotificationPolicy(@NonNull Policy policy, boolean fromUser) {
checkRequired("policy", policy);
INotificationManager service = getService();
try {
- service.setNotificationPolicy(mContext.getOpPackageName(), policy);
+ service.setNotificationPolicy(mContext.getOpPackageName(), policy, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2685,9 +2737,16 @@
*/
// TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public final void setInterruptionFilter(@InterruptionFilter int interruptionFilter) {
+ setInterruptionFilter(interruptionFilter, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ public final void setInterruptionFilter(@InterruptionFilter int interruptionFilter,
+ boolean fromUser) {
final INotificationManager service = getService();
try {
- service.setInterruptionFilter(mContext.getOpPackageName(), interruptionFilter);
+ service.setInterruptionFilter(mContext.getOpPackageName(), interruptionFilter,
+ fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/usage/UsageEventsQuery.java b/core/java/android/app/usage/UsageEventsQuery.java
index 8c63d18..3cd2923 100644
--- a/core/java/android/app/usage/UsageEventsQuery.java
+++ b/core/java/android/app/usage/UsageEventsQuery.java
@@ -19,9 +19,11 @@
import android.annotation.CurrentTimeMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.usage.UsageEvents.Event;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.util.ArraySet;
import com.android.internal.util.ArrayUtils;
@@ -40,11 +42,13 @@
private final @CurrentTimeMillisLong long mBeginTimeMillis;
private final @CurrentTimeMillisLong long mEndTimeMillis;
private final @Event.EventType int[] mEventTypes;
+ private final @UserIdInt int mUserId;
private UsageEventsQuery(@NonNull Builder builder) {
mBeginTimeMillis = builder.mBeginTimeMillis;
mEndTimeMillis = builder.mEndTimeMillis;
mEventTypes = ArrayUtils.convertToIntArray(builder.mEventTypes);
+ mUserId = builder.mUserId;
}
private UsageEventsQuery(Parcel in) {
@@ -53,6 +57,7 @@
int eventTypesLength = in.readInt();
mEventTypes = new int[eventTypesLength];
in.readIntArray(mEventTypes);
+ mUserId = in.readInt();
}
/**
@@ -87,6 +92,11 @@
return eventTypeSet;
}
+ /** @hide */
+ public @UserIdInt int getUserId() {
+ return mUserId;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -98,6 +108,7 @@
dest.writeLong(mEndTimeMillis);
dest.writeInt(mEventTypes.length);
dest.writeIntArray(mEventTypes);
+ dest.writeInt(mUserId);
}
@NonNull
@@ -126,6 +137,7 @@
private final @CurrentTimeMillisLong long mBeginTimeMillis;
private final @CurrentTimeMillisLong long mEndTimeMillis;
private final ArraySet<Integer> mEventTypes = new ArraySet<>();
+ private @UserIdInt int mUserId = UserHandle.USER_NULL;
/**
* Constructor that specifies the period for which to return events.
@@ -169,5 +181,15 @@
}
return this;
}
+
+ /**
+ * Specifices the user id for the query.
+ * @param userId for whom the query should be performed.
+ * @hide
+ */
+ public @NonNull Builder setUserId(@UserIdInt int userId) {
+ mUserId = userId;
+ return this;
+ }
}
}
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
index fac44b5..44942d6 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
+++ b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,11 @@
package android.companion.virtual.camera;
import android.companion.virtual.camera.VirtualCameraStreamConfig;
-import android.companion.virtual.camera.VirtualCameraMetadata;
import android.view.Surface;
/**
- * Interface for the virtual camera service and system server to talk back to the virtual camera owner.
+ * Interface for the virtual camera service and system server to talk back to the virtual camera
+ * owner.
*
* @hide
*/
@@ -40,8 +40,7 @@
in VirtualCameraStreamConfig streamConfig);
/**
- * The client application is requesting a camera frame for the given streamId with the provided
- * metadata.
+ * The client application is requesting a camera frame for the given streamId and frameId.
*
* <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to
* this stream that was provided during the {@link #onStreamConfigured(int, Surface,
@@ -52,16 +51,14 @@
* VirtualCameraStreamConfig)}
* @param frameId The frameId that is being requested. Each request will have a different
* frameId, that will be increasing for each call with a particular streamId.
- * @param metadata The metadata requested for the frame. The virtual camera should do its best
- * to honor the requested metadata.
*/
- oneway void onProcessCaptureRequest(
- int streamId, long frameId, in VirtualCameraMetadata metadata);
+ oneway void onProcessCaptureRequest(int streamId, long frameId);
/**
* The stream previously configured when {@link #onStreamConfigured(int, Surface,
* VirtualCameraStreamConfig)} was called is now being closed and associated resources can be
- * freed. The Surface was disposed on the client side and should not be used anymore by the virtual camera owner
+ * freed. The Surface was disposed on the client side and should not be used anymore by the
+ * virtual camera owner.
*
* @param streamId The id of the stream that was closed.
*/
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
index a18ae03..5b658b8 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 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.
@@ -18,7 +18,6 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.companion.virtual.flags.Flags;
import android.view.Surface;
@@ -50,8 +49,7 @@
@NonNull VirtualCameraStreamConfig streamConfig);
/**
- * The client application is requesting a camera frame for the given streamId with the provided
- * metadata.
+ * The client application is requesting a camera frame for the given streamId and frameId.
*
* <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to
* this stream that was provided during the {@link #onStreamConfigured(int, Surface,
@@ -62,12 +60,8 @@
* VirtualCameraStreamConfig)}
* @param frameId The frameId that is being requested. Each request will have a different
* frameId, that will be increasing for each call with a particular streamId.
- * @param metadata The metadata requested for the frame. The virtual camera should do its best
- * to honor the requested metadata but the consumer won't be informed about the metadata set
- * for a particular frame. If null, the requested frame can be anything the producer sends.
*/
- void onProcessCaptureRequest(
- int streamId, long frameId, @Nullable VirtualCameraMetadata metadata);
+ default void onProcessCaptureRequest(int streamId, long frameId) {}
/**
* The stream previously configured when {@link #onStreamConfigured(int, Surface,
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index f1eb240..a939251 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 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.
@@ -224,9 +224,8 @@
}
@Override
- public void onProcessCaptureRequest(
- int streamId, long frameId, VirtualCameraMetadata metadata) {
- mExecutor.execute(() -> mCallback.onProcessCaptureRequest(streamId, frameId, metadata));
+ public void onProcessCaptureRequest(int streamId, long frameId) {
+ mExecutor.execute(() -> mCallback.onProcessCaptureRequest(streamId, frameId));
}
@Override
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java
deleted file mode 100644
index 1ba36d0..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2023 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.companion.virtual.camera;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.companion.virtual.flags.Flags;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Data structure used to store camera metadata compatible with VirtualCamera.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
-public final class VirtualCameraMetadata implements Parcelable {
-
- /** @hide */
- public VirtualCameraMetadata(@NonNull Parcel in) {}
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {}
-
- @NonNull
- public static final Creator<VirtualCameraMetadata> CREATOR =
- new Creator<>() {
- @Override
- @NonNull
- public VirtualCameraMetadata createFromParcel(Parcel in) {
- return new VirtualCameraMetadata(in);
- }
-
- @Override
- @NonNull
- public VirtualCameraMetadata[] newArray(int size) {
- return new VirtualCameraMetadata[size];
- }
- };
-}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
index e198821..1272f16 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
@@ -24,6 +24,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
+
/**
* The configuration of a single virtual camera stream.
*
@@ -98,6 +100,19 @@
return mHeight;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ VirtualCameraStreamConfig that = (VirtualCameraStreamConfig) o;
+ return mWidth == that.mWidth && mHeight == that.mHeight && mFormat == that.mFormat;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mWidth, mHeight, mFormat);
+ }
+
/** Returns the {@link ImageFormat} of this stream. */
@ImageFormat.Format
public int getFormat() {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index d13d962..12da665 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1393,6 +1393,18 @@
public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L;
/**
+ * Enables {@link #SCREEN_ORIENTATION_USER} which overrides any orientation requested
+ * by the activity. Fixed orientation apps can be overridden to fullscreen on large
+ * screen devices with ignoreOrientationRequest enabled with this override.
+ *
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_ANY_ORIENTATION_TO_USER = 310816437L;
+
+ /**
* Compares activity window layout min width/height with require space for multi window to
* determine if it can be put into multi window mode.
*/
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 98623de..6dc8d47 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -300,7 +300,8 @@
String[] setPackagesSuspendedAsUser(in String[] packageNames, boolean suspended,
in PersistableBundle appExtras, in PersistableBundle launcherExtras,
- in SuspendDialogInfo dialogInfo, int flags, String callingPackage, int userId);
+ in SuspendDialogInfo dialogInfo, int flags, String suspendingPackage,
+ int suspendingUserId, int targetUserId);
String[] getUnsuspendablePackagesForUser(in String[] packageNames, int userId);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 82a8c11..a863870 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -10000,6 +10000,9 @@
* device administrators or apps holding {@link android.Manifest.permission#MANAGE_USERS} or
* {@link android.Manifest.permission#SUSPEND_APPS}.
*
+ * <p>
+ * <strong>Note:</strong>This API doesn't support cross user suspension and should only be used
+ * for testing.
* @param suspendedPackage The package that has been suspended.
* @return Name of the package that suspended the given package. Returns {@code null} if the
* given package is not currently suspended and the platform package name - i.e.
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index b04b7ba..60d5c14 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -115,3 +115,11 @@
description: "Feature flag to fix duplicated PackageManager flag values"
bug: "314815969"
}
+
+flag {
+ name: "provide_info_of_apk_in_apex"
+ namespace: "package_manager_service"
+ description: "Feature flag to provide the information of APK-in-APEX"
+ bug: "306329516"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 57025c2..9a1796f 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -56,3 +56,11 @@
description: "Add support to lock private space automatically after a time period"
bug: "303201022"
}
+
+flag {
+ name: "avatar_sync"
+ namespace: "multiuser"
+ description: "Implement new Avatar Picker outside of SetttingsLib with ability to select avatars from Google Account and synchronise to any changes."
+ bug: "296829976"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index 52bba14..870546a 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -22,14 +22,8 @@
import android.content.res.Resources;
import android.database.sqlite.SQLiteClosable;
import android.database.sqlite.SQLiteException;
-import android.os.Binder;
-import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.Process;
-import android.util.Log;
-import android.util.LongSparseArray;
-import android.util.SparseIntArray;
import dalvik.annotation.optimization.FastNative;
import dalvik.system.CloseGuard;
@@ -44,6 +38,9 @@
* consumer for reading.
* </p>
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+ "com.android.hoststubgen.nativesubstitution.CursorWindow_host")
public class CursorWindow extends SQLiteClosable implements Parcelable {
private static final String STATS_TAG = "CursorWindowStats";
@@ -61,7 +58,7 @@
private int mStartPos;
private final String mName;
- private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final CloseGuard mCloseGuard;
// May throw CursorWindowAllocationException
private static native long nativeCreate(String name, int cursorWindowSize);
@@ -147,7 +144,7 @@
if (mWindowPtr == 0) {
throw new AssertionError(); // Not possible, the native code won't return it.
}
- mCloseGuard.open("CursorWindow.close");
+ mCloseGuard = createCloseGuard();
}
/**
@@ -175,7 +172,18 @@
throw new AssertionError(); // Not possible, the native code won't return it.
}
mName = nativeGetName(mWindowPtr);
- mCloseGuard.open("CursorWindow.close");
+ mCloseGuard = createCloseGuard();
+ }
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private CloseGuard createCloseGuard() {
+ final CloseGuard closeGuard = CloseGuard.get();
+ closeGuard.open("CursorWindow.close");
+ return closeGuard;
+ }
+
+ private CloseGuard createCloseGuard$ravenwood() {
+ return null;
}
@Override
@@ -749,6 +757,7 @@
dispose();
}
+ @android.ravenwood.annotation.RavenwoodReplace
private static int getCursorWindowSize() {
if (sCursorWindowSize < 0) {
// The cursor window size. resource xml file specifies the value in kB.
@@ -759,6 +768,10 @@
return sCursorWindowSize;
}
+ private static int getCursorWindowSize$ravenwood() {
+ return 1024;
+ }
+
@Override
public String toString() {
return getName() + " {" + Long.toHexString(mWindowPtr) + "}";
diff --git a/core/java/android/database/SQLException.java b/core/java/android/database/SQLException.java
index 3402026..4026f20 100644
--- a/core/java/android/database/SQLException.java
+++ b/core/java/android/database/SQLException.java
@@ -19,6 +19,7 @@
/**
* An exception that indicates there was an error with SQL parsing or execution.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class SQLException extends RuntimeException {
public SQLException() {
}
diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index 2fca729..8eb512a 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -25,6 +25,7 @@
*
* This class implements a primitive reference counting scheme for database objects.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public abstract class SQLiteClosable implements Closeable {
@UnsupportedAppUsage
private int mReferenceCount = 1;
diff --git a/core/java/android/database/sqlite/SQLiteException.java b/core/java/android/database/sqlite/SQLiteException.java
index a1d9c9f..531b40a 100644
--- a/core/java/android/database/sqlite/SQLiteException.java
+++ b/core/java/android/database/sqlite/SQLiteException.java
@@ -21,6 +21,7 @@
/**
* A SQLite exception that indicates there was an error with SQL parsing or execution.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class SQLiteException extends SQLException {
public SQLiteException() {
}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index f71e853..ffd7212 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -715,6 +715,14 @@
boolean startOffload();
void stopOffload();
+
+ /**
+ * Called when {@link DisplayOffloadSession} tries to block screen turning on.
+ *
+ * @param unblocker a {@link Runnable} executed upon all work required before screen turning
+ * on is done.
+ */
+ void onBlockingScreenOn(Runnable unblocker);
}
/** A session token that associates a internal display with a {@link DisplayOffloader}. */
@@ -734,6 +742,15 @@
*/
void updateBrightness(float brightness);
+ /**
+ * Called while display is turning to state ON to leave a small period for displayoffload
+ * session to finish some work.
+ *
+ * @param unblocker a {@link Runnable} used by displayoffload session to notify
+ * {@link DisplayManager} that it can continue turning screen on.
+ */
+ boolean blockScreenOn(Runnable unblocker);
+
/** Returns whether displayoffload supports the given display state. */
static boolean isSupportedOffloadState(int displayState) {
return Display.isSuspendedState(displayState);
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl b/core/java/android/hardware/face/FaceSensorConfigurations.aidl
similarity index 74%
rename from core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
rename to core/java/android/hardware/face/FaceSensorConfigurations.aidl
index 6c1f0fc..26367b3 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.aidl
@@ -13,12 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.hardware.face;
-package android.companion.virtual.camera;
-
-/**
- * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with
- * VirtualCamera.
- * @hide
- */
-parcelable VirtualCameraMetadata;
+parcelable FaceSensorConfigurations;
\ No newline at end of file
diff --git a/core/java/android/hardware/face/FaceSensorConfigurations.java b/core/java/android/hardware/face/FaceSensorConfigurations.java
new file mode 100644
index 0000000..6ef692f
--- /dev/null
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2023 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.face;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Provides the sensor props for face sensor, if available.
+ * @hide
+ */
+public class FaceSensorConfigurations implements Parcelable {
+ private static final String TAG = "FaceSensorConfigurations";
+
+ private final boolean mResetLockoutRequiresChallenge;
+ private final Map<String, SensorProps[]> mSensorPropsMap;
+
+ public static final Creator<FaceSensorConfigurations> CREATOR =
+ new Creator<FaceSensorConfigurations>() {
+ @Override
+ public FaceSensorConfigurations createFromParcel(Parcel in) {
+ return new FaceSensorConfigurations(in);
+ }
+
+ @Override
+ public FaceSensorConfigurations[] newArray(int size) {
+ return new FaceSensorConfigurations[size];
+ }
+ };
+
+ public FaceSensorConfigurations(boolean resetLockoutRequiresChallenge) {
+ mResetLockoutRequiresChallenge = resetLockoutRequiresChallenge;
+ mSensorPropsMap = new HashMap<>();
+ }
+
+ protected FaceSensorConfigurations(Parcel in) {
+ mResetLockoutRequiresChallenge = in.readByte() != 0;
+ mSensorPropsMap = in.readHashMap(null, String.class, SensorProps[].class);
+ }
+
+ /**
+ * Process AIDL instances to extract sensor props and add it to the sensor map.
+ * @param aidlInstances available face AIDL instances
+ * @param getIFace function that provides the daemon for the specific instance
+ */
+ public void addAidlConfigs(@NonNull String[] aidlInstances,
+ @NonNull Function<String, IFace> getIFace) {
+ for (String aidlInstance : aidlInstances) {
+ final String fqName = IFace.DESCRIPTOR + "/" + aidlInstance;
+ IFace face = getIFace.apply(fqName);
+ try {
+ if (face != null) {
+ mSensorPropsMap.put(aidlInstance, face.getSensorProps());
+ } else {
+ Slog.e(TAG, "Unable to get declared service: " + fqName);
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, "Unable to get sensor properties!");
+ }
+ }
+ }
+
+ /**
+ * Parse through HIDL configuration and add it to the sensor map.
+ */
+ public void addHidlConfigs(@NonNull String[] hidlConfigStrings,
+ @NonNull Context context) {
+ final List<HidlFaceSensorConfig> hidlFaceSensorConfigs = new ArrayList<>();
+ for (String hidlConfig: hidlConfigStrings) {
+ final HidlFaceSensorConfig hidlFaceSensorConfig = new HidlFaceSensorConfig();
+ try {
+ hidlFaceSensorConfig.parse(hidlConfig, context);
+ } catch (Exception e) {
+ Log.e(TAG, "HIDL sensor configuration format is incorrect.");
+ continue;
+ }
+ if (hidlFaceSensorConfig.getModality() == TYPE_FACE) {
+ hidlFaceSensorConfigs.add(hidlFaceSensorConfig);
+ }
+ }
+ final String hidlHalInstanceName = "defaultHIDL";
+ mSensorPropsMap.put(hidlHalInstanceName, hidlFaceSensorConfigs.toArray(
+ new SensorProps[hidlFaceSensorConfigs.size()]));
+ }
+
+ /**
+ * Returns true if any face sensors have been added.
+ */
+ public boolean hasSensorConfigurations() {
+ return mSensorPropsMap.size() > 0;
+ }
+
+ /**
+ * Returns true if there is only a single face sensor configuration available.
+ */
+ public boolean isSingleSensorConfigurationPresent() {
+ return mSensorPropsMap.size() == 1;
+ }
+
+ /**
+ * Return sensor props for the given instance. If instance is not available,
+ * then null is returned.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPairForInstance(String instance) {
+ if (mSensorPropsMap.containsKey(instance)) {
+ return new Pair<>(instance, mSensorPropsMap.get(instance));
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the first pair of instance and sensor props, which does not correspond to the given
+ * If instance is not available, then null is returned.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPairNotForInstance(String instance) {
+ Optional<String> notAVirtualInstance = mSensorPropsMap.keySet().stream().filter(
+ (instanceName) -> !instanceName.equals(instance)).findFirst();
+ return notAVirtualInstance.map(this::getSensorPairForInstance).orElseGet(
+ this::getSensorPair);
+ }
+
+ /**
+ * Returns the first pair of instance and sensor props that has been added to the map.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPair() {
+ Optional<String> optionalInstance = mSensorPropsMap.keySet().stream().findFirst();
+ return optionalInstance.map(this::getSensorPairForInstance).orElse(null);
+
+ }
+
+ public boolean getResetLockoutRequiresChallenge() {
+ return mResetLockoutRequiresChallenge;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeByte((byte) (mResetLockoutRequiresChallenge ? 1 : 0));
+ dest.writeMap(mSensorPropsMap);
+ }
+}
diff --git a/core/java/android/hardware/face/HidlFaceSensorConfig.java b/core/java/android/hardware/face/HidlFaceSensorConfig.java
new file mode 100644
index 0000000..cab146d
--- /dev/null
+++ b/core/java/android/hardware/face/HidlFaceSensorConfig.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.face;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.common.SensorStrength;
+import android.hardware.biometrics.face.SensorProps;
+
+import com.android.internal.R;
+
+/**
+ * Parse HIDL face sensor config and map it to SensorProps.aidl to match AIDL.
+ * See core/res/res/values/config.xml config_biometric_sensors
+ * @hide
+ */
+public final class HidlFaceSensorConfig extends SensorProps {
+ private int mSensorId;
+ private int mModality;
+ private int mStrength;
+
+ /**
+ * Parse through the config string and map it to SensorProps.aidl.
+ * @throws IllegalArgumentException when config string has unexpected format
+ */
+ public void parse(@NonNull String config, @NonNull Context context)
+ throws IllegalArgumentException {
+ final String[] elems = config.split(":");
+ if (elems.length < 3) {
+ throw new IllegalArgumentException();
+ }
+ mSensorId = Integer.parseInt(elems[0]);
+ mModality = Integer.parseInt(elems[1]);
+ mStrength = Integer.parseInt(elems[2]);
+ mapHidlToAidlFaceSensorConfigurations(context);
+ }
+
+ @BiometricAuthenticator.Modality
+ public int getModality() {
+ return mModality;
+ }
+
+ private void mapHidlToAidlFaceSensorConfigurations(@NonNull Context context) {
+ commonProps = new CommonProps();
+ commonProps.sensorId = mSensorId;
+ commonProps.sensorStrength = authenticatorStrengthToPropertyStrength(mStrength);
+ halControlsPreview = context.getResources().getBoolean(
+ R.bool.config_faceAuthSupportsSelfIllumination);
+ commonProps.maxEnrollmentsPerUser = context.getResources().getInteger(
+ R.integer.config_faceMaxTemplatesPerUser);
+ commonProps.componentInfo = null;
+ supportsDetectInteraction = false;
+ }
+
+ private byte authenticatorStrengthToPropertyStrength(
+ @BiometricManager.Authenticators.Types int strength) {
+ switch (strength) {
+ case BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE:
+ return SensorStrength.CONVENIENCE;
+ case BiometricManager.Authenticators.BIOMETRIC_WEAK:
+ return SensorStrength.WEAK;
+ case BiometricManager.Authenticators.BIOMETRIC_STRONG:
+ return SensorStrength.STRONG;
+ default:
+ throw new IllegalArgumentException("Unknown strength: " + strength);
+ }
+ }
+}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 7080133..0096877 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -26,6 +26,7 @@
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.FaceSensorConfigurations;
import android.view.Surface;
/**
@@ -167,6 +168,10 @@
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
void registerAuthenticators(in List<FaceSensorPropertiesInternal> hidlSensors);
+ //Register all available face sensors.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void registerAuthenticatorsLegacy(in FaceSensorConfigurations faceSensorConfigurations);
+
// Adds a callback which gets called when the service registers all of the face
// authenticators. The callback is automatically removed after it's invoked.
void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl
similarity index 74%
copy from core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
copy to core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl
index 6c1f0fc..ebb05dc 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl
@@ -13,12 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.hardware.fingerprint;
-package android.companion.virtual.camera;
-
-/**
- * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with
- * VirtualCamera.
- * @hide
- */
-parcelable VirtualCameraMetadata;
+parcelable FingerprintSensorConfigurations;
\ No newline at end of file
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
new file mode 100644
index 0000000..f214494a
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 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.fingerprint;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Provides the sensor props for fingerprint sensor, if available.
+ * @hide
+ */
+
+public class FingerprintSensorConfigurations implements Parcelable {
+ private static final String TAG = "FingerprintSensorConfigurations";
+
+ private final Map<String, SensorProps[]> mSensorPropsMap;
+ private final boolean mResetLockoutRequiresHardwareAuthToken;
+
+ public static final Creator<FingerprintSensorConfigurations> CREATOR =
+ new Creator<>() {
+ @Override
+ public FingerprintSensorConfigurations createFromParcel(Parcel in) {
+ return new FingerprintSensorConfigurations(in);
+ }
+
+ @Override
+ public FingerprintSensorConfigurations[] newArray(int size) {
+ return new FingerprintSensorConfigurations[size];
+ }
+ };
+
+ public FingerprintSensorConfigurations(boolean resetLockoutRequiresHardwareAuthToken) {
+ mResetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
+ mSensorPropsMap = new HashMap<>();
+ }
+
+ /**
+ * Process AIDL instances to extract sensor props and add it to the sensor map.
+ * @param aidlInstances available face AIDL instances
+ * @param getIFingerprint function that provides the daemon for the specific instance
+ */
+ public void addAidlSensors(@NonNull String[] aidlInstances,
+ @NonNull Function<String, IFingerprint> getIFingerprint) {
+ for (String aidlInstance : aidlInstances) {
+ try {
+ final String fqName = IFingerprint.DESCRIPTOR + "/" + aidlInstance;
+ final IFingerprint fp = getIFingerprint.apply(fqName);
+ if (fp != null) {
+ SensorProps[] props = fp.getSensorProps();
+ mSensorPropsMap.put(aidlInstance, props);
+ } else {
+ Log.d(TAG, "IFingerprint null for instance " + aidlInstance);
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, "Unable to get sensor properties!");
+ }
+ }
+ }
+
+ /**
+ * Parse through HIDL configuration and add it to the sensor map.
+ */
+ public void addHidlSensors(@NonNull String[] hidlConfigStrings,
+ @NonNull Context context) {
+ final List<HidlFingerprintSensorConfig> hidlFingerprintSensorConfigs = new ArrayList<>();
+ for (String hidlConfigString : hidlConfigStrings) {
+ final HidlFingerprintSensorConfig hidlFingerprintSensorConfig =
+ new HidlFingerprintSensorConfig();
+ try {
+ hidlFingerprintSensorConfig.parse(hidlConfigString, context);
+ } catch (Exception e) {
+ Log.e(TAG, "HIDL sensor configuration format is incorrect.");
+ continue;
+ }
+ if (hidlFingerprintSensorConfig.getModality() == TYPE_FINGERPRINT) {
+ hidlFingerprintSensorConfigs.add(hidlFingerprintSensorConfig);
+ }
+ }
+ final String hidlHalInstanceName = "defaultHIDL";
+ mSensorPropsMap.put(hidlHalInstanceName,
+ hidlFingerprintSensorConfigs.toArray(
+ new HidlFingerprintSensorConfig[hidlFingerprintSensorConfigs.size()]));
+ }
+
+ protected FingerprintSensorConfigurations(Parcel in) {
+ mResetLockoutRequiresHardwareAuthToken = in.readByte() != 0;
+ mSensorPropsMap = in.readHashMap(null /* loader */, String.class, SensorProps[].class);
+ }
+
+ /**
+ * Returns true if any fingerprint sensors have been added.
+ */
+ public boolean hasSensorConfigurations() {
+ return mSensorPropsMap.size() > 0;
+ }
+
+ /**
+ * Returns true if there is only a single fingerprint sensor configuration available.
+ */
+ public boolean isSingleSensorConfigurationPresent() {
+ return mSensorPropsMap.size() == 1;
+ }
+
+ /**
+ * Return sensor props for the given instance. If instance is not available,
+ * then null is returned.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPairForInstance(String instance) {
+ if (mSensorPropsMap.containsKey(instance)) {
+ return new Pair<>(instance, mSensorPropsMap.get(instance));
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the first pair of instance and sensor props, which does not correspond to the given
+ * If instance is not available, then null is returned.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPairNotForInstance(String instance) {
+ Optional<String> notAVirtualInstance = mSensorPropsMap.keySet().stream().filter(
+ (instanceName) -> !instanceName.equals(instance)).findFirst();
+ return notAVirtualInstance.map(this::getSensorPairForInstance).orElseGet(
+ this::getSensorPair);
+ }
+
+ /**
+ * Returns the first pair of instance and sensor props that has been added to the map.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPair() {
+ Optional<String> optionalInstance = mSensorPropsMap.keySet().stream().findFirst();
+ return optionalInstance.map(this::getSensorPairForInstance).orElse(null);
+
+ }
+
+ public boolean getResetLockoutRequiresHardwareAuthToken() {
+ return mResetLockoutRequiresHardwareAuthToken;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeByte((byte) (mResetLockoutRequiresHardwareAuthToken ? 1 : 0));
+ dest.writeMap(mSensorPropsMap);
+ }
+}
diff --git a/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java b/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java
new file mode 100644
index 0000000..d481153
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 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.fingerprint;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.common.SensorStrength;
+import android.hardware.biometrics.fingerprint.FingerprintSensorType;
+import android.hardware.biometrics.fingerprint.SensorLocation;
+import android.hardware.biometrics.fingerprint.SensorProps;
+
+import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * Parse HIDL fingerprint sensor config and map it to SensorProps.aidl to match AIDL.
+ * See core/res/res/values/config.xml config_biometric_sensors
+ * @hide
+ */
+public final class HidlFingerprintSensorConfig extends SensorProps {
+ private int mSensorId;
+ private int mModality;
+ private int mStrength;
+
+ /**
+ * Parse through the config string and map it to SensorProps.aidl.
+ * @throws IllegalArgumentException when config string has unexpected format
+ */
+ public void parse(@NonNull String config, @NonNull Context context)
+ throws IllegalArgumentException {
+ final String[] elems = config.split(":");
+ if (elems.length < 3) {
+ throw new IllegalArgumentException();
+ }
+ mSensorId = Integer.parseInt(elems[0]);
+ mModality = Integer.parseInt(elems[1]);
+ mStrength = Integer.parseInt(elems[2]);
+ mapHidlToAidlSensorConfiguration(context);
+ }
+
+ @BiometricAuthenticator.Modality
+ public int getModality() {
+ return mModality;
+ }
+
+ private void mapHidlToAidlSensorConfiguration(@NonNull Context context) {
+ commonProps = new CommonProps();
+ commonProps.componentInfo = null;
+ commonProps.sensorId = mSensorId;
+ commonProps.sensorStrength = authenticatorStrengthToPropertyStrength(mStrength);
+ commonProps.maxEnrollmentsPerUser = context.getResources().getInteger(
+ R.integer.config_fingerprintMaxTemplatesPerUser);
+ halControlsIllumination = false;
+ sensorLocations = new SensorLocation[1];
+
+ final int[] udfpsProps = context.getResources().getIntArray(
+ com.android.internal.R.array.config_udfps_sensor_props);
+ final boolean isUdfps = !ArrayUtils.isEmpty(udfpsProps);
+ // config_is_powerbutton_fps indicates whether device has a power button fingerprint sensor.
+ final boolean isPowerbuttonFps = context.getResources().getBoolean(
+ R.bool.config_is_powerbutton_fps);
+
+ if (isUdfps) {
+ sensorType = FingerprintSensorType.UNKNOWN;
+ } else if (isPowerbuttonFps) {
+ sensorType = FingerprintSensorType.POWER_BUTTON;
+ } else {
+ sensorType = FingerprintSensorType.REAR;
+ }
+
+ if (isUdfps && udfpsProps.length == 3) {
+ setSensorLocation(udfpsProps[0], udfpsProps[1], udfpsProps[2]);
+ } else {
+ setSensorLocation(540 /* sensorLocationX */, 1636 /* sensorLocationY */,
+ 130 /* sensorRadius */);
+ }
+
+ }
+
+ private void setSensorLocation(int sensorLocationX,
+ int sensorLocationY, int sensorRadius) {
+ sensorLocations[0] = new SensorLocation();
+ sensorLocations[0].display = "";
+ sensorLocations[0].sensorLocationX = sensorLocationX;
+ sensorLocations[0].sensorLocationY = sensorLocationY;
+ sensorLocations[0].sensorRadius = sensorRadius;
+ }
+
+ private byte authenticatorStrengthToPropertyStrength(
+ @BiometricManager.Authenticators.Types int strength) {
+ switch (strength) {
+ case BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE:
+ return SensorStrength.CONVENIENCE;
+ case BiometricManager.Authenticators.BIOMETRIC_WEAK:
+ return SensorStrength.WEAK;
+ case BiometricManager.Authenticators.BIOMETRIC_STRONG:
+ return SensorStrength.STRONG;
+ default:
+ throw new IllegalArgumentException("Unknown strength: " + strength);
+ }
+ }
+}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index f594c00..606b171 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -31,6 +31,7 @@
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import java.util.List;
/**
@@ -173,6 +174,10 @@
@EnforcePermission("MANAGE_FINGERPRINT")
void removeClientActiveCallback(IFingerprintClientActiveCallback callback);
+ //Register all available fingerprint sensors.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void registerAuthenticatorsLegacy(in FingerprintSensorConfigurations fingerprintSensorConfigurations);
+
// Registers all HIDL and AIDL sensors. Only HIDL sensor properties need to be provided, because
// AIDL sensor properties are retrieved directly from the available HALs. If no HIDL HALs exist,
// hidlSensors must be non-null and empty. See AuthService.java
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 4bfff16..7d00b80 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -6911,7 +6911,11 @@
* <td></td>
* </tr>
* </table>
+ *
+ * @deprecated This field may not be well supported by some contacts apps and is discouraged
+ * to use.
*/
+ @Deprecated
public static final class Im implements DataColumnsWithJoins, CommonColumns, ContactCounts {
/**
* This utility class cannot be instantiated
@@ -7721,7 +7725,11 @@
* <td></td>
* </tr>
* </table>
+ *
+ * @deprecated This field may not be well supported by some contacts apps and is discouraged
+ * to use.
*/
+ @Deprecated
public static final class SipAddress implements DataColumnsWithJoins, CommonColumns,
ContactCounts {
/**
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 875031f..23c8393 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -960,8 +960,8 @@
mKeyphraseMetadata = new KeyphraseMetadata(1, mText, fakeSupportedLocales,
AlwaysOnHotwordDetector.RECOGNITION_MODE_VOICE_TRIGGER);
}
+ notifyStateChangedLocked();
}
- notifyStateChanged(availability);
}
/**
@@ -1371,8 +1371,8 @@
mAvailability = STATE_INVALID;
mIsAvailabilityOverriddenByTestApi = false;
+ notifyStateChangedLocked();
}
- notifyStateChanged(STATE_INVALID);
super.destroy();
}
@@ -1402,8 +1402,6 @@
*/
// TODO(b/281608561): remove the enrollment flow from AlwaysOnHotwordDetector
void onSoundModelsChanged() {
- boolean notifyError = false;
-
synchronized (mLock) {
if (mAvailability == STATE_INVALID
|| mAvailability == STATE_HARDWARE_UNAVAILABLE
@@ -1444,9 +1442,6 @@
// calling stopRecognition where there is no started session.
Log.w(TAG, "Failed to stop recognition after enrollment update: code="
+ result);
-
- // Execute a refresh availability task - which should then notify of a change.
- new RefreshAvailabilityTask().execute();
} catch (Exception e) {
Slog.w(TAG, "Failed to stop recognition after enrollment update", e);
if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
@@ -1455,14 +1450,14 @@
+ Log.getStackTraceString(e),
FailureSuggestedAction.RECREATE_DETECTOR));
} else {
- notifyError = true;
+ updateAndNotifyStateChangedLocked(STATE_ERROR);
}
+ return;
}
}
- }
- if (notifyError) {
- updateAndNotifyStateChanged(STATE_ERROR);
+ // Execute a refresh availability task - which should then notify of a change.
+ new RefreshAvailabilityTask().execute();
}
}
@@ -1578,11 +1573,10 @@
}
}
- private void updateAndNotifyStateChanged(int availability) {
- synchronized (mLock) {
- updateAvailabilityLocked(availability);
- }
- notifyStateChanged(availability);
+ @GuardedBy("mLock")
+ private void updateAndNotifyStateChangedLocked(int availability) {
+ updateAvailabilityLocked(availability);
+ notifyStateChangedLocked();
}
@GuardedBy("mLock")
@@ -1596,17 +1590,17 @@
}
}
- private void notifyStateChanged(int newAvailability) {
+ @GuardedBy("mLock")
+ private void notifyStateChangedLocked() {
Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
- message.arg1 = newAvailability;
+ message.arg1 = mAvailability;
message.sendToTarget();
}
+ @GuardedBy("mLock")
private void sendUnknownFailure(String failureMessage) {
- synchronized (mLock) {
- // update but do not call onAvailabilityChanged callback for STATE_ERROR
- updateAvailabilityLocked(STATE_ERROR);
- }
+ // update but do not call onAvailabilityChanged callback for STATE_ERROR
+ updateAvailabilityLocked(STATE_ERROR);
Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, failureMessage).sendToTarget();
}
@@ -1822,17 +1816,19 @@
availability = STATE_KEYPHRASE_UNENROLLED;
}
}
+ updateAndNotifyStateChangedLocked(availability);
}
- updateAndNotifyStateChanged(availability);
} catch (Exception e) {
// Any exception here not caught will crash the process because AsyncTask does not
// bubble up the exceptions to the client app, so we must propagate it to the app.
Slog.w(TAG, "Failed to refresh availability", e);
- if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
- sendUnknownFailure(
- "Failed to refresh availability: " + Log.getStackTraceString(e));
- } else {
- updateAndNotifyStateChanged(STATE_ERROR);
+ synchronized (mLock) {
+ if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
+ sendUnknownFailure(
+ "Failed to refresh availability: " + Log.getStackTraceString(e));
+ } else {
+ updateAndNotifyStateChangedLocked(STATE_ERROR);
+ }
}
}
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index b7d9705..adc54f5 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -94,7 +94,9 @@
*/
public void updateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
- mInitializationDelegate.updateState(options, sharedMemory);
+ synchronized (mInitializationDelegate.getLock()) {
+ mInitializationDelegate.updateState(options, sharedMemory);
+ }
}
@@ -116,18 +118,21 @@
if (DEBUG) {
Slog.i(TAG, "#startRecognition");
}
- // check if the detector is active with the initialization delegate
- mInitializationDelegate.startRecognition();
+ synchronized (mInitializationDelegate.getLock()) {
+ // check if the detector is active with the initialization delegate
+ mInitializationDelegate.startRecognition();
- try {
- mManagerService.startPerceiving(new BinderCallback(mExecutor, mCallback));
- } catch (SecurityException e) {
- Slog.e(TAG, "startRecognition failed: " + e);
- return false;
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ try {
+ mManagerService.startPerceiving(new BinderCallback(
+ mExecutor, mCallback, mInitializationDelegate.getLock()));
+ } catch (SecurityException e) {
+ Slog.e(TAG, "startRecognition failed: " + e);
+ return false;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return true;
}
- return true;
}
/**
@@ -140,15 +145,17 @@
if (DEBUG) {
Slog.i(TAG, "#stopRecognition");
}
- // check if the detector is active with the initialization delegate
- mInitializationDelegate.startRecognition();
+ synchronized (mInitializationDelegate.getLock()) {
+ // check if the detector is active with the initialization delegate
+ mInitializationDelegate.stopRecognition();
- try {
- mManagerService.stopPerceiving();
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ try {
+ mManagerService.stopPerceiving();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return true;
}
- return true;
}
/**
@@ -160,12 +167,16 @@
if (DEBUG) {
Slog.i(TAG, "#destroy");
}
- mInitializationDelegate.destroy();
+ synchronized (mInitializationDelegate.getLock()) {
+ mInitializationDelegate.destroy();
+ }
}
/** @hide */
public void dump(String prefix, PrintWriter pw) {
- // TODO: implement this
+ synchronized (mInitializationDelegate.getLock()) {
+ mInitializationDelegate.dump(prefix, pw);
+ }
}
/** @hide */
@@ -175,7 +186,9 @@
/** @hide */
void registerOnDestroyListener(Consumer<AbstractDetector> onDestroyListener) {
- mInitializationDelegate.registerOnDestroyListener(onDestroyListener);
+ synchronized (mInitializationDelegate.getLock()) {
+ mInitializationDelegate.registerOnDestroyListener(onDestroyListener);
+ }
}
/**
@@ -282,6 +295,15 @@
public boolean isUsingSandboxedDetectionService() {
return true;
}
+
+ @Override
+ public void dump(String prefix, PrintWriter pw) {
+ // No-op
+ }
+
+ private Object getLock() {
+ return mLock;
+ }
}
private static class BinderCallback
@@ -289,31 +311,43 @@
private final Executor mExecutor;
private final VisualQueryDetector.Callback mCallback;
- BinderCallback(Executor executor, VisualQueryDetector.Callback callback) {
+ private final Object mLock;
+
+ BinderCallback(Executor executor, VisualQueryDetector.Callback callback, Object lock) {
this.mExecutor = executor;
this.mCallback = callback;
+ this.mLock = lock;
}
/** Called when the detected result is valid. */
@Override
public void onQueryDetected(@NonNull String partialQuery) {
Slog.v(TAG, "BinderCallback#onQueryDetected");
- Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> mCallback.onQueryDetected(partialQuery)));
+ Binder.withCleanCallingIdentity(() -> {
+ synchronized (mLock) {
+ mCallback.onQueryDetected(partialQuery);
+ }
+ });
}
@Override
public void onQueryFinished() {
Slog.v(TAG, "BinderCallback#onQueryFinished");
- Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> mCallback.onQueryFinished()));
+ Binder.withCleanCallingIdentity(() -> {
+ synchronized (mLock) {
+ mCallback.onQueryFinished();
+ }
+ });
}
@Override
public void onQueryRejected() {
Slog.v(TAG, "BinderCallback#onQueryRejected");
- Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> mCallback.onQueryRejected()));
+ Binder.withCleanCallingIdentity(() -> {
+ synchronized (mLock) {
+ mCallback.onQueryRejected();
+ }
+ });
}
/** Called when the detection fails due to an error. */
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 31d759e..18080e4 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -287,6 +287,16 @@
}
/**
+ * Called when a display hdcp levels change event is received.
+ *
+ * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
+ * @param connectedLevel the new connected HDCP level
+ * @param maxLevel the maximum HDCP level
+ */
+ public void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel) {
+ }
+
+ /**
* Represents a mapping between a UID and an override frame rate
*/
public static class FrameRateOverride {
@@ -374,4 +384,11 @@
onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);
}
+ // Called from native code.
+ @SuppressWarnings("unused")
+ private void dispatchHdcpLevelsChanged(long physicalDisplayId, int connectedLevel,
+ int maxLevel) {
+ onHdcpLevelsChanged(physicalDisplayId, connectedLevel, maxLevel);
+ }
+
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e83488e..9f6395e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -528,8 +528,6 @@
// Set to true to stop input during an Activity Transition.
boolean mPausedForTransition = false;
- boolean mLastInCompatMode = false;
-
SurfaceHolder.Callback2 mSurfaceHolderCallback;
BaseSurfaceHolder mSurfaceHolder;
boolean mIsCreating;
@@ -1375,11 +1373,6 @@
}
if (DEBUG_LAYOUT) Log.d(mTag, "WindowLayout in setView:" + attrs);
- if (!compatibilityInfo.supportsScreen()) {
- attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
- mLastInCompatMode = true;
- }
-
mSoftInputMode = attrs.softInputMode;
mWindowAttributesChanged = true;
mAttachInfo.mRootView = view;
@@ -1913,10 +1906,6 @@
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
- // Preserve compatible window flag if exists.
- final int compatibleWindowFlag = mWindowAttributes.privateFlags
- & WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
-
// Preserve system UI visibility.
final int systemUiVisibility = mWindowAttributes.systemUiVisibility;
final int subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
@@ -1948,8 +1937,7 @@
mWindowAttributes.subtreeSystemUiVisibility = subtreeSystemUiVisibility;
mWindowAttributes.insetsFlags.appearance = appearance;
mWindowAttributes.insetsFlags.behavior = behavior;
- mWindowAttributes.privateFlags |= compatibleWindowFlag
- | appearanceAndBehaviorPrivateFlags;
+ mWindowAttributes.privateFlags |= appearanceAndBehaviorPrivateFlags;
if (mWindowAttributes.preservePreviousSurfaceInsets) {
// Restore old surface insets.
@@ -3150,21 +3138,6 @@
final boolean shouldOptimizeMeasure = shouldOptimizeMeasure(lp);
WindowManager.LayoutParams params = null;
- CompatibilityInfo compatibilityInfo =
- mDisplay.getDisplayAdjustments().getCompatibilityInfo();
- if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
- params = lp;
- mFullRedrawNeeded = true;
- mLayoutRequested = true;
- if (mLastInCompatMode) {
- params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
- mLastInCompatMode = false;
- } else {
- params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
- mLastInCompatMode = true;
- }
- }
-
Rect frame = mWinFrame;
if (mFirst) {
mFullRedrawNeeded = true;
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 1712fd3..07a347a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3143,13 +3143,6 @@
@UnsupportedAppUsage
public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 1 << 6;
- /** Window flag: special flag to limit the size of the window to be
- * original size ([320x480] x density). Used to create window for applications
- * running under compatibility mode.
- *
- * {@hide} */
- public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 1 << 7;
-
/** Window flag: a special option intended for system dialogs. When
* this flag is set, the window will demand focus unconditionally when
* it is created.
@@ -3345,7 +3338,6 @@
SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION,
PRIVATE_FLAG_NO_MOVE_ANIMATION,
- PRIVATE_FLAG_COMPATIBLE_WINDOW,
PRIVATE_FLAG_SYSTEM_ERROR,
PRIVATE_FLAG_OPTIMIZE_MEASURE,
PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
@@ -3399,10 +3391,6 @@
equals = PRIVATE_FLAG_NO_MOVE_ANIMATION,
name = "NO_MOVE_ANIMATION"),
@ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_COMPATIBLE_WINDOW,
- equals = PRIVATE_FLAG_COMPATIBLE_WINDOW,
- name = "COMPATIBLE_WINDOW"),
- @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_SYSTEM_ERROR,
equals = PRIVATE_FLAG_SYSTEM_ERROR,
name = "SYSTEM_ERROR"),
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
index c475723..7cda3a3 100644
--- a/core/java/android/window/BackMotionEvent.java
+++ b/core/java/android/window/BackMotionEvent.java
@@ -36,6 +36,7 @@
private final float mProgress;
private final float mVelocityX;
private final float mVelocityY;
+ private final boolean mTriggerBack;
@BackEvent.SwipeEdge
private final int mSwipeEdge;
@@ -54,6 +55,7 @@
* Value in pixels/second. {@link Float#NaN} if was not computed.
* @param velocityY Y velocity computed from the touch point of this event.
* Value in pixels/second. {@link Float#NaN} if was not computed.
+ * @param triggerBack Indicates whether the back arrow is in the triggered state or not
* @param swipeEdge Indicates which edge the swipe starts from.
* @param departingAnimationTarget The remote animation target of the departing
* application window.
@@ -64,6 +66,7 @@
float progress,
float velocityX,
float velocityY,
+ boolean triggerBack,
@BackEvent.SwipeEdge int swipeEdge,
@Nullable RemoteAnimationTarget departingAnimationTarget) {
mTouchX = touchX;
@@ -71,6 +74,7 @@
mProgress = progress;
mVelocityX = velocityX;
mVelocityY = velocityY;
+ mTriggerBack = triggerBack;
mSwipeEdge = swipeEdge;
mDepartingAnimationTarget = departingAnimationTarget;
}
@@ -81,6 +85,7 @@
mProgress = in.readFloat();
mVelocityX = in.readFloat();
mVelocityY = in.readFloat();
+ mTriggerBack = in.readBoolean();
mSwipeEdge = in.readInt();
mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
}
@@ -110,6 +115,7 @@
dest.writeFloat(mProgress);
dest.writeFloat(mVelocityX);
dest.writeFloat(mVelocityY);
+ dest.writeBoolean(mTriggerBack);
dest.writeInt(mSwipeEdge);
dest.writeTypedObject(mDepartingAnimationTarget, flags);
}
@@ -157,6 +163,15 @@
}
/**
+ * Returns whether the back arrow is in the triggered state or not
+ *
+ * @return boolean indicating whether the back arrow is in the triggered state or not
+ */
+ public boolean getTriggerBack() {
+ return mTriggerBack;
+ }
+
+ /**
* Returns the screen edge that the swipe starts from.
*/
@BackEvent.SwipeEdge
@@ -182,6 +197,7 @@
+ ", mProgress=" + mProgress
+ ", mVelocityX=" + mVelocityX
+ ", mVelocityY=" + mVelocityY
+ + ", mTriggerBack=" + mTriggerBack
+ ", mSwipeEdge" + mSwipeEdge
+ ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+ "}";
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 0077dab..4b5595f 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -36,3 +36,10 @@
bug: "316139088"
is_fixed_read_only: true
}
+
+flag {
+ name: "user_min_aspect_ratio_app_default"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether the API PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT is available"
+ bug: "310816437"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 07beb11..216acdc 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -15,6 +15,14 @@
}
flag {
+ name: "enforce_edge_to_edge"
+ namespace: "windowing_frontend"
+ description: "Make app go edge-to-edge when targeting SDK level 35 or greater"
+ bug: "309578419"
+ is_fixed_read_only: true
+}
+
+flag {
name: "defer_display_updates"
namespace: "windowing_frontend"
description: "Feature flag for deferring DisplayManager updates to WindowManager if Shell transition is running"
@@ -58,4 +66,12 @@
description: "Predictive back for system animations"
bug: "309545085"
is_fixed_read_only: true
+}
+
+flag {
+ name: "activity_snapshot_by_default"
+ namespace: "systemui"
+ description: "Enable record activity snapshot by default"
+ bug: "259497289"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index efc1455..467cd49 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -39,6 +39,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.SuspendDialogInfo;
+import android.content.pm.UserPackage;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -308,7 +309,8 @@
try {
final String[] errored = ipm.setPackagesSuspendedAsUser(
new String[]{mSuspendedPackage}, false, null, null, null, 0,
- mSuspendingPackage, mUserId);
+ mSuspendingPackage, mUserId /* suspendingUserId */,
+ mUserId /* targetUserId */);
if (ArrayUtils.contains(errored, mSuspendedPackage)) {
Slog.e(TAG, "Could not unsuspend " + mSuspendedPackage);
break;
@@ -350,17 +352,19 @@
}
public static Intent createSuspendedAppInterceptIntent(String suspendedPackage,
- String suspendingPackage, SuspendDialogInfo dialogInfo, Bundle options,
+ UserPackage suspendingPackage, SuspendDialogInfo dialogInfo, Bundle options,
IntentSender onUnsuspend, int userId) {
- return new Intent()
+ Intent intent = new Intent()
.setClassName("android", SuspendedAppActivity.class.getName())
.putExtra(EXTRA_SUSPENDED_PACKAGE, suspendedPackage)
.putExtra(EXTRA_DIALOG_INFO, dialogInfo)
- .putExtra(EXTRA_SUSPENDING_PACKAGE, suspendingPackage)
+ .putExtra(EXTRA_SUSPENDING_PACKAGE,
+ suspendingPackage != null ? suspendingPackage.packageName : null)
.putExtra(EXTRA_UNSUSPEND_INTENT, onUnsuspend)
.putExtra(EXTRA_ACTIVITY_OPTIONS, options)
.putExtra(Intent.EXTRA_USER_ID, userId)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ return intent;
}
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 201b23c..31910ac 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -169,20 +169,12 @@
private final static int DEFAULT_BACKGROUND_FADE_DURATION_MS = 300;
/**
- * Makes navigation bar color transparent by default if the target SDK is
- * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
- private static final long NAV_BAR_COLOR_DEFAULT_TRANSPARENT = 232195501L;
-
- /**
* Make app go edge-to-edge by default if the target SDK is
* {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
- private static final long EDGE_TO_EDGE_BY_DEFAULT = 309578419;
+ private static final long ENFORCE_EDGE_TO_EDGE = 309578419;
private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES |
(1 << FEATURE_CUSTOM_TITLE) |
@@ -193,9 +185,9 @@
private static final Transition USE_DEFAULT_TRANSITION = new TransitionSet();
/**
- * Since which target SDK version this window should be edge-to-edge by default.
+ * Since which target SDK version this window is enforced to go edge-to-edge.
*/
- private static final int DEFAULT_EDGE_TO_EDGE_SDK_VERSION =
+ private static final int ENFORCE_EDGE_TO_EDGE_SDK_VERSION =
SystemProperties.getInt("persist.wm.debug.default_e2e_since_sdk", Integer.MAX_VALUE);
/**
@@ -376,7 +368,7 @@
boolean mDecorFitsSystemWindows = true;
@VisibleForTesting
- public final boolean mDefaultEdgeToEdge;
+ public final boolean mEdgeToEdgeEnforced;
private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher;
@@ -396,11 +388,11 @@
mProxyOnBackInvokedDispatcher = new ProxyOnBackInvokedDispatcher(context);
mAllowFloatingWindowsFillScreen = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowFloatingWindowsFillScreen);
- mDefaultEdgeToEdge =
- context.getApplicationInfo().targetSdkVersion >= DEFAULT_EDGE_TO_EDGE_SDK_VERSION
- || (CompatChanges.isChangeEnabled(EDGE_TO_EDGE_BY_DEFAULT)
- && Flags.edgeToEdgeByDefault());
- if (mDefaultEdgeToEdge) {
+ mEdgeToEdgeEnforced =
+ context.getApplicationInfo().targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
+ || (CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
+ && Flags.enforceEdgeToEdge());
+ if (mEdgeToEdgeEnforced) {
mDecorFitsSystemWindows = false;
}
}
@@ -2472,7 +2464,7 @@
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
params.setFitInsetsSides(0);
params.setFitInsetsTypes(0);
- if (mDefaultEdgeToEdge) {
+ if (mEdgeToEdgeEnforced) {
params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
}
}
@@ -2562,7 +2554,7 @@
final int statusBarColor = a.getColor(R.styleable.Window_statusBarColor,
statusBarDefaultColor);
- mStatusBarColor = statusBarColor == statusBarDefaultColor && !mDefaultEdgeToEdge
+ mStatusBarColor = statusBarColor == statusBarDefaultColor && !mEdgeToEdgeEnforced
? statusBarCompatibleColor
: statusBarColor;
}
@@ -2574,9 +2566,7 @@
mNavigationBarColor =
navBarColor == navBarDefaultColor
- && !mDefaultEdgeToEdge
- && !(CompatChanges.isChangeEnabled(NAV_BAR_COLOR_DEFAULT_TRANSPARENT)
- && Flags.navBarTransparentByDefault())
+ && !mEdgeToEdgeEnforced
&& !context.getResources().getBoolean(
R.bool.config_navBarDefaultTransparent)
? navBarCompatibleColor
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index c24d21d..17aad43 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -653,6 +653,8 @@
sizeof("-Xps-save-resolved-classes-delay-ms:")-1 + PROPERTY_VALUE_MAX];
char profileMinSavePeriodOptsBuf[sizeof("-Xps-min-save-period-ms:")-1 + PROPERTY_VALUE_MAX];
char profileMinFirstSaveOptsBuf[sizeof("-Xps-min-first-save-ms:") - 1 + PROPERTY_VALUE_MAX];
+ char profileInlineCacheThresholdOptsBuf[
+ sizeof("-Xps-inline-cache-threshold:") - 1 + PROPERTY_VALUE_MAX];
char madviseWillNeedFileSizeVdex[
sizeof("-XMadviseWillNeedVdexFileSize:")-1 + PROPERTY_VALUE_MAX];
char madviseWillNeedFileSizeOdex[
@@ -898,6 +900,9 @@
parseRuntimeOption("dalvik.vm.ps-min-first-save-ms", profileMinFirstSaveOptsBuf,
"-Xps-min-first-save-ms:");
+ parseRuntimeOption("dalvik.vm.ps-inline-cache-threshold", profileInlineCacheThresholdOptsBuf,
+ "-Xps-inline-cache-threshold:");
+
property_get("ro.config.low_ram", propBuf, "");
if (strcmp(propBuf, "true") == 0) {
addOption("-XX:LowMemoryMode");
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index fef8ad7..f007cc5 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -41,6 +41,7 @@
jmethodID dispatchHotplugConnectionError;
jmethodID dispatchModeChanged;
jmethodID dispatchFrameRateOverrides;
+ jmethodID dispatchHdcpLevelsChanged;
struct {
jclass clazz;
@@ -96,6 +97,8 @@
void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
std::vector<FrameRateOverride> overrides) override;
void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
+ void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int connectedLevel,
+ int maxLevel) override;
};
NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
@@ -294,6 +297,22 @@
mMessageQueue->raiseAndClearException(env, "dispatchModeChanged");
}
+void NativeDisplayEventReceiver::dispatchHdcpLevelsChanged(PhysicalDisplayId displayId,
+ int connectedLevel, int maxLevel) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+ ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
+ if (receiverObj.get()) {
+ ALOGV("receiver %p ~ Invoking hdcp levels changed handler.", this);
+ env->CallVoidMethod(receiverObj.get(),
+ gDisplayEventReceiverClassInfo.dispatchHdcpLevelsChanged,
+ displayId.value, connectedLevel, maxLevel);
+ ALOGV("receiver %p ~ Returned from hdcp levels changed handler.", this);
+ }
+
+ mMessageQueue->raiseAndClearException(env, "dispatchHdcpLevelsChanged");
+}
+
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject vsyncEventDataWeak,
jobject messageQueueObj, jint vsyncSource, jint eventRegistration,
jlong layerHandle) {
@@ -385,6 +404,9 @@
GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz,
"dispatchFrameRateOverrides",
"(JJ[Landroid/view/DisplayEventReceiver$FrameRateOverride;)V");
+ gDisplayEventReceiverClassInfo.dispatchHdcpLevelsChanged =
+ GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchHdcpLevelsChanged",
+ "(JII)V");
jclass frameRateOverrideClazz =
FindClassOrDie(env, "android/view/DisplayEventReceiver$FrameRateOverride");
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index c5889ba..75cfba0 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -429,6 +429,7 @@
optional string base_dir = 1;
optional string res_dir = 2;
optional string data_dir = 3;
+ optional int32 targetSdkVersion = 4;
}
optional AppInfo appinfo = 8;
optional ProcessRecordProto app = 9;
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 1a3ec27..c058174 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -202,12 +202,14 @@
"testng",
],
srcs: [
+ "src/android/database/CursorWindowTest.java",
"src/android/os/**/*.java",
- "src/com/android/internal/os/**/*.java",
"src/android/util/**/*.java",
+ "src/com/android/internal/os/**/*.java",
"src/com/android/internal/os/LongArrayMultiStateCounterTest.java",
"src/com/android/internal/util/**/*.java",
"src/com/android/internal/power/EnergyConsumerStatsTest.java",
+
":FrameworksCoreTests{.aapt.srcjar}",
":FrameworksCoreTests-aidl",
":FrameworksCoreTests-helpers",
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 1577d9c1c1..5b0502d 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -1244,29 +1244,33 @@
}
@Test
- public void testBigPictureStyle_setExtras_pictureIconNull_noPictureIconKey() {
+ public void testBigPictureStyle_setExtras_pictureIconNull_pictureIconKeyNull() {
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture((Bitmap) null);
Bundle extras = new Bundle();
bpStyle.addExtras(extras);
- assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse();
+ assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isTrue();
+ final Parcelable pictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON);
+ assertThat(pictureIcon).isNull();
}
@Test
- public void testBigPictureStyle_setExtras_pictureIconNull_noPictureKey() {
+ public void testBigPictureStyle_setExtras_pictureIconNull_pictureKeyNull() {
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture((Bitmap) null);
Bundle extras = new Bundle();
bpStyle.addExtras(extras);
- assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse();
+ assertThat(extras.containsKey(EXTRA_PICTURE)).isTrue();
+ final Parcelable picture = extras.getParcelable(EXTRA_PICTURE);
+ assertThat(picture).isNull();
}
@Test
- public void testBigPictureStyle_setExtras_pictureIconTypeBitmap_noPictureIconKey() {
+ public void testBigPictureStyle_setExtras_pictureIconTypeBitmap_pictureIconKeyNull() {
Bitmap bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture(bitmap);
@@ -1274,11 +1278,13 @@
Bundle extras = new Bundle();
bpStyle.addExtras(extras);
- assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse();
+ assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isTrue();
+ final Parcelable pictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON);
+ assertThat(pictureIcon).isNull();
}
@Test
- public void testBigPictureStyle_setExtras_pictureIconTypeIcon_noPictureKey() {
+ public void testBigPictureStyle_setExtras_pictureIconTypeIcon_pictureKeyNull() {
Icon icon = Icon.createWithResource(mContext, R.drawable.btn_plus);
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture(icon);
@@ -1286,7 +1292,9 @@
Bundle extras = new Bundle();
bpStyle.addExtras(extras);
- assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse();
+ assertThat(extras.containsKey(EXTRA_PICTURE)).isTrue();
+ final Parcelable picture = extras.getParcelable(EXTRA_PICTURE);
+ assertThat(picture).isNull();
}
@Test
diff --git a/core/tests/coretests/src/android/database/CursorWindowPerformanceTest.java b/core/tests/coretests/src/android/database/CursorWindowPerformanceTest.java
new file mode 100644
index 0000000..c6d3ebf
--- /dev/null
+++ b/core/tests/coretests/src/android/database/CursorWindowPerformanceTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+import android.test.PerformanceTestCase;
+
+import androidx.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+/**
+ * <pre>
+ * m -j44 FrameworksCoreTests
+ * adb install -r -g \
+ * ${ANDROID_PRODUCT_OUT}/testcases/FrameworksCoreTests/arm64/FrameworksCoreTests.apk
+ * adb shell am instrument -r -e perf true \
+ * -e class 'android.database.CursorWindowPerformanceTest'
+ * -w 'com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner'
+ * </pre>
+ */
+public class CursorWindowPerformanceTest extends TestCase implements PerformanceTestCase {
+
+ private final CursorWindowTest mTest = new CursorWindowTest();
+
+ @Override
+ public boolean isPerformanceOnly() {
+ return true;
+ }
+
+ // These test can only be run once.
+ @Override
+ public int startPerformance(Intermediates intermediates) {
+ return 1;
+ }
+
+ @SmallTest
+ public void testConstructor_WithName() {
+ mTest.testConstructor_WithName();
+ }
+
+ @SmallTest
+ public void testConstructorWithEmptyName() {
+ mTest.testConstructorWithEmptyName();
+ }
+
+ @SmallTest
+ public void testValues() {
+ mTest.testValues();
+ }
+}
diff --git a/core/tests/coretests/src/android/database/CursorWindowTest.java b/core/tests/coretests/src/android/database/CursorWindowTest.java
index 123da3e..255020a 100644
--- a/core/tests/coretests/src/android/database/CursorWindowTest.java
+++ b/core/tests/coretests/src/android/database/CursorWindowTest.java
@@ -16,25 +16,29 @@
package android.database;
-import android.test.PerformanceTestCase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.database.sqlite.SQLiteException;
+import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.Arrays;
-public class CursorWindowTest extends TestCase implements PerformanceTestCase {
- public boolean isPerformanceOnly() {
- return false;
- }
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CursorWindowTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
- // These test can only be run once.
- public int startPerformance(Intermediates intermediates) {
- return 1;
- }
-
- @SmallTest
+ @Test
public void testConstructor_WithName() {
CursorWindow window = new CursorWindow("MyWindow");
assertEquals("MyWindow", window.getName());
@@ -42,7 +46,7 @@
window.close();
}
- @SmallTest
+ @Test
public void testConstructorWithEmptyName() {
CursorWindow window = new CursorWindow("");
assertEquals("<unnamed>", window.getName());
@@ -50,7 +54,7 @@
window.close();
}
- @SmallTest
+ @Test
public void testConstructorWithNullName() {
CursorWindow window = new CursorWindow(null);
assertEquals("<unnamed>", window.getName());
@@ -58,7 +62,7 @@
window.close();
}
- @SmallTest
+ @Test
public void testDeprecatedConstructor() {
@SuppressWarnings("deprecation")
CursorWindow window = new CursorWindow(true /*this argument is ignored*/);
@@ -67,7 +71,7 @@
window.close();
}
- @SmallTest
+ @Test
public void testValues() {
CursorWindow window = new CursorWindow("MyWindow");
doTestValues(window);
@@ -77,17 +81,25 @@
private void doTestValues(CursorWindow window) {
assertTrue(window.setNumColumns(7));
assertTrue(window.allocRow());
+ assertEquals(window.getType(0, 0), Cursor.FIELD_TYPE_NULL);
+
double db1 = 1.26;
assertTrue(window.putDouble(db1, 0, 0));
+ assertEquals(window.getType(0, 0), Cursor.FIELD_TYPE_FLOAT);
double db2 = window.getDouble(0, 0);
- assertEquals(db1, db2);
+ assertEquals(db1, db2, 0.01);
+ assertEquals(1, window.getInt(0, 0));
+ assertEquals("1.26", window.getString(0, 0));
+ assertThrows(SQLiteException.class, () -> window.getBlob(0, 0));
long int1 = Long.MAX_VALUE;
assertTrue(window.putLong(int1, 0, 1));
+ assertEquals(window.getType(0, 1), Cursor.FIELD_TYPE_INTEGER);
long int2 = window.getLong(0, 1);
assertEquals(int1, int2);
assertTrue(window.putString("1198032740000", 0, 3));
+ assertEquals(window.getType(0, 3), Cursor.FIELD_TYPE_STRING);
assertEquals("1198032740000", window.getString(0, 3));
assertEquals(1198032740000L, window.getLong(0, 3));
@@ -97,13 +109,14 @@
assertTrue(window.putString(Double.toString(42.0), 0, 4));
assertEquals(Double.toString(42.0), window.getString(0, 4));
- assertEquals(42.0, window.getDouble(0, 4));
+ assertEquals(42.0, window.getDouble(0, 4), 0.01);
// put blob
byte[] blob = new byte[1000];
byte value = 99;
Arrays.fill(blob, value);
assertTrue(window.putBlob(blob, 0, 6));
+ assertEquals(window.getType(0, 6), Cursor.FIELD_TYPE_BLOB);
assertTrue(Arrays.equals(blob, window.getBlob(0, 6)));
}
}
diff --git a/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
new file mode 100644
index 0000000..da3a465
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 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.face;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Function;
+
+@Presubmit
+@SmallTest
+public class FaceSensorConfigurationsTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private IFace mFace;
+ @Mock
+ private Function<String, IFace> mGetIFace;
+
+ private final String[] mAidlInstances = new String[]{"default", "virtual"};
+ private String[] mHidlConfigStrings = new String[]{"0:2:15", "0:8:15"};
+ private FaceSensorConfigurations mFaceSensorConfigurations;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mGetIFace.apply(anyString())).thenReturn(mFace);
+ when(mFace.getSensorProps()).thenReturn(new SensorProps[]{});
+ when(mContext.getResources()).thenReturn(mResources);
+ }
+
+ @Test
+ public void testAidlInstanceSensorProps() {
+ mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+ mFaceSensorConfigurations.addAidlConfigs(mAidlInstances, mGetIFace);
+
+ assertThat(mFaceSensorConfigurations.hasSensorConfigurations()).isTrue();
+ assertThat(!mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+ .isFalse();
+ }
+
+ @Test
+ public void testHidlConfigStrings() {
+ mFaceSensorConfigurations = new FaceSensorConfigurations(true);
+ mFaceSensorConfigurations.addHidlConfigs(mHidlConfigStrings, mContext);
+
+ assertThat(mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+ .isTrue();
+ }
+
+ @Test
+ public void testHidlConfigStrings_incorrectFormat() {
+ mHidlConfigStrings = new String[]{"0:8:15", "0:2", "0:face:15"};
+ mFaceSensorConfigurations = new FaceSensorConfigurations(true);
+ mFaceSensorConfigurations.addHidlConfigs(mHidlConfigStrings, mContext);
+
+ assertThat(mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+ .isTrue();
+ }
+}
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
new file mode 100644
index 0000000..613089c
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 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.fingerprint;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Function;
+
+
+@Presubmit
+@SmallTest
+public class FingerprintSensorConfigurationsTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private IFingerprint mFingerprint;
+ @Mock
+ private Function<String, IFingerprint> mGetIFingerprint;
+
+ private final String[] mAidlInstances = new String[]{"default", "virtual"};
+ private String[] mHidlConfigStrings = new String[]{"0:2:15", "0:8:15"};
+ private FingerprintSensorConfigurations mFingerprintSensorConfigurations;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mGetIFingerprint.apply(anyString())).thenReturn(mFingerprint);
+ when(mFingerprint.getSensorProps()).thenReturn(new SensorProps[]{});
+ when(mContext.getResources()).thenReturn(mResources);
+ }
+
+ @Test
+ public void testAidlInstanceSensorProps() {
+ mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ mFingerprintSensorConfigurations.addAidlSensors(mAidlInstances, mGetIFingerprint);
+
+ assertThat(mFingerprintSensorConfigurations.hasSensorConfigurations()).isTrue();
+ assertThat(!mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent())
+ .isTrue();
+ assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+ .isTrue();
+ }
+
+ @Test
+ public void testHidlConfigStrings() {
+ mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+ false /* resetLockoutRequiresHardwareAuthToken */);
+ mFingerprintSensorConfigurations.addHidlSensors(mHidlConfigStrings, mContext);
+
+ assertThat(mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+ .isFalse();
+ }
+
+ @Test
+ public void testHidlConfigStrings_incorrectFormat() {
+ mHidlConfigStrings = new String[]{"0:8:15", "0:2", "0:fingerprint:15"};
+ mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+ false /* resetLockoutRequiresHardwareAuthToken */);
+ mFingerprintSensorConfigurations.addHidlSensors(mHidlConfigStrings, mContext);
+
+ assertThat(mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+ .isFalse();
+ }
+}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 68c0693..a709d7b 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -84,6 +84,7 @@
/* progress = */ 0,
/* velocityX = */ 0,
/* velocityY = */ 0,
+ /* triggerBack = */ false,
/* swipeEdge = */ BackEvent.EDGE_LEFT,
/* departingAnimationTarget = */ null);
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index 6bdc06a..de55b07 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -63,7 +63,7 @@
createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeUnset);
installDecor();
- if (mPhoneWindow.mDefaultEdgeToEdge && !mPhoneWindow.isFloating()) {
+ if (mPhoneWindow.mEdgeToEdgeEnforced && !mPhoneWindow.isFloating()) {
assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS));
} else {
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 69a6e6d..c6f920f 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -518,6 +518,8 @@
<permission name="android.permission.ACCESS_BROADCAST_RADIO"/>
<!-- Permission required for CTS test - CtsAmbientContextServiceTestCases -->
<permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
+ <!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
+ <permission name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"/>
<!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
<permission name="android.permission.WRITE_APN_SETTINGS"/>
<!-- Permission required for GTS test - GtsStatsdHostTestCases -->
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index aaddf0e..742d5a2 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -379,12 +379,6 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
- "-1770075711": {
- "message": "Adding window client %s that is dead, aborting.",
- "level": "WARN",
- "group": "WM_ERROR",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"-1768557332": {
"message": "removeWallpaperAnimation()",
"level": "DEBUG",
@@ -3253,6 +3247,12 @@
"group": "WM_DEBUG_LOCKTASK",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
+ "723575093": {
+ "message": "Attempted to add window with a client %s that is dead. Aborting.",
+ "level": "WARN",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"726205185": {
"message": "Moving to DESTROYED: %s (destroy skipped)",
"level": "VERBOSE",
@@ -4237,12 +4237,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "1720696061": {
- "message": "Adding window to Display that has been removed.",
- "level": "WARN",
- "group": "WM_ERROR",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"1730300180": {
"message": "PendingStartTransaction found",
"level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
index e06d3ef..5b0de50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationConstants.java
@@ -21,5 +21,4 @@
*/
class BackAnimationConstants {
static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.20f;
- static final float PROGRESS_COMMIT_THRESHOLD = 0.1f;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index d8c691b..a498236 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -70,9 +70,11 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -124,6 +126,7 @@
private final Context mContext;
private final ContentResolver mContentResolver;
private final ShellController mShellController;
+ private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
@@ -180,7 +183,8 @@
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context,
@NonNull BackAnimationBackground backAnimationBackground,
- ShellBackAnimationRegistry shellBackAnimationRegistry) {
+ ShellBackAnimationRegistry shellBackAnimationRegistry,
+ ShellCommandHandler shellCommandHandler) {
this(
shellInit,
shellController,
@@ -190,7 +194,8 @@
context,
context.getContentResolver(),
backAnimationBackground,
- shellBackAnimationRegistry);
+ shellBackAnimationRegistry,
+ shellCommandHandler);
}
@VisibleForTesting
@@ -203,7 +208,8 @@
Context context,
ContentResolver contentResolver,
@NonNull BackAnimationBackground backAnimationBackground,
- ShellBackAnimationRegistry shellBackAnimationRegistry) {
+ ShellBackAnimationRegistry shellBackAnimationRegistry,
+ ShellCommandHandler shellCommandHandler) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
@@ -219,6 +225,7 @@
.build();
mShellBackAnimationRegistry = shellBackAnimationRegistry;
mLatencyTracker = LatencyTracker.getInstance(mContext);
+ mShellCommandHandler = shellCommandHandler;
}
private void onInit() {
@@ -227,6 +234,7 @@
createAdapter();
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
this::createExternalInterface, this);
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
private void setupAnimationDeveloperSettingsObserver(
@@ -968,4 +976,20 @@
};
mBackAnimationAdapter = new BackAnimationAdapter(runner);
}
+
+ /**
+ * Description of current BackAnimationController state.
+ */
+ private void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "BackAnimationController state:");
+ pw.println(prefix + " mEnableAnimations=" + mEnableAnimations.get());
+ pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted);
+ pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress);
+ pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent);
+ pw.println(prefix + " mCurrentTracker state:");
+ mCurrentTracker.dump(pw, prefix + " ");
+ pw.println(prefix + " mQueuedTracker state:");
+ mQueuedTracker.dump(pw, prefix + " ");
+ }
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
index 215a6cc..30d5edb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
@@ -18,9 +18,9 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.window.BackEvent.EDGE_RIGHT;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
-import static com.android.wm.shell.back.BackAnimationConstants.PROGRESS_COMMIT_THRESHOLD;
import static com.android.wm.shell.back.BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
@@ -91,7 +91,7 @@
}
};
private static final float MIN_WINDOW_ALPHA = 0.01f;
- private static final float WINDOW_X_SHIFT_DP = 96;
+ private static final float WINDOW_X_SHIFT_DP = 48;
private static final int SCALE_FACTOR = 100;
// TODO(b/264710590): Use the progress commit threshold from ViewConfiguration once it exists.
private static final float TARGET_COMMIT_PROGRESS = 0.5f;
@@ -126,6 +126,8 @@
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
private boolean mBackInProgress = false;
+ private boolean mIsRightEdge;
+ private boolean mTriggerBack = false;
private PointF mTouchPos = new PointF();
private IRemoteAnimationFinishedCallback mFinishCallback;
@@ -209,6 +211,7 @@
private void finishAnimation() {
if (mEnteringTarget != null) {
+ mTransaction.setCornerRadius(mEnteringTarget.leash, 0);
mEnteringTarget.leash.release();
mEnteringTarget = null;
}
@@ -241,14 +244,15 @@
private void onGestureProgress(@NonNull BackEvent backEvent) {
if (!mBackInProgress) {
+ mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
mBackInProgress = true;
}
mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
float progress = backEvent.getProgress();
- float springProgress = (progress > PROGRESS_COMMIT_THRESHOLD
- ? mapLinear(progress, 0.1f, 1, TARGET_COMMIT_PROGRESS, 1)
+ float springProgress = (mTriggerBack
+ ? mapLinear(progress, 0f, 1, TARGET_COMMIT_PROGRESS, 1)
: mapLinear(progress, 0, 1f, 0, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
mLeavingProgressSpring.animateToFinalPosition(springProgress);
mEnteringProgressSpring.animateToFinalPosition(springProgress);
@@ -312,7 +316,7 @@
transformWithProgress(
mEnteringProgress,
Math.max(
- smoothstep(ENTER_ALPHA_THRESHOLD, 1, mEnteringProgress),
+ smoothstep(ENTER_ALPHA_THRESHOLD, 0.7f, mEnteringProgress),
MIN_WINDOW_ALPHA), /* alpha */
mEnteringTarget.leash,
mEnteringRect,
@@ -337,14 +341,13 @@
mClosingTarget.leash,
mClosingRect,
0,
- mWindowXShift
+ mIsRightEdge ? 0 : mWindowXShift
);
}
}
private void transformWithProgress(float progress, float alpha, SurfaceControl surface,
RectF targetRect, float deltaXMin, float deltaXMax) {
- final float touchY = mTouchPos.y;
final int width = mStartTaskRect.width();
final int height = mStartTaskRect.height();
@@ -376,12 +379,14 @@
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
+ mTriggerBack = backEvent.getTriggerBack();
mProgressAnimator.onBackStarted(backEvent,
CrossActivityBackAnimation.this::onGestureProgress);
}
@Override
public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
+ mTriggerBack = backEvent.getTriggerBack();
mProgressAnimator.onBackProgressed(backEvent);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index 4bd56d4..6213f62 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -24,6 +24,8 @@
import android.window.BackEvent;
import android.window.BackMotionEvent;
+import java.io.PrintWriter;
+
/**
* Helper class to record the touch location for gesture and generate back events.
*/
@@ -129,6 +131,7 @@
/* progress = */ 0,
/* velocityX = */ 0,
/* velocityY = */ 0,
+ /* triggerBack = */ mTriggerBack,
/* swipeEdge = */ mSwipeEdge,
/* departingAnimationTarget = */ target);
}
@@ -204,6 +207,7 @@
/* progress = */ progress,
/* velocityX = */ mLatestVelocityX,
/* velocityY = */ mLatestVelocityY,
+ /* triggerBack = */ mTriggerBack,
/* swipeEdge = */ mSwipeEdge,
/* departingAnimationTarget = */ null);
}
@@ -219,6 +223,12 @@
mNonLinearFactor = nonLinearFactor;
}
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "TouchTracker state:");
+ pw.println(prefix + " mState=" + mState);
+ pw.println(prefix + " mTriggerBack=" + mTriggerBack);
+ }
+
enum TouchTrackerState {
INITIAL, ACTIVE, FINISHED
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index b7f749e..470a825 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1291,7 +1291,7 @@
// We only show user education for conversation bubbles right now
return false;
}
- final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION);
+ final boolean seen = getPrefBoolean(ManageEducationView.PREF_MANAGED_EDUCATION);
final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
&& mExpandedBubble != null && mExpandedBubble.getExpandedView() != null;
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
@@ -1342,7 +1342,7 @@
// We only show user education for conversation bubbles right now
return false;
}
- final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION);
+ final boolean seen = getPrefBoolean(StackEducationView.PREF_STACK_EDUCATION);
final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext);
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
Log.d(TAG, "Show stack edu: " + shouldShow);
@@ -2323,7 +2323,8 @@
updateOverflowVisibility();
updatePointerPosition(false /* forIme */);
mExpandedAnimationController.expandFromStack(() -> {
- if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
+ if (mIsExpanded && mExpandedBubble != null
+ && mExpandedBubble.getExpandedView() != null) {
maybeShowManageEdu();
}
updateOverflowDotVisibility(true /* expanding */);
@@ -2384,7 +2385,7 @@
}
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
- if (mExpandedBubble.getExpandedView() != null) {
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
mExpandedBubble.getExpandedView().setContentAlpha(0f);
mExpandedBubble.getExpandedView().setBackgroundAlpha(0f);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index 61e17c8..da71b1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -33,15 +33,16 @@
* User education view to highlight the manage button that allows a user to configure the settings
* for the bubble. Shown only the first time a user expands a bubble.
*/
-class ManageEducationView(context: Context, positioner: BubblePositioner) : LinearLayout(context) {
+class ManageEducationView(
+ context: Context,
+ private val positioner: BubblePositioner
+) : LinearLayout(context) {
- private val TAG =
- if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView"
- else BubbleDebugConfig.TAG_BUBBLES
+ companion object {
+ const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
+ private const val ANIMATE_DURATION: Long = 200
+ }
- private val ANIMATE_DURATION: Long = 200
-
- private val positioner: BubblePositioner = positioner
private val manageView by lazy { requireViewById<ViewGroup>(R.id.manage_education_view) }
private val manageButton by lazy { requireViewById<Button>(R.id.manage_button) }
private val gotItButton by lazy { requireViewById<Button>(R.id.got_it) }
@@ -128,7 +129,7 @@
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
}
- setShouldShow(false)
+ updateManageEducationSeen()
}
/**
@@ -218,13 +219,11 @@
}
}
- private fun setShouldShow(shouldShow: Boolean) {
+ private fun updateManageEducationSeen() {
context
.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
.edit()
- .putBoolean(PREF_MANAGED_EDUCATION, !shouldShow)
+ .putBoolean(PREF_MANAGED_EDUCATION, true)
.apply()
}
}
-
-const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 2cabb65..95f1017 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -34,19 +34,15 @@
*/
class StackEducationView(
context: Context,
- positioner: BubblePositioner,
- controller: BubbleController
+ private val positioner: BubblePositioner,
+ private val controller: BubbleController
) : LinearLayout(context) {
- private val TAG =
- if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
- else BubbleDebugConfig.TAG_BUBBLES
-
- private val ANIMATE_DURATION: Long = 200
- private val ANIMATE_DURATION_SHORT: Long = 40
-
- private val positioner: BubblePositioner = positioner
- private val controller: BubbleController = controller
+ companion object {
+ const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
+ private const val ANIMATE_DURATION: Long = 200
+ private const val ANIMATE_DURATION_SHORT: Long = 40
+ }
private val view by lazy { requireViewById<View>(R.id.stack_education_layout) }
private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) }
@@ -175,7 +171,7 @@
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
}
- setShouldShow(false)
+ updateStackEducationSeen()
return true
}
@@ -196,13 +192,11 @@
.withEndAction { visibility = GONE }
}
- private fun setShouldShow(shouldShow: Boolean) {
+ private fun updateStackEducationSeen() {
context
.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
.edit()
- .putBoolean(PREF_STACK_EDUCATION, !shouldShow)
+ .putBoolean(PREF_STACK_EDUCATION, true)
.apply()
}
}
-
-const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 5b0239f..02af2d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -563,7 +563,7 @@
? p.x - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR
: p.x + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
animationForChild(child)
- .translationX(fromX, p.y)
+ .translationX(fromX, p.x)
.start();
} else {
float fromY = p.y - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR;
@@ -634,4 +634,9 @@
.start();
}
}
+
+ /** Returns true if we're in the middle of a collapse or expand animation. */
+ boolean isAnimating() {
+ return mAnimatingCollapse || mAnimatingExpand;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 3c6bc17..fc97c798 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -363,7 +363,8 @@
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler,
BackAnimationBackground backAnimationBackground,
- Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry) {
+ Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry,
+ ShellCommandHandler shellCommandHandler) {
if (BackAnimationController.IS_ENABLED) {
return shellBackAnimationRegistry.map(
(animations) ->
@@ -374,7 +375,8 @@
backgroundHandler,
context,
backAnimationBackground,
- animations));
+ animations,
+ shellCommandHandler));
}
return Optional.empty();
}
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 ae21c4b..f58aeac 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
@@ -277,11 +277,6 @@
params.token = appToken;
params.packageName = activityInfo.packageName;
params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-
- if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
- params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
- }
-
params.setTitle("Splash Screen " + title);
return params;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 771876f..9ded6ea 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -63,6 +63,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellSharedConstants;
@@ -110,6 +111,8 @@
@Mock
private InputManager mInputManager;
+ @Mock
+ private ShellCommandHandler mShellCommandHandler;
private BackAnimationController mController;
private TestableContentResolver mContentResolver;
@@ -145,7 +148,8 @@
mContext,
mContentResolver,
mAnimationBackground,
- mShellBackAnimationRegistry);
+ mShellBackAnimationRegistry,
+ mShellCommandHandler);
mShellInit.init();
mShellExecutor.flushAll();
}
@@ -298,7 +302,8 @@
mContext,
mContentResolver,
mAnimationBackground,
- mShellBackAnimationRegistry);
+ mShellBackAnimationRegistry,
+ mShellCommandHandler);
shellInit.init();
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 874ef80..91503b1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -53,6 +53,7 @@
/* progress = */ progress,
/* velocityX = */ 0,
/* velocityY = */ 0,
+ /* triggerBack = */ false,
/* swipeEdge = */ BackEvent.EDGE_LEFT,
/* departingAnimationTarget = */ null);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index c1ff260..60f1d271 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -16,52 +16,51 @@
package com.android.wm.shell.bubbles.animation;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.SuppressLint;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleStackView;
+import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase {
- private int mDisplayWidth = 500;
- private int mDisplayHeight = 1000;
-
- private Runnable mOnBubbleAnimatedOutAction = mock(Runnable.class);
+ private final Semaphore mBubbleRemovedSemaphore = new Semaphore(0);
+ private final Runnable mOnBubbleAnimatedOutAction = mBubbleRemovedSemaphore::release;
ExpandedAnimationController mExpandedController;
private int mStackOffset;
private PointF mExpansionPoint;
private BubblePositioner mPositioner;
- private BubbleStackView.StackViewState mStackViewState = new BubbleStackView.StackViewState();
+ private final BubbleStackView.StackViewState mStackViewState =
+ new BubbleStackView.StackViewState();
- @SuppressLint("VisibleForTests")
@Before
public void setUp() throws Exception {
super.setUp();
@@ -70,15 +69,13 @@
getContext().getSystemService(WindowManager.class));
mPositioner.updateInternal(Configuration.ORIENTATION_PORTRAIT,
Insets.of(0, 0, 0, 0),
- new Rect(0, 0, mDisplayWidth, mDisplayHeight));
+ new Rect(0, 0, 500, 1000));
BubbleStackView stackView = mock(BubbleStackView.class);
- when(stackView.getState()).thenReturn(getStackViewState());
mExpandedController = new ExpandedAnimationController(mPositioner,
mOnBubbleAnimatedOutAction,
stackView);
- spyOn(mExpandedController);
addOneMoreThanBubbleLimitBubbles();
mLayout.setActiveController(mExpandedController);
@@ -86,9 +83,18 @@
Resources res = mLayout.getResources();
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
mExpansionPoint = new PointF(100, 100);
+
+ getStackViewState();
+ when(stackView.getState()).thenAnswer(i -> getStackViewState());
+ waitForMainThread();
}
- public BubbleStackView.StackViewState getStackViewState() {
+ @After
+ public void tearDown() {
+ waitForMainThread();
+ }
+
+ private BubbleStackView.StackViewState getStackViewState() {
mStackViewState.numberOfBubbles = mLayout.getChildCount();
mStackViewState.selectedIndex = 0;
mStackViewState.onLeft = mPositioner.isStackOnLeft(mExpansionPoint);
@@ -96,68 +102,71 @@
}
@Test
- @Ignore
- public void testExpansionAndCollapse() throws InterruptedException {
- Runnable afterExpand = mock(Runnable.class);
- mExpandedController.expandFromStack(afterExpand);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
+ public void testExpansionAndCollapse() throws Exception {
+ expand();
testBubblesInCorrectExpandedPositions();
- verify(afterExpand).run();
+ waitForMainThread();
- Runnable afterCollapse = mock(Runnable.class);
+ final Semaphore semaphore = new Semaphore(0);
+ Runnable afterCollapse = semaphore::release;
mExpandedController.collapseBackToStack(mExpansionPoint, false, afterCollapse);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
- testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1);
- verify(afterExpand).run();
+ assertThat(semaphore.tryAcquire(1, 2, TimeUnit.SECONDS)).isTrue();
+ waitForAnimation();
+ testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y);
}
@Test
- @Ignore
- public void testOnChildAdded() throws InterruptedException {
+ public void testOnChildAdded() throws Exception {
expand();
+ waitForMainThread();
// Add another new view and wait for its animation.
final View newView = new FrameLayout(getContext());
mLayout.addView(newView, 0);
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ waitForAnimation();
testBubblesInCorrectExpandedPositions();
}
@Test
- @Ignore
- public void testOnChildRemoved() throws InterruptedException {
+ public void testOnChildRemoved() throws Exception {
expand();
+ waitForMainThread();
- // Remove some views and see if the remaining child views still pass the expansion test.
+ // Remove some views and verify the remaining child views still pass the expansion test.
mLayout.removeView(mViews.get(0));
mLayout.removeView(mViews.get(3));
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+ // Removing a view will invoke onBubbleAnimatedOutAction. Block until it gets called twice.
+ assertThat(mBubbleRemovedSemaphore.tryAcquire(2, 2, TimeUnit.SECONDS)).isTrue();
+
+ waitForAnimation();
testBubblesInCorrectExpandedPositions();
}
@Test
- public void testDragBubbleOutDoesntNPE() throws InterruptedException {
+ public void testDragBubbleOutDoesntNPE() {
mExpandedController.onGestureFinished();
mExpandedController.dragBubbleOut(mViews.get(0), 1, 1);
}
/** Expand the stack and wait for animations to finish. */
private void expand() throws InterruptedException {
- mExpandedController.expandFromStack(mock(Runnable.class));
- waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ final Semaphore semaphore = new Semaphore(0);
+ Runnable afterExpand = semaphore::release;
+
+ mExpandedController.expandFromStack(afterExpand);
+ assertThat(semaphore.tryAcquire(1, TimeUnit.SECONDS)).isTrue();
}
/** Check that children are in the correct positions for being stacked. */
- private void testStackedAtPosition(float x, float y, int offsetMultiplier) {
+ private void testStackedAtPosition(float x, float y) {
// Make sure the rest of the stack moved again, including the first bubble not moving, and
// is stacked to the right now that we're on the right side of the screen.
for (int i = 0; i < mLayout.getChildCount(); i++) {
- assertEquals(x + i * offsetMultiplier * mStackOffset,
- mLayout.getChildAt(i).getTranslationX(), 2f);
- assertEquals(y, mLayout.getChildAt(i).getTranslationY(), 2f);
+ assertEquals(x, mLayout.getChildAt(i).getTranslationX(), 2f);
+ assertEquals(y + Math.min(i, 1) * mStackOffset, mLayout.getChildAt(i).getTranslationY(),
+ 2f);
assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f);
}
}
@@ -175,4 +184,22 @@
mLayout.getChildAt(i).getTranslationY(), 2f);
}
}
+
+ private void waitForAnimation() throws Exception {
+ final Semaphore semaphore = new Semaphore(0);
+ boolean[] animating = new boolean[]{ true };
+ for (int i = 0; i < 4; i++) {
+ if (animating[0]) {
+ mMainThreadHandler.post(() -> {
+ if (!mExpandedController.isAnimating()) {
+ animating[0] = false;
+ semaphore.release();
+ }
+ });
+ Thread.sleep(500);
+ }
+ }
+ assertThat(semaphore.tryAcquire(1, 2, TimeUnit.SECONDS)).isTrue();
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
index 48ae296..2ed5add 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
@@ -164,11 +164,17 @@
@Override
public void cancelAllAnimations() {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(super::cancelAllAnimations);
}
@Override
public void cancelAnimationsOnView(View view) {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(() -> super.cancelAnimationsOnView(view));
}
@@ -221,6 +227,9 @@
@Override
protected void startPathAnimation() {
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
mMainThreadHandler.post(super::startPathAnimation);
}
}
@@ -322,4 +331,9 @@
e.printStackTrace();
}
}
+
+ /** Waits for the main thread to finish processing all pending runnables. */
+ public void waitForMainThread() {
+ runOnMainThreadAndBlock(() -> {});
+ }
}
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index b6055e8..f4b1056 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -20,3 +20,10 @@
description: "Flag for GnssMeasurementRequest WorkSource API"
bug: "295235160"
}
+
+flag {
+ name: "release_supl_connection_on_timeout"
+ namespace: "location"
+ description: "Flag for releasing SUPL connection on timeout"
+ bug: "315024652"
+}
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 5a72b0b..1a3d7b7 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -23,6 +23,8 @@
import android.annotation.TestApi;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
@@ -402,7 +404,9 @@
private final AudioDevicePort mPort;
- AudioDeviceInfo(AudioDevicePort port) {
+ /** @hide */
+ @VisibleForTesting
+ public AudioDeviceInfo(AudioDevicePort port) {
mPort = port;
}
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index 73bc6f9..2de8eef 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -20,6 +20,8 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
+import com.android.aconfig.annotations.VisibleForTesting;
+
import java.util.Arrays;
import java.util.List;
@@ -38,6 +40,26 @@
public class AudioDevicePort extends AudioPort {
+ /** @hide */
+ // TODO: b/316864909 - Remove this method once there's a way to fake audio device ports further
+ // down the stack.
+ @VisibleForTesting
+ public static AudioDevicePort createForTesting(
+ int type, @NonNull String name, @NonNull String address) {
+ return new AudioDevicePort(
+ new AudioHandle(/* id= */ 0),
+ name,
+ /* samplingRates= */ null,
+ /* channelMasks= */ null,
+ /* channelIndexMasks= */ null,
+ /* formats= */ null,
+ /* gains= */ null,
+ type,
+ address,
+ /* encapsulationModes= */ null,
+ /* encapsulationMetadataTypes= */ null);
+ }
+
private final int mType;
private final String mAddress;
private final int[] mEncapsulationModes;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 3dfd572..a5a69f9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -22,6 +22,7 @@
import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.media.audio.Flags.automaticBtDeviceType;
import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.Manifest;
import android.annotation.CallbackExecutor;
@@ -5120,6 +5121,71 @@
}
/**
+ * Notifies an application with a focus listener of gain or loss of audio focus
+ *
+ * <p>This is similar to {@link #dispatchAudioFocusChange(AudioFocusInfo, int, AudioPolicy)} but
+ * with additional functionality of fade. The players of the application with audio focus
+ * change, provided they meet the active {@link FadeManagerConfiguration} requirements, are
+ * faded before dispatching the callback to the application. For example, players of the
+ * application losing audio focus will be faded out, whereas players of the application gaining
+ * audio focus will be faded in, if needed.
+ *
+ * <p>The applicability of fade is decided against the supplied active {@link AudioFocusInfo}.
+ * This list cannot be {@code null}. The list can be empty if no other active
+ * {@link AudioFocusInfo} available at the time of the dispatch.
+ *
+ * <p>The {@link FadeManagerConfiguration} supplied here is prioritized over existing fade
+ * configurations. If none supplied, either the {@link FadeManagerConfiguration} set through
+ * {@link AudioPolicy} or the default will be used to determine the fade properties.
+ *
+ * <p>This method can only be used by owners of an {@link AudioPolicy} configured with
+ * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to true.
+ *
+ * @param afi the recipient of the focus change, that has previously requested audio focus, and
+ * that was received by the {@code AudioPolicy} through
+ * {@link AudioPolicy.AudioPolicyFocusListener#onAudioFocusRequest(AudioFocusInfo, int)}
+ * @param focusChange one of focus gain types ({@link #AUDIOFOCUS_GAIN},
+ * {@link #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or
+ * {@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE})
+ * or one of the focus loss types ({@link AudioManager#AUDIOFOCUS_LOSS},
+ * {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT},
+ * or {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}).
+ * <br>For the focus gain, the change type should be the same as the app requested
+ * @param ap a valid registered {@link AudioPolicy} configured as a focus policy.
+ * @param otherActiveAfis active {@link AudioFocusInfo} that are granted audio focus at the time
+ * of dispatch
+ * @param transientFadeMgrConfig {@link FadeManagerConfiguration} that will be used for fading
+ * players resulting from this dispatch. This is a transient configuration that is only
+ * valid for this focus change and shall be discarded after processing this request.
+ * @return {@link #AUDIOFOCUS_REQUEST_FAILED} if the focus client didn't have a listener or if
+ * there was an error sending the request, or {@link #AUDIOFOCUS_REQUEST_GRANTED} if the
+ * dispatch was successfully sent, or {@link #AUDIOFOCUS_REQUEST_DELAYED} if
+ * the request was successful but the dispatch of focus change was delayed due to a fade
+ * operation.
+ * @throws NullPointerException if the {@link AudioFocusInfo} or {@link AudioPolicy} or list of
+ * other active {@link AudioFocusInfo} are {@code null}.
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int dispatchAudioFocusChangeWithFade(@NonNull AudioFocusInfo afi, int focusChange,
+ @NonNull AudioPolicy ap, @NonNull List<AudioFocusInfo> otherActiveAfis,
+ @Nullable FadeManagerConfiguration transientFadeMgrConfig) {
+ Objects.requireNonNull(afi, "AudioFocusInfo cannot be null");
+ Objects.requireNonNull(ap, "AudioPolicy cannot be null");
+ Objects.requireNonNull(otherActiveAfis, "Other active AudioFocusInfo list cannot be null");
+
+ IAudioService service = getService();
+ try {
+ return service.dispatchFocusChangeWithFade(afi, focusChange, ap.cb(), otherActiveAfis,
+ transientFadeMgrConfig);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @hide
* Used internally by telephony package to abandon audio focus, typically after a call or
* when ringing ends and the call is rejected or not answered.
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index 337d4b0..40b0e3e 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -16,12 +16,13 @@
package android.media;
-import static com.android.media.flags.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
@@ -93,11 +94,9 @@
* Helps with recreating a new instance from another to simply change/add on top of the
* existing ones</li>
* </ul>
- * TODO(b/304835727): Convert into system API so that it can be set through AudioPolicy
- *
* @hide
*/
-
+@SystemApi
@FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
public final class FadeManagerConfiguration implements Parcelable {
@@ -523,6 +522,7 @@
*
* @param fadeState one of the fade state in {@link FadeStateEnum}
* @return human-readable string
+ * @hide
*/
@NonNull
public static String fadeStateToString(@FadeStateEnum int fadeState) {
@@ -712,7 +712,8 @@
*
* <p><b>Notes:</b>
* <ul>
- * <li>When fade state is set to enabled, the builder expects at least one valid usage to be
+ * <li>When fade state is set to {@link #FADE_STATE_ENABLED_DEFAULT} or
+ * {@link #FADE_STATE_ENABLED_AUTO}, the builder expects at least one valid usage to be
* set/added. Failure to do so will result in an exception during {@link #build()}</li>
* <li>Every usage added to the fadeable list should have corresponding volume shaper
* configs defined. This can be achieved by setting either the duration or volume shaper
@@ -720,8 +721,8 @@
* {@link #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)}</li>
* <li> It is recommended to set volume shaper configurations individually for fade out and
* fade in</li>
- * <li>For any incomplete volume shaper configs a volume shaper configuration will be
- * created using either the default fade durations or the ones provided as part of the
+ * <li>For any incomplete volume shaper configurations, a volume shaper configuration will
+ * be created using either the default fade durations or the ones provided as part of the
* {@link #Builder(long, long)}</li>
* <li>Additional volume shaper configs can also configured for a given usage
* with additional attributes like content-type in order to achieve finer fade controls.
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index a52f0b0..5c268d4 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -28,6 +28,7 @@
import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
import android.media.BluetoothProfileConnectionInfo;
+import android.media.FadeManagerConfiguration;
import android.media.IAudioDeviceVolumeDispatcher;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioModeDispatcher;
@@ -398,6 +399,14 @@
int dispatchFocusChange(in AudioFocusInfo afi, in int focusChange,
in IAudioPolicyCallback pcb);
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ int dispatchFocusChangeWithFade(in AudioFocusInfo afi,
+ in int focusChange,
+ in IAudioPolicyCallback pcb,
+ in List<AudioFocusInfo> otherActiveAfis,
+ in FadeManagerConfiguration transientFadeMgrConfig);
+
oneway void playerHasOpPlayAudio(in int piid, in boolean hasOpPlayAudio);
@EnforcePermission("BLUETOOTH_STACK")
@@ -754,4 +763,16 @@
oneway void removeLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo);
PersistableBundle getLoudnessParams(int piid, in LoudnessCodecInfo codecInfo);
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ int setFadeManagerConfigurationForFocusLoss(in FadeManagerConfiguration fmcForFocusLoss);
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ int clearFadeManagerConfigurationForFocusLoss();
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss();
}
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index e168498..b85decc 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -16,6 +16,8 @@
package android.media.audiopolicy;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -34,6 +36,7 @@
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
+import android.media.FadeManagerConfiguration;
import android.media.IAudioService;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
@@ -49,6 +52,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -612,6 +616,110 @@
return mRegistrationId;
}
+ /**
+ * Sets a custom {@link FadeManagerConfiguration} to handle fade cycle of players during
+ * {@link android.media.AudioManager#AUDIOFOCUS_LOSS}
+ *
+ * @param fmcForFocusLoss custom {@link FadeManagerConfiguration}
+ * @return {@link AudioManager#SUCCESS} if the update was successful,
+ * {@link AudioManager#ERROR} otherwise
+ * @throws IllegalStateException if the audio policy is not registered
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @SystemApi
+ public int setFadeManagerConfigurationForFocusLoss(
+ @NonNull FadeManagerConfiguration fmcForFocusLoss) {
+ Objects.requireNonNull(fmcForFocusLoss,
+ "FadeManagerConfiguration for focus loss cannot be null");
+
+ IAudioService service = getService();
+ synchronized (mLock) {
+ Preconditions.checkState(isAudioPolicyRegisteredLocked(),
+ "Cannot set FadeManagerConfiguration with unregistered AudioPolicy");
+
+ try {
+ return service.setFadeManagerConfigurationForFocusLoss(fmcForFocusLoss);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception for setFadeManagerConfigurationForFocusLoss:",
+ e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Clear the current {@link FadeManagerConfiguration} set to handle fade cycles of players
+ * during {@link android.media.AudioManager#AUDIOFOCUS_LOSS}
+ *
+ * <p>In the absence of custom {@link FadeManagerConfiguration}, the default configurations will
+ * be used to handle fade cycles during audio focus loss.
+ *
+ * @return {@link AudioManager#SUCCESS} if the update was successful,
+ * {@link AudioManager#ERROR} otherwise
+ * @throws IllegalStateException if the audio policy is not registered
+ * @see #setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @SystemApi
+ public int clearFadeManagerConfigurationForFocusLoss() {
+ IAudioService service = getService();
+ synchronized (mLock) {
+ Preconditions.checkState(isAudioPolicyRegisteredLocked(),
+ "Cannot clear FadeManagerConfiguration from unregistered AudioPolicy");
+
+ try {
+ return service.clearFadeManagerConfigurationForFocusLoss();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception for "
+ + "clearFadeManagerConfigurationForFocusLoss:", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Get the current fade manager configuration used for fade operations during
+ * {@link android.media.AudioManager#AUDIOFOCUS_LOSS}
+ *
+ * <p>If no custom {@link FadeManagerConfiguration} is set, the default configuration currently
+ * active will be returned.
+ *
+ * @return the active {@link FadeManagerConfiguration} used during audio focus loss
+ * @throws IllegalStateException if the audio policy is not registered
+ * @see #setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)
+ * @see #clearFadeManagerConfigurationForFocusLoss()
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @SystemApi
+ @NonNull
+ public FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss() {
+ IAudioService service = getService();
+ synchronized (mLock) {
+ Preconditions.checkState(isAudioPolicyRegisteredLocked(),
+ "Cannot get FadeManagerConfiguration from unregistered AudioPolicy");
+
+ try {
+ return service.getFadeManagerConfigurationForFocusLoss();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception for getFadeManagerConfigurationForFocusLoss:",
+ e);
+ throw e.rethrowFromSystemServer();
+
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isAudioPolicyRegisteredLocked() {
+ return mStatus == POLICY_STATUS_REGISTERED;
+ }
+
private boolean policyReadyToUse() {
synchronized (mLock) {
if (mStatus != POLICY_STATUS_REGISTERED) {
diff --git a/media/java/android/media/flags/fade_manager_configuration.aconfig b/media/java/android/media/flags/fade_manager_configuration.aconfig
deleted file mode 100644
index 100e2235..0000000
--- a/media/java/android/media/flags/fade_manager_configuration.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.media.flags"
-
-flag {
- namespace: "media_solutions"
- name: "enable_fade_manager_configuration"
- description: "Enable Fade Manager Configuration support to determine fade properties"
- bug: "307354764"
-}
\ No newline at end of file
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
index fb6bd48..f105ae9 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
@@ -16,7 +16,7 @@
package com.android.audiopolicytest;
-import static com.android.media.flags.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import static org.junit.Assert.assertThrows;
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index b9dc618..2501869 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -48,6 +48,7 @@
"SettingsLibTwoTargetPreference",
"SettingsLibUsageProgressBarPreference",
"SettingsLibUtils",
+ "settingslib_media_flags_lib",
"settingslib_flags_lib",
],
@@ -78,6 +79,19 @@
}
java_aconfig_library {
- name: "settingslib_flags_lib",
+ name: "settingslib_media_flags_lib",
aconfig_declarations: "settingslib_media_flags",
}
+
+aconfig_declarations {
+ name: "settingslib_flags",
+ package: "com.android.settingslib.flags",
+ srcs: [
+ "aconfig/settingslib.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "settingslib_flags_lib",
+ aconfig_declarations: "settingslib_flags",
+}
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
new file mode 100644
index 0000000..fd2f9bd
--- /dev/null
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.settingslib.flags"
+
+flag {
+ name: "new_status_bar_icons"
+ namespace: "systemui"
+ description: "Enable new status bar system icons"
+ bug: "314812750"
+}
+
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index 562d20d..7fb959a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -20,6 +20,7 @@
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AlertDialog;
+import android.app.Flags;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
@@ -143,9 +144,16 @@
Slog.d(TAG, "Invalid manual condition: " + tag.condition);
}
// always triggers priority-only dnd with chosen condition
- mNotificationManager.setZenMode(
- Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- getRealConditionId(tag.condition), TAG);
+ if (Flags.modesApi()) {
+ mNotificationManager.setZenMode(
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ getRealConditionId(tag.condition), TAG,
+ /* fromUser= */ true);
+ } else {
+ mNotificationManager.setZenMode(
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ getRealConditionId(tag.condition), TAG);
+ }
}
});
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index 9084aa2..e83b9bc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -22,6 +22,7 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
+import android.multiuser.Flags;
import android.net.Uri;
import android.util.Log;
import android.widget.ImageView;
@@ -59,6 +60,9 @@
private static final String IMAGES_DIR = "multi_user";
private static final String NEW_USER_PHOTO_FILE_NAME = "NewUserPhoto.png";
+ private static final String AVATAR_PICKER_ACTION = "com.android.avatarpicker"
+ + ".FULL_SCREEN_ACTIVITY";
+
private final Activity mActivity;
private final ActivityStarter mActivityStarter;
private final ImageView mImageView;
@@ -105,7 +109,6 @@
onPhotoCropped(data.getData());
return true;
}
-
}
return false;
}
@@ -115,7 +118,13 @@
}
private void showAvatarPicker() {
- Intent intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
+ Intent intent;
+ if (Flags.avatarSync()) {
+ intent = new Intent(AVATAR_PICKER_ACTION);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ } else {
+ intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
+ }
intent.putExtra(AvatarPickerActivity.EXTRA_FILE_AUTHORITY, mFileAuthority);
mActivityStarter.startActivityForResult(intent, REQUEST_CODE_PICK_AVATAR);
}
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index 4b4caf5..644b72c 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -56,7 +56,7 @@
"SettingsLibDeviceStateRotationLock",
"SettingsLibSettingsSpinner",
"SettingsLibUsageProgressBarPreference",
- "settingslib_flags_lib",
+ "settingslib_media_flags_lib",
],
dxflags: ["--multi-dex"],
diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp
index 732c336..f4ddd0a 100644
--- a/packages/SettingsLib/tests/robotests/Android.bp
+++ b/packages/SettingsLib/tests/robotests/Android.bp
@@ -50,7 +50,7 @@
"androidx.test.core",
"androidx.core_core",
"flag-junit",
- "settingslib_flags_lib",
+ "settingslib_media_flags_lib",
"testng", // TODO: remove once JUnit on Android provides assertThrows
],
java_resource_dirs: ["config"],
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index adebdcd..d5814e3 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -59,10 +59,10 @@
// Note we statically link SettingsProviderLib to do some unit tests. It's not accessible otherwise
// because this test is not an instrumentation test. (because the target runs in the system process.)
"SettingsProviderLib",
-
"androidx.test.rules",
"flag-junit",
"junit",
+ "libaconfig_java_proto_lite",
"mockito-target-minus-junit4",
"platform-test-annotations",
"truth",
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 8f459c6..73c2e22 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -18,6 +18,9 @@
import static android.os.Process.FIRST_APPLICATION_UID;
+import android.aconfig.Aconfig.flag_state;
+import android.aconfig.Aconfig.parsed_flag;
+import android.aconfig.Aconfig.parsed_flags;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -147,6 +150,17 @@
*/
private static final String CONFIG_STAGED_PREFIX = "staged/";
+ private static final List<String> sAconfigTextProtoFilesOnDevice = List.of(
+ "/system/etc/aconfig_flags.pb",
+ "/system_ext/etc/aconfig_flags.pb",
+ "/product/etc/aconfig_flags.pb",
+ "/vendor/etc/aconfig_flags.pb");
+
+ /**
+ * This tag is applied to all aconfig default value-loaded flags.
+ */
+ private static final String BOOT_LOADED_DEFAULT_TAG = "BOOT_LOADED_DEFAULT";
+
// This was used in version 120 and before.
private static final String NULL_VALUE_OLD_STYLE = "null";
@@ -315,6 +329,59 @@
synchronized (mLock) {
readStateSyncLocked();
+
+ if (Flags.loadAconfigDefaults()) {
+ // Only load aconfig defaults if this is the first boot, the XML
+ // file doesn't exist yet, or this device is on its first boot after
+ // an OTA.
+ boolean shouldLoadAconfigValues = isConfigSettingsKey(mKey)
+ && (!file.exists()
+ || mContext.getPackageManager().isDeviceUpgrading());
+ if (shouldLoadAconfigValues) {
+ loadAconfigDefaultValuesLocked();
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void loadAconfigDefaultValuesLocked() {
+ for (String fileName : sAconfigTextProtoFilesOnDevice) {
+ try (FileInputStream inputStream = new FileInputStream(fileName)) {
+ byte[] contents = inputStream.readAllBytes();
+ loadAconfigDefaultValues(contents);
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "failed to read protobuf", e);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ public void loadAconfigDefaultValues(byte[] fileContents) {
+ try {
+ parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents);
+
+ if (parsedFlags == null) {
+ Slog.e(LOG_TAG, "failed to parse aconfig protobuf");
+ return;
+ }
+
+ for (parsed_flag flag : parsedFlags.getParsedFlagList()) {
+ String flagName = flag.getNamespace() + "/"
+ + flag.getPackage() + "." + flag.getName();
+ String value = flag.getState() == flag_state.ENABLED ? "true" : "false";
+
+ Setting existingSetting = getSettingLocked(flagName);
+ boolean isDefaultLoaded = existingSetting.getTag() != null
+ && existingSetting.getTag().equals(BOOT_LOADED_DEFAULT_TAG);
+ if (existingSetting.getValue() == null || isDefaultLoaded) {
+ insertSettingLocked(flagName, value, BOOT_LOADED_DEFAULT_TAG,
+ false, flag.getPackage());
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "failed to parse protobuf", e);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index 27ce0d4..ecac5ee 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -6,3 +6,11 @@
description: "When enabled, allows setting and displaying local overrides via adb."
bug: "298392357"
}
+
+flag {
+ name: "load_aconfig_defaults"
+ namespace: "core_experiments_team_internal"
+ description: "When enabled, loads aconfig default values into DeviceConfig on boot."
+ bug: "311155098"
+ is_fixed_read_only: true
+}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 02a7bc1..24625ea 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -15,6 +15,9 @@
*/
package com.android.providers.settings;
+import android.aconfig.Aconfig;
+import android.aconfig.Aconfig.parsed_flag;
+import android.aconfig.Aconfig.parsed_flags;
import android.os.Looper;
import android.test.AndroidTestCase;
import android.util.Xml;
@@ -84,6 +87,86 @@
super.tearDown();
}
+ public void testLoadValidAconfigProto() {
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+ Object lock = new Object();
+ SettingsState settingsState = new SettingsState(
+ getContext(), lock, mSettingsFile, configKey,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
+ parsed_flags flags = parsed_flags
+ .newBuilder()
+ .addParsedFlag(parsed_flag
+ .newBuilder()
+ .setPackage("com.android.flags")
+ .setName("flag1")
+ .setNamespace("test_namespace")
+ .setDescription("test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.DISABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .addParsedFlag(parsed_flag
+ .newBuilder()
+ .setPackage("com.android.flags")
+ .setName("flag2")
+ .setNamespace("test_namespace")
+ .setDescription("another test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.ENABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .build();
+
+ synchronized (lock) {
+ settingsState.loadAconfigDefaultValues(flags.toByteArray());
+ settingsState.persistSettingsLocked();
+ }
+ settingsState.waitForHandler();
+
+ synchronized (lock) {
+ assertEquals("false",
+ settingsState.getSettingLocked(
+ "test_namespace/com.android.flags.flag1").getValue());
+ assertEquals("true",
+ settingsState.getSettingLocked(
+ "test_namespace/com.android.flags.flag2").getValue());
+ }
+ }
+
+ public void testSkipLoadingAconfigFlagWithMissingFields() {
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+ Object lock = new Object();
+ SettingsState settingsState = new SettingsState(
+ getContext(), lock, mSettingsFile, configKey,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
+ parsed_flags flags = parsed_flags
+ .newBuilder()
+ .addParsedFlag(parsed_flag
+ .newBuilder()
+ .setDescription("test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.DISABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .build();
+
+ synchronized (lock) {
+ settingsState.loadAconfigDefaultValues(flags.toByteArray());
+ settingsState.persistSettingsLocked();
+ }
+ settingsState.waitForHandler();
+
+ synchronized (lock) {
+ assertEquals(null,
+ settingsState.getSettingLocked(
+ "test_namespace/com.android.flags.flag1").getValue());
+ }
+ }
+
+ public void testInvalidAconfigProtoDoesNotCrash() {
+ SettingsState settingsState = getSettingStateObject();
+ settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes());
+ }
+
public void testIsBinary() {
assertFalse(SettingsState.isBinary(" abc 日本語"));
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 6e65c16..477c42e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -688,6 +688,9 @@
<!-- Permission required for CTS test - CtsAmbientContextDetectionServiceDeviceTest -->
<uses-permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" />
+ <!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
+ <uses-permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE" />
+
<!-- Permission required for CTS test - CallAudioInterceptionTest -->
<uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 21ca613..8b27960 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -45,6 +45,13 @@
}
flag {
+ name: "notifications_improved_hun_animation"
+ namespace: "systemui"
+ description: "Adds a translateY animation, and other improvements to match the motion specs of the HUN Intro + Outro animations."
+ bug: "243302608"
+}
+
+flag {
name: "notification_lifetime_extension_refactor"
namespace: "systemui"
description: "Enables moving notification lifetime extension management from SystemUI to "
@@ -239,3 +246,18 @@
description: "Enable special visual and haptic effects for quick settings tiles with long-press actions"
bug: "229856884"
}
+
+flag {
+ name: "switch_user_on_bg"
+ namespace: "systemui"
+ description: "Does user switching on a background thread"
+ bug: "284095720"
+}
+
+flag {
+ name: "status_bar_static_inout_indicators"
+ namespace: "systemui"
+ description: "(Upstream request) Always show the network activity inout indicators and "
+ "prefer using alpha to distinguish network activity."
+ bug: "310715220"
+}
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/core/Android.bp
similarity index 100%
rename from packages/SystemUI/animation/Android.bp
rename to packages/SystemUI/animation/core/Android.bp
diff --git a/packages/SystemUI/animation/AndroidManifest.xml b/packages/SystemUI/animation/core/AndroidManifest.xml
similarity index 100%
rename from packages/SystemUI/animation/AndroidManifest.xml
rename to packages/SystemUI/animation/core/AndroidManifest.xml
diff --git a/packages/SystemUI/animation/build.gradle b/packages/SystemUI/animation/core/build.gradle
similarity index 81%
rename from packages/SystemUI/animation/build.gradle
rename to packages/SystemUI/animation/core/build.gradle
index 939455f..12637f4 100644
--- a/packages/SystemUI/animation/build.gradle
+++ b/packages/SystemUI/animation/core/build.gradle
@@ -5,8 +5,8 @@
android {
sourceSets {
main {
- java.srcDirs = ["${SYS_UI_DIR}/animation/src/com/android/systemui/surfaceeffects/"]
- manifest.srcFile "${SYS_UI_DIR}/animation/AndroidManifest.xml"
+ java.srcDirs = ["${SYS_UI_DIR}/animation/core/src/com/android/systemui/surfaceeffects/"]
+ manifest.srcFile "${SYS_UI_DIR}/animation/core/AndroidManifest.xml"
}
}
diff --git a/packages/SystemUI/animation/res/anim/launch_dialog_enter.xml b/packages/SystemUI/animation/core/res/anim/launch_dialog_enter.xml
similarity index 100%
rename from packages/SystemUI/animation/res/anim/launch_dialog_enter.xml
rename to packages/SystemUI/animation/core/res/anim/launch_dialog_enter.xml
diff --git a/packages/SystemUI/animation/res/anim/launch_dialog_exit.xml b/packages/SystemUI/animation/core/res/anim/launch_dialog_exit.xml
similarity index 100%
rename from packages/SystemUI/animation/res/anim/launch_dialog_exit.xml
rename to packages/SystemUI/animation/core/res/anim/launch_dialog_exit.xml
diff --git a/packages/SystemUI/animation/res/values/ids.xml b/packages/SystemUI/animation/core/res/values/ids.xml
similarity index 100%
rename from packages/SystemUI/animation/res/values/ids.xml
rename to packages/SystemUI/animation/core/res/values/ids.xml
diff --git a/packages/SystemUI/animation/res/values/styles.xml b/packages/SystemUI/animation/core/res/values/styles.xml
similarity index 100%
rename from packages/SystemUI/animation/res/values/styles.xml
rename to packages/SystemUI/animation/core/res/values/styles.xml
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/AnimationFeatureFlags.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/AnimationFeatureFlags.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/DialogLaunchAnimator.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/DialogLaunchAnimator.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/Expandable.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/Expandable.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontInterpolator.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/FontInterpolator.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/FontVariationUtils.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/FontVariationUtils.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchAnimator.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchAnimator.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchableView.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/LaunchableView.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ShadeInterpolation.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/ShadeInterpolation.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextAnimator.kt
similarity index 93%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/TextAnimator.kt
index 8dc7495..dc54240 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextAnimator.kt
@@ -42,9 +42,9 @@
return baseTypeface
}
- val axes = FontVariationAxis.fromFontVariationSettings(fVar)
- ?.toMutableList()
- ?: mutableListOf()
+ val axes =
+ FontVariationAxis.fromFontVariationSettings(fVar)?.toMutableList()
+ ?: mutableListOf()
axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) }
if (axes.isEmpty()) {
return baseTypeface
@@ -206,12 +206,14 @@
*
* Here is an example of font runs: "fin. 終わり"
*
+ * ```
* Characters : f i n . _ 終 わ り
* Code Points: \u0066 \u0069 \u006E \u002E \u0020 \u7D42 \u308F \u308A
* Font Runs : <-- Roboto-Regular.ttf --><-- NotoSans-CJK.otf -->
* runStart = 0, runLength = 5 runStart = 5, runLength = 3
* Glyph IDs : 194 48 7 8 4367 1039 1002
* Glyph Index: 0 1 2 3 0 1 2
+ * ```
*
* In this example, the "fi" is converted into ligature form, thus the single glyph ID is
* assigned for two characters, f and i.
@@ -243,22 +245,21 @@
/**
* Set text style with animation.
*
- * By passing -1 to weight, the view preserve the current weight.
- * By passing -1 to textSize, the view preserve the current text size.
- * Bu passing -1 to duration, the default text animation, 1000ms, is used.
- * By passing false to animate, the text will be updated without animation.
+ * By passing -1 to weight, the view preserve the current weight. By passing -1 to textSize, the
+ * view preserve the current text size. Bu passing -1 to duration, the default text animation,
+ * 1000ms, is used. By passing false to animate, the text will be updated without animation.
*
* @param fvar an optional text fontVariationSettings.
* @param textSize an optional font size.
- * @param colors an optional colors array that must be the same size as numLines passed to
- * the TextInterpolator
+ * @param colors an optional colors array that must be the same size as numLines passed to the
+ * TextInterpolator
* @param strokeWidth an optional paint stroke width
* @param animate an optional boolean indicating true for showing style transition as animation,
- * false for immediate style transition. True by default.
+ * false for immediate style transition. True by default.
* @param duration an optional animation duration in milliseconds. This is ignored if animate is
- * false.
+ * false.
* @param interpolator an optional time interpolator. If null is passed, last set interpolator
- * will be used. This is ignored if animate is false.
+ * will be used. This is ignored if animate is false.
*/
fun setTextStyle(
fvar: String? = "",
@@ -324,7 +325,8 @@
}
/**
- * Set text style with animation. Similar as
+ * Set text style with animation. Similar as fun setTextStyle( fvar: String? = "", textSize:
+ * ```
* fun setTextStyle(
* fvar: String? = "",
* textSize: Float = -1f,
@@ -336,6 +338,7 @@
* delay: Long = 0,
* onAnimationEnd: Runnable? = null
* )
+ * ```
*
* @param weight an optional style value for `wght` in fontVariationSettings.
* @param width an optional style value for `wdth` in fontVariationSettings.
@@ -356,12 +359,13 @@
delay: Long = 0,
onAnimationEnd: Runnable? = null
) {
- val fvar = fontVariationUtils.updateFontVariation(
- weight = weight,
- width = width,
- opticalSize = opticalSize,
- roundness = roundness,
- )
+ val fvar =
+ fontVariationUtils.updateFontVariation(
+ weight = weight,
+ width = width,
+ opticalSize = opticalSize,
+ roundness = roundness,
+ )
setTextStyle(
fvar = fvar,
textSize = textSize,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/TextInterpolator.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/TextInterpolator.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
similarity index 92%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 00d9056..a9c53d1 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -196,9 +196,9 @@
*
* @param includeFadeIn true if the animator should also fade in the view and child views.
* @param fadeInInterpolator the interpolator to use when fading in the view. Unused if
- * [includeFadeIn] is false.
- * @param onAnimationEnd an optional runnable that will be run once the animation
- * finishes successfully. Will not be run if the animation is cancelled.
+ * [includeFadeIn] is false.
+ * @param onAnimationEnd an optional runnable that will be run once the animation finishes
+ * successfully. Will not be run if the animation is cancelled.
*/
@JvmOverloads
fun animateAddition(
@@ -241,7 +241,10 @@
// First, fade in the container view
val containerDuration = duration / 6
createAndStartFadeInAnimator(
- rootView, containerDuration, startDelay = 0, interpolator = fadeInInterpolator
+ rootView,
+ containerDuration,
+ startDelay = 0,
+ interpolator = fadeInInterpolator
)
// Then, fade in the child views
@@ -397,7 +400,7 @@
* included by specifying [includeMargins].
*
* @param onAnimationEnd an optional runnable that will be run once the animation finishes
- * successfully. Will not be run if the animation is cancelled.
+ * successfully. Will not be run if the animation is cancelled.
*/
@JvmOverloads
fun animateRemoval(
@@ -616,6 +619,7 @@
* not newly introduced margins are included.
*
* Base case
+ *
* ```
* 1) origin=TOP
* x---------x x---------x x---------x x---------x x---------x
@@ -636,9 +640,11 @@
* x-----x x-------x | |
* x---------x
* ```
+ *
* In case the start and end values differ in the direction of the origin, and
* [ignorePreviousValues] is false, the previous values are used and a translation is
* included in addition to the view expansion.
+ *
* ```
* origin=TOP_LEFT - (0,0,0,0) -> (30,30,70,70)
* x
@@ -777,8 +783,7 @@
includeMargins: Boolean = false,
): Map<Bound, Int> {
val marginAdjustment =
- if (includeMargins &&
- (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
+ if (includeMargins && (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams
DimenHolder(
left = marginLp.leftMargin,
@@ -786,9 +791,9 @@
right = marginLp.rightMargin,
bottom = marginLp.bottomMargin
)
- } else {
- DimenHolder(0, 0, 0, 0)
- }
+ } else {
+ DimenHolder(0, 0, 0, 0)
+ }
// These are the end values to use *if* this bound is part of the destination.
val endLeft = left - marginAdjustment.left
@@ -805,60 +810,69 @@
// - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight.
return when (destination) {
- Hotspot.TOP -> mapOf(
- Bound.TOP to endTop,
- Bound.BOTTOM to endTop,
- Bound.LEFT to left,
- Bound.RIGHT to right,
- )
- Hotspot.TOP_RIGHT -> mapOf(
- Bound.TOP to endTop,
- Bound.BOTTOM to endTop,
- Bound.RIGHT to endRight,
- Bound.LEFT to endRight,
- )
- Hotspot.RIGHT -> mapOf(
- Bound.RIGHT to endRight,
- Bound.LEFT to endRight,
- Bound.TOP to top,
- Bound.BOTTOM to bottom,
- )
- Hotspot.BOTTOM_RIGHT -> mapOf(
- Bound.BOTTOM to endBottom,
- Bound.TOP to endBottom,
- Bound.RIGHT to endRight,
- Bound.LEFT to endRight,
- )
- Hotspot.BOTTOM -> mapOf(
- Bound.BOTTOM to endBottom,
- Bound.TOP to endBottom,
- Bound.LEFT to left,
- Bound.RIGHT to right,
- )
- Hotspot.BOTTOM_LEFT -> mapOf(
- Bound.BOTTOM to endBottom,
- Bound.TOP to endBottom,
- Bound.LEFT to endLeft,
- Bound.RIGHT to endLeft,
- )
- Hotspot.LEFT -> mapOf(
- Bound.LEFT to endLeft,
- Bound.RIGHT to endLeft,
- Bound.TOP to top,
- Bound.BOTTOM to bottom,
- )
- Hotspot.TOP_LEFT -> mapOf(
- Bound.TOP to endTop,
- Bound.BOTTOM to endTop,
- Bound.LEFT to endLeft,
- Bound.RIGHT to endLeft,
- )
- Hotspot.CENTER -> mapOf(
- Bound.LEFT to (endLeft + endRight) / 2,
- Bound.RIGHT to (endLeft + endRight) / 2,
- Bound.TOP to (endTop + endBottom) / 2,
- Bound.BOTTOM to (endTop + endBottom) / 2,
- )
+ Hotspot.TOP ->
+ mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.LEFT to left,
+ Bound.RIGHT to right,
+ )
+ Hotspot.TOP_RIGHT ->
+ mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ )
+ Hotspot.RIGHT ->
+ mapOf(
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ Bound.TOP to top,
+ Bound.BOTTOM to bottom,
+ )
+ Hotspot.BOTTOM_RIGHT ->
+ mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ )
+ Hotspot.BOTTOM ->
+ mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.LEFT to left,
+ Bound.RIGHT to right,
+ )
+ Hotspot.BOTTOM_LEFT ->
+ mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ )
+ Hotspot.LEFT ->
+ mapOf(
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ Bound.TOP to top,
+ Bound.BOTTOM to bottom,
+ )
+ Hotspot.TOP_LEFT ->
+ mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ )
+ Hotspot.CENTER ->
+ mapOf(
+ Bound.LEFT to (endLeft + endRight) / 2,
+ Bound.RIGHT to (endLeft + endRight) / 2,
+ Bound.TOP to (endTop + endBottom) / 2,
+ Bound.BOTTOM to (endTop + endBottom) / 2,
+ )
}
}
@@ -1083,11 +1097,13 @@
animator.startDelay = startDelay
animator.duration = duration
animator.interpolator = interpolator
- animator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- view.setTag(R.id.tag_alpha_animator, null /* tag */)
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ view.setTag(R.id.tag_alpha_animator, null /* tag */)
+ }
}
- })
+ )
(view.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.cancel()
view.setTag(R.id.tag_alpha_animator, animator)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewRootSync.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/ViewRootSync.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpec.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpec.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackTransformation.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/back/BackTransformation.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableImageView.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableImageView.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableTextView.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/animation/view/LaunchableTextView.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/AnimatorExtensions.kt b/packages/SystemUI/animation/core/src/com/android/systemui/util/AnimatorExtensions.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/util/AnimatorExtensions.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/util/AnimatorExtensions.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt b/packages/SystemUI/animation/core/src/com/android/systemui/util/Dialog.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/util/Dialog.kt
diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt b/packages/SystemUI/animation/core/src/com/android/systemui/util/Dimension.kt
similarity index 100%
rename from packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt
rename to packages/SystemUI/animation/core/src/com/android/systemui/util/Dimension.kt
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
index 4d53cca..cbf2496 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.KeyguardViewConfigurator
import com.android.systemui.keyguard.qualifiers.KeyguardRootView
import com.android.systemui.keyguard.ui.composable.LockscreenScene
+import com.android.systemui.keyguard.ui.composable.LockscreenSceneBlueprintModule
import com.android.systemui.scene.shared.model.Scene
import dagger.Binds
import dagger.Module
@@ -29,7 +30,12 @@
import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
-@Module
+@Module(
+ includes =
+ [
+ LockscreenSceneBlueprintModule::class,
+ ],
+)
interface LockscreenSceneModule {
@Binds @IntoSet fun lockscreenScene(scene: LockscreenScene): Scene
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
new file mode 100644
index 0000000..2cb0034
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.keyguard.ui.composable.blueprint.LockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import javax.inject.Inject
+
+/**
+ * Renders the content of the lockscreen.
+ *
+ * This is separate from the [LockscreenScene] because it's meant to support usage of this UI from
+ * outside the scene container framework.
+ */
+class LockscreenContent
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+ private val blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
+) {
+
+ private val sceneKeyByBlueprint: Map<LockscreenSceneBlueprint, SceneKey> by lazy {
+ blueprints.associateWith { blueprint -> SceneKey(blueprint.id) }
+ }
+ private val sceneKeyByBlueprintId: Map<String, SceneKey> by lazy {
+ sceneKeyByBlueprint.entries.associate { (blueprint, sceneKey) -> blueprint.id to sceneKey }
+ }
+
+ @Composable
+ fun Content(
+ modifier: Modifier = Modifier,
+ ) {
+ val coroutineScope = rememberCoroutineScope()
+ val blueprintId by viewModel.blueprintId(coroutineScope).collectAsState()
+
+ // Switch smoothly between blueprints, any composable tagged with element() will be
+ // transition-animated between any two blueprints, if they both display the same element.
+ SceneTransitionLayout(
+ currentScene = checkNotNull(sceneKeyByBlueprintId[blueprintId]),
+ onChangeScene = {},
+ transitions =
+ transitions { sceneKeyByBlueprintId.values.forEach { sceneKey -> to(sceneKey) } },
+ modifier = modifier,
+ ) {
+ sceneKeyByBlueprint.entries.forEach { (blueprint, sceneKey) ->
+ scene(sceneKey) { with(blueprint) { Content(Modifier.fillMaxSize()) } }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 3053654..93f31ec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
+@file:OptIn(ExperimentalFoundationApi::class)
package com.android.systemui.keyguard.ui.composable
@@ -50,14 +50,17 @@
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.scene.ui.composable.ComposableScene
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+/** Set this to `true` to use the LockscreenContent replacement of KeyguardRootView. */
+private val UseLockscreenContent = false
+
/** The lock screen scene shows when the device is locked. */
@SysUISingleton
class LockscreenScene
@@ -66,6 +69,7 @@
@Application private val applicationScope: CoroutineScope,
private val viewModel: LockscreenSceneViewModel,
@KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
+ private val lockscreenContent: Lazy<LockscreenContent>,
) : ComposableScene {
override val key = SceneKey.Lockscreen
@@ -91,6 +95,7 @@
LockscreenScene(
viewProvider = viewProvider,
viewModel = viewModel,
+ lockscreenContent = lockscreenContent,
modifier = modifier,
)
}
@@ -113,6 +118,7 @@
private fun SceneScope.LockscreenScene(
viewProvider: () -> View,
viewModel: LockscreenSceneViewModel,
+ lockscreenContent: Lazy<LockscreenContent>,
modifier: Modifier = Modifier,
) {
fun findSettingsMenu(): View {
@@ -133,16 +139,24 @@
modifier = Modifier.fillMaxSize(),
)
- AndroidView(
- factory = { _ ->
- val keyguardRootView = viewProvider()
- // Remove the KeyguardRootView from any parent it might already have in legacy code
- // just in case (a view can't have two parents).
- (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
- keyguardRootView
- },
- modifier = Modifier.fillMaxSize(),
- )
+ if (UseLockscreenContent) {
+ lockscreenContent
+ .get()
+ .Content(
+ modifier = Modifier.fillMaxSize(),
+ )
+ } else {
+ AndroidView(
+ factory = { _ ->
+ val keyguardRootView = viewProvider()
+ // Remove the KeyguardRootView from any parent it might already have in legacy
+ // code just in case (a view can't have two parents).
+ (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
+ keyguardRootView
+ },
+ modifier = Modifier.fillMaxSize(),
+ )
+ }
val notificationStackPosition by viewModel.keyguardRoot.notificationBounds.collectAsState()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
new file mode 100644
index 0000000..9abb50c
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable
+
+import com.android.systemui.keyguard.ui.composable.blueprint.CommunalBlueprintModule
+import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule
+import com.android.systemui.keyguard.ui.composable.blueprint.ShortcutsBesideUdfpsBlueprintModule
+import com.android.systemui.keyguard.ui.composable.blueprint.SplitShadeBlueprintModule
+import dagger.Module
+
+@Module(
+ includes =
+ [
+ CommunalBlueprintModule::class,
+ DefaultBlueprintModule::class,
+ ShortcutsBesideUdfpsBlueprintModule::class,
+ SplitShadeBlueprintModule::class,
+ ],
+)
+interface LockscreenSceneBlueprintModule
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
new file mode 100644
index 0000000..7eddaaf
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable.blueprint
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+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.Color
+import com.android.compose.animation.scene.SceneScope
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import javax.inject.Inject
+
+/** Renders the lockscreen scene when showing the communal glanceable hub. */
+class CommunalBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+
+ override val id: String = "communal"
+
+ @Composable
+ override fun SceneScope.Content(modifier: Modifier) {
+ Box(modifier.background(Color.Black)) {
+ Text(
+ text = "TODO(b/316211368): communal blueprint",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+}
+
+@Module
+interface CommunalBlueprintModule {
+ @Binds @IntoSet fun blueprint(blueprint: CommunalBlueprint): LockscreenSceneBlueprint
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
new file mode 100644
index 0000000..fc1df84
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable.blueprint
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.IntOffset
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.height
+import com.android.compose.modifiers.width
+import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
+import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
+import com.android.systemui.keyguard.ui.composable.section.ClockSection
+import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
+import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import javax.inject.Inject
+
+/**
+ * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
+ * factor).
+ */
+class DefaultBlueprint
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+ private val statusBarSection: StatusBarSection,
+ private val clockSection: ClockSection,
+ private val smartSpaceSection: SmartSpaceSection,
+ private val notificationSection: NotificationSection,
+ private val lockSection: LockSection,
+ private val ambientIndicationSection: AmbientIndicationSection,
+ private val bottomAreaSection: BottomAreaSection,
+) : LockscreenSceneBlueprint {
+
+ override val id: String = "default"
+
+ @Composable
+ override fun SceneScope.Content(modifier: Modifier) {
+ val context = LocalContext.current
+ val lockIconBounds = lockSection.lockIconBounds(context)
+ val isUdfpsVisible = viewModel.isUdfpsVisible
+
+ Box(
+ modifier = modifier,
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth().height { lockIconBounds.top },
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+ with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+ with(notificationSection) {
+ Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+ }
+ if (!isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+ }
+
+ with(lockSection) {
+ LockIcon(
+ modifier =
+ Modifier.width { lockIconBounds.width() }
+ .height { lockIconBounds.height() }
+ .offset { IntOffset(lockIconBounds.left, lockIconBounds.top) }
+ )
+ }
+
+ Column(modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)) {
+ if (isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ with(bottomAreaSection) { BottomArea(modifier = Modifier.fillMaxWidth()) }
+ }
+ }
+ }
+}
+
+@Module
+interface DefaultBlueprintModule {
+ @Binds @IntoSet fun blueprint(blueprint: DefaultBlueprint): LockscreenSceneBlueprint
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt
new file mode 100644
index 0000000..6d9cba4
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable.blueprint
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.SceneScope
+
+/** Defines interface for classes that can render the content for a specific blueprint/layout. */
+interface LockscreenSceneBlueprint {
+
+ /** The ID that uniquely identifies this blueprint across all other blueprints. */
+ val id: String
+
+ /** Renders the content of this blueprint. */
+ @Composable fun SceneScope.Content(modifier: Modifier)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
new file mode 100644
index 0000000..fa913f1
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable.blueprint
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.IntOffset
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.height
+import com.android.compose.modifiers.width
+import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
+import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
+import com.android.systemui.keyguard.ui.composable.section.ClockSection
+import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
+import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import javax.inject.Inject
+import kotlin.math.roundToInt
+
+/**
+ * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
+ * factor).
+ */
+class ShortcutsBesideUdfpsBlueprint
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+ private val statusBarSection: StatusBarSection,
+ private val clockSection: ClockSection,
+ private val smartSpaceSection: SmartSpaceSection,
+ private val notificationSection: NotificationSection,
+ private val lockSection: LockSection,
+ private val ambientIndicationSection: AmbientIndicationSection,
+ private val bottomAreaSection: BottomAreaSection,
+) : LockscreenSceneBlueprint {
+
+ override val id: String = "shortcuts-besides-udfps"
+
+ @Composable
+ override fun SceneScope.Content(modifier: Modifier) {
+ val context = LocalContext.current
+ val lockIconBounds = lockSection.lockIconBounds(context)
+ val isUdfpsVisible = viewModel.isUdfpsVisible
+
+ Box(
+ modifier = modifier,
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth().height { lockIconBounds.top },
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+ with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+ with(notificationSection) {
+ Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+ }
+ if (!isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+ }
+
+ val shortcutSizePx =
+ with(LocalDensity.current) { bottomAreaSection.shortcutSizeDp().toSize() }
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ Modifier.fillMaxWidth().offset {
+ val rowTop =
+ if (shortcutSizePx.height > lockIconBounds.height()) {
+ (lockIconBounds.top -
+ (shortcutSizePx.height + lockIconBounds.height()) / 2)
+ .roundToInt()
+ } else {
+ lockIconBounds.top
+ }
+
+ IntOffset(0, rowTop)
+ },
+ ) {
+ Spacer(Modifier.weight(1f))
+
+ with(bottomAreaSection) { Shortcut(isStart = true) }
+
+ Spacer(Modifier.weight(1f))
+
+ with(lockSection) {
+ LockIcon(
+ modifier =
+ Modifier.width { lockIconBounds.width() }
+ .height { lockIconBounds.height() }
+ )
+ }
+
+ Spacer(Modifier.weight(1f))
+
+ with(bottomAreaSection) { Shortcut(isStart = false) }
+
+ Spacer(Modifier.weight(1f))
+ }
+
+ Column(modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)) {
+ if (isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ with(bottomAreaSection) {
+ IndicationArea(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ horizontal =
+ dimensionResource(
+ R.dimen.keyguard_affordance_horizontal_offset
+ )
+ )
+ .padding(
+ bottom =
+ dimensionResource(
+ R.dimen.keyguard_affordance_vertical_offset
+ )
+ )
+ .heightIn(min = shortcutSizeDp().height),
+ )
+ }
+ }
+ }
+ }
+}
+
+@Module
+interface ShortcutsBesideUdfpsBlueprintModule {
+ @Binds
+ @IntoSet
+ fun blueprint(blueprint: ShortcutsBesideUdfpsBlueprint): LockscreenSceneBlueprint
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
new file mode 100644
index 0000000..7545d5f
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable.blueprint
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+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.Color
+import com.android.compose.animation.scene.SceneScope
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import javax.inject.Inject
+
+/**
+ * Renders the lockscreen scene when showing with a split shade (e.g. unfolded foldable and/or
+ * tablet form factor).
+ */
+class SplitShadeBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+
+ override val id: String = "split-shade"
+
+ @Composable
+ override fun SceneScope.Content(modifier: Modifier) {
+ Box(modifier.background(Color.Black)) {
+ Text(
+ text = "TODO(b/316211368): split shade blueprint",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+}
+
+@Module
+interface SplitShadeBlueprintModule {
+ @Binds @IntoSet fun blueprint(blueprint: SplitShadeBlueprint): LockscreenSceneBlueprint
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt
new file mode 100644
index 0000000..0e7ac5e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable.section
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+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.Color
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import javax.inject.Inject
+
+class AmbientIndicationSection @Inject constructor() {
+ @Composable
+ fun SceneScope.AmbientIndication(modifier: Modifier = Modifier) {
+ MovableElement(
+ key = AmbientIndicationElementKey,
+ modifier = modifier,
+ ) {
+ Box(
+ modifier = Modifier.fillMaxWidth().background(Color.Green),
+ ) {
+ Text(
+ text = "TODO(b/316211368): Ambient indication",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+ }
+}
+
+private val AmbientIndicationElementKey = ElementKey("AmbientIndication")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
new file mode 100644
index 0000000..53e4be3
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable.section
+
+import android.view.View
+import android.widget.ImageView
+import androidx.annotation.IdRes
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.content.res.ResourcesCompat
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.animation.view.LaunchableImageView
+import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
+import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.VibratorHelper
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.Flow
+
+class BottomAreaSection
+@Inject
+constructor(
+ private val viewModel: KeyguardQuickAffordancesCombinedViewModel,
+ private val falsingManager: FalsingManager,
+ private val vibratorHelper: VibratorHelper,
+ private val indicationController: KeyguardIndicationController,
+ private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
+ private val keyguardRootViewModel: KeyguardRootViewModel,
+) {
+ @Composable
+ fun SceneScope.BottomArea(
+ modifier: Modifier = Modifier,
+ ) {
+ Row(
+ modifier =
+ modifier
+ .padding(
+ horizontal =
+ dimensionResource(R.dimen.keyguard_affordance_horizontal_offset)
+ )
+ .padding(
+ bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset)
+ ),
+ ) {
+ Shortcut(
+ isStart = true,
+ )
+
+ IndicationArea(
+ modifier = Modifier.weight(1f),
+ )
+
+ Shortcut(
+ isStart = false,
+ )
+ }
+ }
+
+ @Composable
+ fun SceneScope.Shortcut(
+ isStart: Boolean,
+ modifier: Modifier = Modifier,
+ ) {
+ MovableElement(
+ key = if (isStart) StartButtonElementKey else EndButtonElementKey,
+ modifier = modifier,
+ ) {
+ Shortcut(
+ viewId = if (isStart) R.id.start_button else R.id.end_button,
+ viewModel = if (isStart) viewModel.startButton else viewModel.endButton,
+ transitionAlpha = viewModel.transitionAlpha,
+ falsingManager = falsingManager,
+ vibratorHelper = vibratorHelper,
+ indicationController = indicationController,
+ )
+ }
+ }
+
+ @Composable
+ fun SceneScope.IndicationArea(
+ modifier: Modifier = Modifier,
+ ) {
+ MovableElement(
+ key = IndicationAreaElementKey,
+ modifier = modifier,
+ ) {
+ IndicationArea(
+ indicationAreaViewModel = indicationAreaViewModel,
+ keyguardRootViewModel = keyguardRootViewModel,
+ indicationController = indicationController,
+ )
+ }
+ }
+
+ @Composable
+ fun shortcutSizeDp(): DpSize {
+ return DpSize(
+ width = dimensionResource(R.dimen.keyguard_affordance_fixed_width),
+ height = dimensionResource(R.dimen.keyguard_affordance_fixed_height),
+ )
+ }
+
+ @Composable
+ private fun Shortcut(
+ @IdRes viewId: Int,
+ viewModel: Flow<KeyguardQuickAffordanceViewModel>,
+ transitionAlpha: Flow<Float>,
+ falsingManager: FalsingManager,
+ vibratorHelper: VibratorHelper,
+ indicationController: KeyguardIndicationController,
+ modifier: Modifier = Modifier,
+ ) {
+ val (binding, setBinding) = mutableStateOf<KeyguardQuickAffordanceViewBinder.Binding?>(null)
+
+ AndroidView(
+ factory = { context ->
+ val padding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyguard_affordance_fixed_padding
+ )
+ val view =
+ LaunchableImageView(context, null).apply {
+ id = viewId
+ scaleType = ImageView.ScaleType.FIT_CENTER
+ background =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_bg,
+ context.theme
+ )
+ foreground =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_selected_border,
+ context.theme
+ )
+ visibility = View.INVISIBLE
+ setPadding(padding, padding, padding, padding)
+ }
+
+ setBinding(
+ KeyguardQuickAffordanceViewBinder.bind(
+ view,
+ viewModel,
+ transitionAlpha,
+ falsingManager,
+ vibratorHelper,
+ ) {
+ indicationController.showTransientIndication(it)
+ }
+ )
+
+ view
+ },
+ onRelease = { binding?.destroy() },
+ modifier =
+ modifier.size(
+ width = shortcutSizeDp().width,
+ height = shortcutSizeDp().height,
+ )
+ )
+ }
+
+ @Composable
+ private fun IndicationArea(
+ indicationAreaViewModel: KeyguardIndicationAreaViewModel,
+ keyguardRootViewModel: KeyguardRootViewModel,
+ indicationController: KeyguardIndicationController,
+ modifier: Modifier = Modifier,
+ ) {
+ val (disposable, setDisposable) = mutableStateOf<DisposableHandle?>(null)
+
+ AndroidView(
+ factory = { context ->
+ val view = KeyguardIndicationArea(context, null)
+ setDisposable(
+ KeyguardIndicationAreaBinder.bind(
+ view = view,
+ viewModel = indicationAreaViewModel,
+ keyguardRootViewModel = keyguardRootViewModel,
+ indicationController = indicationController,
+ )
+ )
+ view
+ },
+ onRelease = { disposable?.dispose() },
+ modifier = modifier.fillMaxWidth(),
+ )
+ }
+}
+
+private val StartButtonElementKey = ElementKey("StartButton")
+private val EndButtonElementKey = ElementKey("EndButton")
+private val IndicationAreaElementKey = ElementKey("IndicationArea")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
new file mode 100644
index 0000000..eaf8063
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable.section
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+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.Color
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import javax.inject.Inject
+
+class ClockSection
+@Inject
+constructor(
+ private val viewModel: KeyguardClockViewModel,
+) {
+ @Composable
+ fun SceneScope.SmallClock(modifier: Modifier = Modifier) {
+ if (viewModel.useLargeClock) {
+ return
+ }
+
+ MovableElement(
+ key = ClockElementKey,
+ modifier = modifier,
+ ) {
+ Box(
+ modifier = Modifier.fillMaxWidth().background(Color.Magenta),
+ ) {
+ Text(
+ text = "TODO(b/316211368): Small clock",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+ }
+
+ @Composable
+ fun SceneScope.LargeClock(modifier: Modifier = Modifier) {
+ if (!viewModel.useLargeClock) {
+ return
+ }
+
+ MovableElement(
+ key = ClockElementKey,
+ modifier = modifier,
+ ) {
+ Box(
+ modifier = Modifier.fillMaxWidth().background(Color.Blue),
+ ) {
+ Text(
+ text = "TODO(b/316211368): Large clock",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+ }
+}
+
+private val ClockElementKey = ElementKey("Clock")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
new file mode 100644
index 0000000..8bbe424b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable.section
+
+import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.DisplayMetrics
+import android.view.WindowManager
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+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.Color
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class LockSection
+@Inject
+constructor(
+ private val windowManager: WindowManager,
+ private val authController: AuthController,
+ private val featureFlags: FeatureFlagsClassic,
+) {
+ @Composable
+ fun SceneScope.LockIcon(modifier: Modifier = Modifier) {
+ MovableElement(
+ key = LockIconElementKey,
+ modifier = modifier,
+ ) {
+ Box(
+ modifier = Modifier.background(Color.Red),
+ ) {
+ Text(
+ text = "TODO(b/316211368): Lock",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+ }
+
+ /**
+ * Returns the bounds of the lock icon, in window view coordinates.
+ *
+ * On devices that support UDFPS (under-display fingerprint sensor), the bounds of the icon are
+ * the same as the bounds of the sensor.
+ */
+ fun lockIconBounds(
+ context: Context,
+ ): Rect {
+ val windowViewBounds = windowManager.currentWindowMetrics.bounds
+ var widthPx = windowViewBounds.right.toFloat()
+ if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
+ val insets = windowManager.currentWindowMetrics.windowInsets
+ // Assumed to be initially neglected as there are no left or right insets in portrait.
+ // However, on landscape, these insets need to included when calculating the midpoint.
+ @Suppress("DEPRECATION")
+ widthPx -= (insets.systemWindowInsetLeft + insets.systemWindowInsetRight).toFloat()
+ }
+ val defaultDensity =
+ DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+ DisplayMetrics.DENSITY_DEFAULT.toFloat()
+ val lockIconRadiusPx = (defaultDensity * 36).toInt()
+
+ val udfpsLocation = authController.udfpsLocation
+ return if (authController.isUdfpsSupported && udfpsLocation != null) {
+ centerLockIcon(udfpsLocation, authController.udfpsRadius)
+ } else {
+ val scaleFactor = authController.scaleFactor
+ val bottomPaddingPx =
+ context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
+ val heightPx = windowViewBounds.bottom.toFloat()
+
+ centerLockIcon(
+ Point(
+ (widthPx / 2).toInt(),
+ (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
+ ),
+ lockIconRadiusPx * scaleFactor
+ )
+ }
+ }
+
+ private fun centerLockIcon(
+ center: Point,
+ radius: Float,
+ ): Rect {
+ return Rect().apply {
+ set(
+ center.x - radius.toInt(),
+ center.y - radius.toInt(),
+ center.x + radius.toInt(),
+ center.y + radius.toInt(),
+ )
+ }
+ }
+}
+
+private val LockIconElementKey = ElementKey("LockIcon")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
new file mode 100644
index 0000000..f135be2
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable.section
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+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.Color
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import javax.inject.Inject
+
+class NotificationSection @Inject constructor() {
+ @Composable
+ fun SceneScope.Notifications(modifier: Modifier = Modifier) {
+ MovableElement(
+ key = NotificationsElementKey,
+ modifier = modifier,
+ ) {
+ Box(
+ modifier = Modifier.fillMaxSize().background(Color.Yellow),
+ ) {
+ Text(
+ text = "TODO(b/316211368): Notifications",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+ }
+}
+
+private val NotificationsElementKey = ElementKey("Notifications")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
new file mode 100644
index 0000000..ca03af5
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable.section
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+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.Color
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import javax.inject.Inject
+
+class SmartSpaceSection @Inject constructor() {
+ @Composable
+ fun SceneScope.SmartSpace(modifier: Modifier = Modifier) {
+ MovableElement(key = SmartSpaceElementKey, modifier = modifier) {
+ Box(
+ modifier = Modifier.fillMaxWidth().background(Color.Cyan),
+ ) {
+ Text(
+ text = "TODO(b/316211368): Smart space",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+ }
+ }
+}
+
+private val SmartSpaceElementKey = ElementKey("SmartSpace")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
new file mode 100644
index 0000000..6811eb4
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.composable.section
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.height
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent
+import com.android.systemui.res.R
+import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.shade.ShadeViewStateProvider
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView
+import com.android.systemui.util.Utils
+import dagger.Lazy
+import javax.inject.Inject
+
+class StatusBarSection
+@Inject
+constructor(
+ private val componentFactory: KeyguardStatusBarViewComponent.Factory,
+ private val notificationPanelView: Lazy<NotificationPanelView>,
+) {
+ @Composable
+ fun SceneScope.StatusBar(modifier: Modifier = Modifier) {
+ val context = LocalContext.current
+
+ MovableElement(
+ key = StatusBarElementKey,
+ modifier = modifier,
+ ) {
+ AndroidView(
+ factory = {
+ notificationPanelView.get().findViewById<View>(R.id.keyguard_header)?.let {
+ (it.parent as ViewGroup).removeView(it)
+ }
+
+ val provider =
+ object : ShadeViewStateProvider {
+ override val lockscreenShadeDragProgress: Float = 0f
+ override val panelViewExpandedHeight: Float = 0f
+ override fun shouldHeadsUpBeVisible(): Boolean {
+ return false
+ }
+ }
+
+ @SuppressLint("InflateParams")
+ val view =
+ LayoutInflater.from(context)
+ .inflate(
+ R.layout.keyguard_status_bar,
+ null,
+ false,
+ ) as KeyguardStatusBarView
+ componentFactory.build(view, provider).keyguardStatusBarViewController.init()
+ view
+ },
+ modifier =
+ Modifier.fillMaxWidth().height {
+ Utils.getStatusBarHeaderHeightKeyguard(context)
+ },
+ )
+ }
+ }
+}
+
+private val StatusBarElementKey = ElementKey("StatusBar")
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 5dc1079..a85d9bf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -16,6 +16,7 @@
package com.android.compose.animation.scene
+import android.graphics.Picture
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
@@ -66,12 +67,21 @@
* The movable content of this element, if this element is composed using
* [SceneScope.MovableElement].
*/
- val movableContent by
- // This is only accessed from the composition (main) thread, so no need to use the default
- // lock of lazy {} to synchronize.
- lazy(mode = LazyThreadSafetyMode.NONE) {
- movableContentOf { content: @Composable () -> Unit -> content() }
- }
+ private var _movableContent: (@Composable (@Composable () -> Unit) -> Unit)? = null
+ val movableContent: @Composable (@Composable () -> Unit) -> Unit
+ get() =
+ _movableContent
+ ?: movableContentOf { content: @Composable () -> Unit -> content() }
+ .also { _movableContent = it }
+
+ /**
+ * The [Picture] to which we save the last drawing commands of this element, if it is movable.
+ * This is necessary because the content of this element might not be composed in the scene it
+ * should currently be drawn.
+ */
+ private var _picture: Picture? = null
+ val picture: Picture
+ get() = _picture ?: Picture().also { _picture = it }
override fun toString(): String {
return "Element(key=$key)"
@@ -235,18 +245,16 @@
}
override fun ContentDrawScope.draw() {
- if (shouldDrawElement(layoutImpl, scene, element)) {
- val drawScale = getDrawScale(layoutImpl, element, scene, sceneValues)
- if (drawScale == Scale.Default) {
- drawContent()
- } else {
- scale(
- drawScale.scaleX,
- drawScale.scaleY,
- if (drawScale.pivot.isUnspecified) center else drawScale.pivot,
- ) {
- this@draw.drawContent()
- }
+ val drawScale = getDrawScale(layoutImpl, element, scene, sceneValues)
+ if (drawScale == Scale.Default) {
+ drawContent()
+ } else {
+ scale(
+ drawScale.scaleX,
+ drawScale.scaleY,
+ if (drawScale.pivot.isUnspecified) center else drawScale.pivot,
+ ) {
+ this@draw.drawContent()
}
}
}
@@ -516,7 +524,7 @@
with(placementScope) {
// Update the offset (relative to the SceneTransitionLayout) this element has in this scene
// when idle.
- val coords = coordinates!!
+ val coords = coordinates ?: error("Element ${element.key} does not have any coordinates")
val targetOffsetInScene = lookaheadScopeCoordinates.localLookaheadPositionOf(coords)
if (targetOffsetInScene != sceneValues.targetOffset) {
// TODO(b/290930950): Better handle when this changes to avoid instant offset jumps.
@@ -545,6 +553,13 @@
lastSharedValues.offset = targetOffset
lastValues.offset = targetOffset
+ // No need to place the element in this scene if we don't want to draw it anyways. Note that
+ // it's still important to compute the target offset and update lastValues, otherwise it
+ // will be out of date.
+ if (!shouldDrawElement(layoutImpl, scene, element)) {
+ return
+ }
+
val offset = (targetOffset - currentOffset).round()
if (isElementOpaque(layoutImpl, element, scene, sceneValues)) {
// TODO(b/291071158): Call placeWithLayer() if offset != IntOffset.Zero and size is not
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 306f276..49df2f6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -16,7 +16,6 @@
package com.android.compose.animation.scene
-import android.graphics.Picture
import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
@@ -60,7 +59,7 @@
// The [Picture] to which we save the last drawing commands of this element. This is
// necessary because the content of this element might not be composed in this scene, in
// which case we still need to draw it.
- val picture = remember { Picture() }
+ val picture = element.picture
// Whether we should compose the movable element here. The scene picker logic to know in
// which scene we should compose/draw a movable element might depend on the current
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 6a7a3a0..30e50a9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -34,6 +34,7 @@
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.zIndex
+import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions
/** A scene in a [SceneTransitionLayout]. */
@Stable
@@ -152,4 +153,8 @@
bounds: ElementKey,
shape: Shape
): Modifier = punchHole(layoutImpl, element, bounds, shape)
+
+ override fun Modifier.noResizeDuringTransitions(): Modifier {
+ return noResizeDuringTransitions(layoutState = layoutImpl.state)
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 3608e37..5eb339e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -65,11 +65,11 @@
SceneTransitionLayoutForTesting(
currentScene,
onChangeScene,
+ modifier,
transitions,
state,
edgeDetector,
transitionInterceptionThreshold,
- modifier,
onLayoutImpl = null,
scenes,
)
@@ -205,6 +205,12 @@
* the result.
*/
fun Modifier.punchHole(element: ElementKey, bounds: ElementKey, shape: Shape): Modifier
+
+ /**
+ * Don't resize during transitions. This can for instance be used to make sure that scrollable
+ * lists keep a constant size during transitions even if its elements are growing/shrinking.
+ */
+ fun Modifier.noResizeDuringTransitions(): Modifier
}
// TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey
@@ -257,12 +263,12 @@
internal fun SceneTransitionLayoutForTesting(
currentScene: SceneKey,
onChangeScene: (SceneKey) -> Unit,
- transitions: SceneTransitions,
- state: SceneTransitionLayoutState,
- edgeDetector: EdgeDetector,
- transitionInterceptionThreshold: Float,
- modifier: Modifier,
- onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)?,
+ modifier: Modifier = Modifier,
+ transitions: SceneTransitions = transitions {},
+ state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) },
+ edgeDetector: EdgeDetector = DefaultEdgeDetector,
+ transitionInterceptionThreshold: Float = 0f,
+ onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
scenes: SceneTransitionLayoutScope.() -> Unit,
) {
val density = LocalDensity.current
@@ -280,6 +286,10 @@
.also { onLayoutImpl?.invoke(it) }
}
+ // TODO(b/317014852): Move this into the SideEffect {} again once STLImpl.scenes is not a
+ // SnapshotStateMap anymore.
+ layoutImpl.updateScenes(scenes)
+
val targetSceneChannel = remember { Channel<SceneKey>(Channel.CONFLATED) }
SideEffect {
if (state != layoutImpl.state) {
@@ -293,7 +303,6 @@
(state as SceneTransitionLayoutStateImpl).transitions = transitions
layoutImpl.density = density
layoutImpl.edgeDetector = edgeDetector
- layoutImpl.updateScenes(scenes)
state.transitions = transitions
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index c99c325..45e1a0f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -46,7 +46,12 @@
builder: SceneTransitionLayoutScope.() -> Unit,
coroutineScope: CoroutineScope,
) {
- internal val scenes = mutableMapOf<SceneKey, Scene>()
+ /**
+ * The map of [Scene]s.
+ *
+ * TODO(b/317014852): Make this a normal MutableMap instead.
+ */
+ internal val scenes = SnapshotStateMap<SceneKey, Scene>()
/**
* The map of [Element]s.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt
new file mode 100644
index 0000000..bd36cb8
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/modifiers/Size.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.compose.animation.scene.modifiers
+
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.unit.Constraints
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+
+@OptIn(ExperimentalComposeUiApi::class)
+internal fun Modifier.noResizeDuringTransitions(layoutState: SceneTransitionLayoutState): Modifier {
+ return intermediateLayout { measurable, constraints ->
+ if (layoutState.currentTransition == null) {
+ return@intermediateLayout measurable.measure(constraints).run {
+ layout(width, height) { place(0, 0) }
+ }
+ }
+
+ // Make sure that this layout node has the same size than when we are at rest.
+ val sizeAtRest = lookaheadSize
+ measurable.measure(Constraints.fixed(sizeAtRest.width, sizeAtRest.height)).run {
+ layout(width, height) { place(0, 0) }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 439dc00..da5a0a0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -16,6 +16,7 @@
package com.android.compose.animation.scene
+import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
@@ -30,16 +31,21 @@
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.test.subjects.DpOffsetSubject
+import com.android.compose.test.subjects.assertThat
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -116,7 +122,13 @@
toSceneContent = {
Box(Modifier.size(layoutSize)) {
// Shared element.
- Element(TestElements.Foo, elementSize, elementOffset)
+ Element(
+ TestElements.Foo,
+ elementSize,
+ elementOffset,
+ onLayout = { fooLayouts++ },
+ onPlacement = { fooPlacements++ },
+ )
}
},
transition = {
@@ -127,21 +139,25 @@
scaleSize(TestElements.Bar, width = 1f, height = 1f)
},
) {
- var numberOfLayoutsAfterOneAnimationFrame = 0
- var numberOfPlacementsAfterOneAnimationFrame = 0
+ var fooLayoutsAfterOneAnimationFrame = 0
+ var fooPlacementsAfterOneAnimationFrame = 0
+ var barLayoutsAfterOneAnimationFrame = 0
+ var barPlacementsAfterOneAnimationFrame = 0
fun assertNumberOfLayoutsAndPlacements() {
- assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
- assertThat(fooPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame)
- assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
- assertThat(barPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame)
+ assertThat(fooLayouts).isEqualTo(fooLayoutsAfterOneAnimationFrame)
+ assertThat(fooPlacements).isEqualTo(fooPlacementsAfterOneAnimationFrame)
+ assertThat(barLayouts).isEqualTo(barLayoutsAfterOneAnimationFrame)
+ assertThat(barPlacements).isEqualTo(barPlacementsAfterOneAnimationFrame)
}
at(16) {
// Capture the number of layouts and placements that happened after 1 animation
// frame.
- numberOfLayoutsAfterOneAnimationFrame = fooLayouts
- numberOfPlacementsAfterOneAnimationFrame = fooPlacements
+ fooLayoutsAfterOneAnimationFrame = fooLayouts
+ fooPlacementsAfterOneAnimationFrame = fooPlacements
+ barLayoutsAfterOneAnimationFrame = barLayouts
+ barPlacementsAfterOneAnimationFrame = barPlacements
}
repeat(nFrames - 2) { i ->
// Ensure that all animation frames (except the final one) don't relayout or replace
@@ -187,7 +203,13 @@
toSceneContent = {
Box(Modifier.size(layoutSize)) {
// Shared element.
- Element(TestElements.Foo, elementSize, offset = 20.dp)
+ Element(
+ TestElements.Foo,
+ elementSize,
+ offset = 20.dp,
+ onLayout = { fooLayouts++ },
+ onPlacement = { fooPlacements++ },
+ )
}
},
transition = {
@@ -198,25 +220,30 @@
scaleSize(TestElements.Bar, width = 1f, height = 1f)
},
) {
- var numberOfLayoutsAfterOneAnimationFrame = 0
- var lastNumberOfPlacements = 0
+ var fooLayoutsAfterOneAnimationFrame = 0
+ var barLayoutsAfterOneAnimationFrame = 0
+ var lastFooPlacements = 0
+ var lastBarPlacements = 0
fun assertNumberOfLayoutsAndPlacements() {
// The number of layouts have not changed.
- assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
- assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
+ assertThat(fooLayouts).isEqualTo(fooLayoutsAfterOneAnimationFrame)
+ assertThat(barLayouts).isEqualTo(barLayoutsAfterOneAnimationFrame)
// The number of placements have increased.
- assertThat(fooPlacements).isGreaterThan(lastNumberOfPlacements)
- assertThat(barPlacements).isGreaterThan(lastNumberOfPlacements)
- lastNumberOfPlacements = fooPlacements
+ assertThat(fooPlacements).isGreaterThan(lastFooPlacements)
+ assertThat(barPlacements).isGreaterThan(lastBarPlacements)
+ lastFooPlacements = fooPlacements
+ lastBarPlacements = barPlacements
}
at(16) {
// Capture the number of layouts and placements that happened after 1 animation
// frame.
- numberOfLayoutsAfterOneAnimationFrame = fooLayouts
- lastNumberOfPlacements = fooPlacements
+ fooLayoutsAfterOneAnimationFrame = fooLayouts
+ barLayoutsAfterOneAnimationFrame = barLayouts
+ lastFooPlacements = fooPlacements
+ lastBarPlacements = barPlacements
}
repeat(nFrames - 2) { i ->
// Ensure that all animation frames (except the final one) only replaced the
@@ -238,11 +265,6 @@
SceneTransitionLayoutForTesting(
currentScene = currentScene,
onChangeScene = { currentScene = it },
- transitions = remember { transitions {} },
- state = remember { SceneTransitionLayoutState(currentScene) },
- edgeDetector = DefaultEdgeDetector,
- modifier = Modifier,
- transitionInterceptionThreshold = 0f,
onLayoutImpl = { nullableLayoutImpl = it },
) {
scene(TestScenes.SceneA) { /* Nothing */}
@@ -408,11 +430,6 @@
SceneTransitionLayoutForTesting(
currentScene = TestScenes.SceneA,
onChangeScene = {},
- transitions = remember { transitions {} },
- state = remember { SceneTransitionLayoutState(TestScenes.SceneA) },
- edgeDetector = DefaultEdgeDetector,
- modifier = Modifier,
- transitionInterceptionThreshold = 0f,
onLayoutImpl = { nullableLayoutImpl = it },
) {
scene(TestScenes.SceneA) { Box(Modifier.element(key)) }
@@ -463,11 +480,6 @@
SceneTransitionLayoutForTesting(
currentScene = TestScenes.SceneA,
onChangeScene = {},
- transitions = remember { transitions {} },
- state = remember { SceneTransitionLayoutState(TestScenes.SceneA) },
- edgeDetector = DefaultEdgeDetector,
- modifier = Modifier,
- transitionInterceptionThreshold = 0f,
onLayoutImpl = { nullableLayoutImpl = it },
) {
scene(TestScenes.SceneA) {
@@ -553,4 +565,86 @@
after { assertThat(fooCompositions).isEqualTo(1) }
}
}
+
+ @Test
+ fun sharedElementOffsetIsUpdatedEvenWhenNotPlaced() {
+ var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
+ var density: Density? = null
+
+ fun layoutImpl() = nullableLayoutImpl ?: error("nullableLayoutImpl was not set")
+
+ fun density() = density ?: error("density was not set")
+
+ fun Offset.toDpOffset() = with(density()) { DpOffset(x.toDp(), y.toDp()) }
+
+ fun foo() = layoutImpl().elements[TestElements.Foo] ?: error("Foo not in elements map")
+
+ fun Element.lastSharedOffset() = lastSharedValues.offset.toDpOffset()
+
+ fun Element.lastOffsetIn(scene: SceneKey) =
+ (sceneValues[scene] ?: error("$scene not in sceneValues map"))
+ .lastValues
+ .offset
+ .toDpOffset()
+
+ rule.testTransition(
+ from = TestScenes.SceneA,
+ to = TestScenes.SceneB,
+ transitionLayout = { currentScene, onChangeScene ->
+ density = LocalDensity.current
+
+ SceneTransitionLayoutForTesting(
+ currentScene = currentScene,
+ onChangeScene = onChangeScene,
+ onLayoutImpl = { nullableLayoutImpl = it },
+ transitions =
+ transitions {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) {
+ spec = tween(durationMillis = 4 * 16, easing = LinearEasing)
+ }
+ }
+ ) {
+ scene(TestScenes.SceneA) { Box(Modifier.element(TestElements.Foo)) }
+ scene(TestScenes.SceneB) {
+ Box(Modifier.offset(x = 40.dp, y = 80.dp).element(TestElements.Foo))
+ }
+ }
+ }
+ ) {
+ val tolerance = DpOffsetSubject.DefaultTolerance
+
+ before {
+ val expected = DpOffset(0.dp, 0.dp)
+ assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
+ }
+
+ at(16) {
+ val expected = DpOffset(10.dp, 20.dp)
+ assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
+ }
+
+ at(32) {
+ val expected = DpOffset(20.dp, 40.dp)
+ assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
+ }
+
+ at(48) {
+ val expected = DpOffset(30.dp, 60.dp)
+ assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneA)).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
+ }
+
+ after {
+ val expected = DpOffset(40.dp, 80.dp)
+ assertThat(foo().lastSharedOffset()).isWithin(tolerance).of(expected)
+ assertThat(foo().lastOffsetIn(TestScenes.SceneB)).isWithin(tolerance).of(expected)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 83af630..3cd65cd 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -30,6 +30,7 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.hasParent
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
@@ -92,15 +93,15 @@
}
at(32) {
- // In the middle of the transition, there are 2 copies of the counter: the previous
- // one from scene A (equal to 3) and the new one from scene B (equal to 0).
+ // In the middle of the transition, 2 copies of the counter are composed but only
+ // the one in scene B is placed/drawn.
rule
.onNode(
hasText("count: 3") and
hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA))
)
- .assertIsDisplayed()
- .assertSizeIsEqualTo(75.dp, 75.dp)
+ .assertExists()
+ .assertIsNotDisplayed()
rule
.onNode(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 321cf63..ebbd500 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -40,9 +40,7 @@
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onChild
-import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.unit.Dp
@@ -251,9 +249,9 @@
// Advance to the middle of the animation.
rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
- // We need to use onAllNodesWithTag().onFirst() here given that shared elements are
- // composed and laid out in both scenes (but drawn only in one).
- sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.testTag).onFirst()
+ // Foo is shared between Scene A and Scene B, and is therefore placed/drawn in Scene B given
+ // that B has a higher zIndex than A.
+ sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneB))
// In scene B, foo is at the top start (x = 0, y = 0) of the layout and has a size of
// 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
@@ -287,7 +285,7 @@
val expectedLeft = 0.dp
val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
- sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.testTag).onFirst()
+ sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneC))
assertThat((layoutState.transitionState as TransitionState.Transition).progress)
.isEqualTo(interpolatedProgress)
sharedFoo.assertWidthIsEqualTo(expectedSize)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt
new file mode 100644
index 0000000..2c159d1
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/modifiers/SizeTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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.compose.animation.scene.modifiers
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.element
+import com.android.compose.animation.scene.testTransition
+import com.android.compose.test.assertSizeIsEqualTo
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SizeTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun noResizeDuringTransitions() {
+ // The tag for the parent of the shared Foo element.
+ val parentTag = "parent"
+
+ rule.testTransition(
+ fromSceneContent = { Box(Modifier.element(TestElements.Foo).size(100.dp)) },
+ toSceneContent = {
+ // Don't resize the parent of Foo during transitions so that it's always the same
+ // size as when there is no transition (200dp).
+ Box(Modifier.noResizeDuringTransitions().testTag(parentTag)) {
+ Box(Modifier.element(TestElements.Foo).size(200.dp))
+ }
+ },
+ transition = { spec = tween(durationMillis = 4 * 16, easing = LinearEasing) },
+ ) {
+ at(16) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) }
+ at(32) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) }
+ at(48) { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) }
+ after { rule.onNodeWithTag(parentTag).assertSizeIsEqualTo(200.dp, 200.dp) }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
index e94eff3..8001f41 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
@@ -23,6 +23,7 @@
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.unit.dp
@@ -33,7 +34,6 @@
import com.android.compose.animation.scene.inScene
import com.android.compose.animation.scene.testTransition
import com.android.compose.test.assertSizeIsEqualTo
-import com.android.compose.test.onEach
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -63,28 +63,34 @@
onElement(TestElements.Foo).assertSizeIsEqualTo(20.dp, 80.dp)
}
at(0) {
- onSharedElement(TestElements.Foo).onEach {
- assertPositionInRootIsEqualTo(10.dp, 50.dp)
- assertSizeIsEqualTo(20.dp, 80.dp)
- }
+ // Shared elements are by default placed and drawn only in the scene with highest
+ // zIndex.
+ onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
+
+ onElement(TestElements.Foo, TestScenes.SceneB)
+ .assertPositionInRootIsEqualTo(10.dp, 50.dp)
+ .assertSizeIsEqualTo(20.dp, 80.dp)
}
at(16) {
- onSharedElement(TestElements.Foo).onEach {
- assertPositionInRootIsEqualTo(20.dp, 55.dp)
- assertSizeIsEqualTo(17.5.dp, 70.dp)
- }
+ onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
+
+ onElement(TestElements.Foo, TestScenes.SceneB)
+ .assertPositionInRootIsEqualTo(20.dp, 55.dp)
+ .assertSizeIsEqualTo(17.5.dp, 70.dp)
}
at(32) {
- onSharedElement(TestElements.Foo).onEach {
- assertPositionInRootIsEqualTo(30.dp, 60.dp)
- assertSizeIsEqualTo(15.dp, 60.dp)
- }
+ onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
+
+ onElement(TestElements.Foo, TestScenes.SceneB)
+ .assertPositionInRootIsEqualTo(30.dp, 60.dp)
+ .assertSizeIsEqualTo(15.dp, 60.dp)
}
at(48) {
- onSharedElement(TestElements.Foo).onEach {
- assertPositionInRootIsEqualTo(40.dp, 65.dp)
- assertSizeIsEqualTo(12.5.dp, 50.dp)
- }
+ onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
+
+ onElement(TestElements.Foo, TestScenes.SceneB)
+ .assertPositionInRootIsEqualTo(40.dp, 65.dp)
+ .assertSizeIsEqualTo(12.5.dp, 50.dp)
}
after {
onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 70.dp)
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
new file mode 100644
index 0000000..e743c78
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.compose.animation.scene
+
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.hasParent
+import androidx.compose.ui.test.hasTestTag
+
+/** A [SemanticsMatcher] that matches [element], optionally restricted to scene [scene]. */
+fun isElement(element: ElementKey, scene: SceneKey? = null): SemanticsMatcher {
+ return if (scene == null) {
+ hasTestTag(element.testTag)
+ } else {
+ hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag))
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 06de296..6d6d575 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -21,13 +21,8 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteraction
-import androidx.compose.ui.test.SemanticsNodeInteractionCollection
-import androidx.compose.ui.test.hasParent
-import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import androidx.compose.ui.test.onAllNodesWithTag
@DslMarker annotation class TransitionTestDsl
@@ -62,23 +57,15 @@
@TransitionTestDsl
interface TransitionTestAssertionScope {
- fun isElement(element: ElementKey, scene: SceneKey? = null): SemanticsMatcher
-
/**
* Assert on [element].
*
* Note that presence/value assertions on the returned [SemanticsNodeInteraction] will fail if 0
* or more than 1 elements matched [element]. If you need to assert on a shared element that
- * will be present multiple times in the layout during transitions, either specify the [scene]
- * in which you are matching or use [onSharedElement] instead.
+ * will be present multiple times in the layout during transitions, specify the [scene] in which
+ * you are matching.
*/
fun onElement(element: ElementKey, scene: SceneKey? = null): SemanticsNodeInteraction
-
- /**
- * Assert on a shared [element]. This will throw if [element] is not shared and present only in
- * one scene during a transition.
- */
- fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection
}
/**
@@ -131,29 +118,12 @@
val test = transitionTest(builder)
val assertionScope =
object : TransitionTestAssertionScope {
- override fun isElement(element: ElementKey, scene: SceneKey?): SemanticsMatcher {
- return if (scene == null) {
- hasTestTag(element.testTag)
- } else {
- hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag))
- }
- }
-
override fun onElement(
element: ElementKey,
scene: SceneKey?
): SemanticsNodeInteraction {
return onNode(isElement(element, scene))
}
-
- override fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection {
- val interaction = onAllNodesWithTag(element.testTag)
- val matches = interaction.fetchSemanticsNodes(atLeastOneRootRequired = false).size
- if (matches < 2) {
- error("Element $element is not shared ($matches matches)")
- }
- return interaction
- }
}
var currentScene by mutableStateOf(from)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 0b7c3f9..26da1f0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -17,56 +17,39 @@
package com.android.systemui.shared.notifications.data.repository
import android.provider.Settings
-import com.android.systemui.shared.notifications.shared.model.NotificationSettingsModel
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
-/** Provides access to state related to notifications. */
+/** Provides access to state related to notification settings. */
class NotificationSettingsRepository(
scope: CoroutineScope,
private val backgroundDispatcher: CoroutineDispatcher,
private val secureSettingsRepository: SecureSettingsRepository,
) {
/** The current state of the notification setting. */
- val settings: SharedFlow<NotificationSettingsModel> =
+ val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
secureSettingsRepository
.intSetting(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
)
- .map { lockScreenShowNotificationsInt ->
- NotificationSettingsModel(
- isShowNotificationsOnLockScreenEnabled = lockScreenShowNotificationsInt == 1,
- )
- }
- .shareIn(
+ .map { it == 1 }
+ .stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
- replay = 1,
+ initialValue = false,
)
- suspend fun getSettings(): NotificationSettingsModel {
- return withContext(backgroundDispatcher) {
- NotificationSettingsModel(
- isShowNotificationsOnLockScreenEnabled =
- secureSettingsRepository.get(
- name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
- defaultValue = 0,
- ) == 1
- )
- }
- }
-
- suspend fun setSettings(model: NotificationSettingsModel) {
+ suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) {
withContext(backgroundDispatcher) {
secureSettingsRepository.set(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
- value = if (model.isShowNotificationsOnLockScreenEnabled) 1 else 0,
+ value = if (enabled) 1 else 0,
)
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
index 21f3aca..9ec6ec8 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
@@ -17,32 +17,23 @@
package com.android.systemui.shared.notifications.domain.interactor
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
-import com.android.systemui.shared.notifications.shared.model.NotificationSettingsModel
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/** Encapsulates business logic for interacting with notification settings. */
class NotificationSettingsInteractor(
private val repository: NotificationSettingsRepository,
) {
- /** The current state of the notification setting. */
- val settings: Flow<NotificationSettingsModel> = repository.settings
+ /** Should notifications be visible on the lockscreen? */
+ val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
+ repository.isShowNotificationsOnLockScreenEnabled
+
+ suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) {
+ repository.setShowNotificationsOnLockscreenEnabled(enabled)
+ }
/** Toggles the setting to show or hide notifications on the lock screen. */
- suspend fun toggleShowNotificationsOnLockScreenEnabled() {
- val currentModel = repository.getSettings()
- setSettings(
- currentModel.copy(
- isShowNotificationsOnLockScreenEnabled =
- !currentModel.isShowNotificationsOnLockScreenEnabled,
- )
- )
- }
-
- suspend fun setSettings(model: NotificationSettingsModel) {
- repository.setSettings(model)
- }
-
- suspend fun getSettings(): NotificationSettingsModel {
- return repository.getSettings()
+ suspend fun toggleShowNotificationsOnLockscreenEnabled() {
+ val current = repository.isShowNotificationsOnLockScreenEnabled.value
+ repository.setShowNotificationsOnLockscreenEnabled(!current)
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/shared/model/NotificationSettingsModel.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/shared/model/NotificationSettingsModel.kt
deleted file mode 100644
index 7e35360..0000000
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/shared/model/NotificationSettingsModel.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.shared.notifications.shared.model
-
-/** Models notification settings. */
-data class NotificationSettingsModel(
- /** Whether notifications are shown on the lock screen. */
- val isShowNotificationsOnLockScreenEnabled: Boolean = false,
-)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index dddcf18..a4b55e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -90,7 +90,6 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -124,8 +123,6 @@
import java.util.ArrayList;
import java.util.List;
-import javax.inject.Provider;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
@RunWithLooper(setAsMainLooper = true)
@@ -216,8 +213,6 @@
@Mock
private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
@Mock
- private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
- @Mock
private SelectedUserInteractor mSelectedUserInteractor;
// Capture listeners so that they can be used to send events
@@ -339,7 +334,6 @@
mInputManager,
mock(KeyguardFaceAuthInteractor.class),
mUdfpsKeyguardAccessibilityDelegate,
- mUdfpsKeyguardViewModels,
mSelectedUserInteractor,
mFpsUnlockTracker,
mKeyguardTransitionInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 0ab596c..1f8854f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -616,4 +616,28 @@
.onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN))
job.cancel()
}
+
+ @Test
+ fun bouncerToAod_dozeAmountChanged() =
+ testScope.runTest {
+ // GIVEN view is attached
+ mController.onViewAttached()
+ Mockito.reset(mView)
+
+ val job = mController.listenForPrimaryBouncerToAodTransitions(this)
+ // WHEN alternate bouncer to aod transition in progress
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.AOD,
+ value = .3f,
+ transitionState = TransitionState.RUNNING
+ )
+ )
+ runCurrent()
+
+ // THEN doze amount is updated to
+ verify(mView).onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATE_APPEAR_ON_SCREEN_OFF))
+ job.cancel()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index 0870122..adf4fc6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -32,18 +32,15 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class AlternateBouncerInteractorTest : SysuiTestCase() {
@@ -167,7 +164,6 @@
}
@Test
- @Ignore("b/287599719")
fun canShowAlternateBouncerForFingerprint_rearFps() {
mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
initializeUnderTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
new file mode 100644
index 0000000..9daf186
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlternateBouncerToAodTransitionViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+ private val underTest = kosmos.alternateBouncerToAodTransitionViewModel
+
+ @Test
+ fun deviceEntryParentViewAppear() =
+ testScope.runTest {
+ fingerprintPropertyRepository.setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.UDFPS_OPTICAL,
+ sensorLocations = emptyMap(),
+ )
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ val values by collectValues(underTest.deviceEntryParentViewAlpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(0.1f),
+ step(0.2f),
+ step(0.3f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ values.forEach { assertThat(it).isEqualTo(1f) }
+ }
+
+ @Test
+ fun deviceEntryBackgroundViewDisappear() =
+ testScope.runTest {
+ val values by collectValues(underTest.deviceEntryBackgroundViewAlpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(0.1f),
+ step(0.2f),
+ step(0.3f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ assertThat(values.size).isEqualTo(6)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+ }
+
+ private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = AOD,
+ value = value,
+ transitionState = state,
+ ownerName = "AlternateBouncerToAodTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
new file mode 100644
index 0000000..3f7e0df
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlternateBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
+ val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply {
+ set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ }
+ }
+ private val testScope = kosmos.testScope
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val underTest = kosmos.alternateBouncerToGoneTransitionViewModel
+
+ @Test
+ fun deviceEntryParentViewDisappear() =
+ testScope.runTest {
+ val values by collectValues(underTest.deviceEntryParentViewAlpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(0.1f),
+ step(0.2f),
+ step(0.3f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ values.forEach { assertThat(it).isEqualTo(0f) }
+ }
+
+ private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = GONE,
+ value = value,
+ transitionState = state,
+ ownerName = "AlternateBouncerToGoneTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
index cf076c5..a84b9fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -24,16 +24,19 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
import com.android.systemui.qs.tiles.impl.custom.commons.copy
+import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -44,194 +47,260 @@
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileRepositoryTest : SysuiTestCase() {
- private val testScope = TestScope()
-
- private val persister = FakeCustomTileStatePersister()
-
+ private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
private val underTest: CustomTileRepository =
- CustomTileRepositoryImpl(
- TileSpec.create(TEST_COMPONENT),
- persister,
- testScope.testScheduler,
- )
+ with(kosmos) {
+ CustomTileRepositoryImpl(
+ tileSpec,
+ customTileStatePersister,
+ packageManagerAdapterFacade.packageManagerAdapter,
+ testScope.testScheduler,
+ )
+ }
@Test
fun persistableTileIsRestoredForUser() =
- testScope.runTest {
- persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
- persister.persistState(TEST_TILE_KEY_2, TEST_TILE_2)
+ with(kosmos) {
+ testScope.runTest {
+ customTileStatePersister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
+ customTileStatePersister.persistState(TEST_TILE_KEY_2, TEST_TILE_2)
- underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
- runCurrent()
+ underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
+ runCurrent()
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun notPersistableTileIsNotRestored() =
- testScope.runTest {
- persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
- val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ with(kosmos) {
+ testScope.runTest {
+ customTileStatePersister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
- underTest.restoreForTheUserIfNeeded(TEST_USER_1, false)
- runCurrent()
+ underTest.restoreForTheUserIfNeeded(TEST_USER_1, false)
+ runCurrent()
- assertThat(tiles()).isEmpty()
+ assertThat(tiles()).isEmpty()
+ }
}
@Test
fun emptyPersistedStateIsHandled() =
- testScope.runTest {
- val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ with(kosmos) {
+ testScope.runTest {
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
- underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
- runCurrent()
+ underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
+ runCurrent()
- assertThat(tiles()).isEmpty()
+ assertThat(tiles()).isEmpty()
+ }
}
@Test
fun updatingWithPersistableTilePersists() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1)
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1))
+ .isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun updatingWithNotPersistableTileDoesntPersist() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
+ }
}
@Test
fun updateWithTileEmits() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun updatingPeristableWithDefaultsPersists() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1)
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1))
+ .isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun updatingNotPersistableWithDefaultsDoesntPersist() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
+ }
}
@Test
fun updatingPeristableWithErrorDefaultsDoesntPersist() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
+ }
}
@Test
fun updateWithDefaultsEmits() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+ runCurrent()
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun getTileForAnotherUserReturnsNull() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- assertThat(underTest.getTile(TEST_USER_2)).isNull()
+ assertThat(underTest.getTile(TEST_USER_2)).isNull()
+ }
}
@Test
fun getTilesForAnotherUserEmpty() =
- testScope.runTest {
- val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+ with(kosmos) {
+ testScope.runTest {
+ val tiles = collectValues(underTest.getTiles(TEST_USER_2))
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- assertThat(tiles()).isEmpty()
+ assertThat(tiles()).isEmpty()
+ }
}
@Test
fun updatingWithTileForTheSameUserAddsData() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
- runCurrent()
+ underTest.updateWithTile(
+ TEST_USER_1,
+ Tile().apply { subtitle = "test_subtitle" },
+ true
+ )
+ runCurrent()
- val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+ val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+ }
}
@Test
fun updatingWithTileForAnotherUserOverridesTile() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- val tiles = collectValues(underTest.getTiles(TEST_USER_2))
- underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true)
- runCurrent()
+ val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+ underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true)
+ runCurrent()
- assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
- assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+ assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+ }
}
@Test
fun updatingWithDefaultsForTheSameUserAddsData() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(
+ TEST_USER_1,
+ Tile().apply { subtitle = "test_subtitle" },
+ true
+ )
+ runCurrent()
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
- runCurrent()
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+ runCurrent()
- val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+ val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+ }
}
@Test
fun updatingWithDefaultsForAnotherUserOverridesTile() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+ runCurrent()
- val tiles = collectValues(underTest.getTiles(TEST_USER_2))
- underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true)
- runCurrent()
+ val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+ underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true)
+ runCurrent()
- assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
- assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+ assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+ }
+ }
+
+ @Test
+ fun isActiveFollowsPackageManagerAdapter() =
+ with(kosmos) {
+ testScope.runTest {
+ packageManagerAdapterFacade.setIsActive(false)
+ assertThat(underTest.isTileActive()).isFalse()
+
+ packageManagerAdapterFacade.setIsActive(true)
+ assertThat(underTest.isTileActive()).isTrue()
+ }
+ }
+
+ @Test
+ fun isToggleableFollowsPackageManagerAdapter() =
+ with(kosmos) {
+ testScope.runTest {
+ packageManagerAdapterFacade.setIsToggleable(false)
+ assertThat(underTest.isTileToggleable()).isFalse()
+
+ packageManagerAdapterFacade.setIsToggleable(true)
+ assertThat(underTest.isTileToggleable()).isTrue()
+ }
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index eebb145..90779cb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -25,157 +25,159 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.external.TileServiceKey
-import com.android.systemui.qs.external.TileServiceManager
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.customTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
-import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
-import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileInteractorTest : SysuiTestCase() {
- @Mock private lateinit var tileServiceManager: TileServiceManager
+ private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
- private val testScope = TestScope()
-
- private val defaultsRepository = FakeCustomTileDefaultsRepository()
- private val customTileStatePersister = FakeCustomTileStatePersister()
- private val customTileRepository =
- FakeCustomTileRepository(
- TEST_TILE_SPEC,
- customTileStatePersister,
- testScope.testScheduler,
- )
-
- private lateinit var underTest: CustomTileInteractor
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
-
- underTest =
+ private val underTest: CustomTileInteractor =
+ with(kosmos) {
CustomTileInteractor(
- TEST_USER,
- defaultsRepository,
+ customTileDefaultsRepository,
customTileRepository,
- tileServiceManager,
testScope.backgroundScope,
testScope.testScheduler,
)
- }
+ }
@Test
fun activeTileIsAvailableAfterRestored() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(true)
- customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
- )
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(true)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+ TEST_TILE,
+ )
- underTest.init()
+ underTest.initForUser(TEST_USER)
- assertThat(underTest.tile).isEqualTo(TEST_TILE)
- assertThat(underTest.tiles.first()).isEqualTo(TEST_TILE)
+ assertThat(underTest.getTile(TEST_USER)).isEqualTo(TEST_TILE)
+ assertThat(underTest.getTiles(TEST_USER).first()).isEqualTo(TEST_TILE)
+ }
}
@Test
fun notActiveTileIsAvailableAfterUpdated() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(false)
- customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
- )
- val tiles = collectValues(underTest.tiles)
- val initJob = launch { underTest.init() }
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+ TEST_TILE,
+ )
+ val tiles = collectValues(underTest.getTiles(TEST_USER))
+ val initJob = launch { underTest.initForUser(TEST_USER) }
- underTest.updateTile(TEST_TILE)
- runCurrent()
- initJob.join()
+ underTest.updateTile(TEST_TILE)
+ runCurrent()
+ initJob.join()
- assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ }
}
@Test
fun notActiveTileIsAvailableAfterDefaultsUpdated() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(false)
- customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
- )
- val tiles = collectValues(underTest.tiles)
- val initJob = launch { underTest.init() }
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+ TEST_TILE,
+ )
+ val tiles = collectValues(underTest.getTiles(TEST_USER))
+ val initJob = launch { underTest.initForUser(TEST_USER) }
- defaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
- defaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
- runCurrent()
- initJob.join()
+ customTileDefaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
+ customTileDefaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
+ runCurrent()
+ initJob.join()
- assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ }
}
@Test(expected = IllegalStateException::class)
- fun getTileBeforeInitThrows() = testScope.runTest { underTest.tile }
+ fun getTileBeforeInitThrows() =
+ with(kosmos) { testScope.runTest { underTest.getTile(TEST_USER) } }
@Test
fun initSuspendsForActiveTileNotRestoredAndNotUpdated() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(true)
- val tiles = collectValues(underTest.tiles)
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(true)
+ val tiles = collectValues(underTest.getTiles(TEST_USER))
- val initJob = backgroundScope.launch { underTest.init() }
- advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
+ val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
+ advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
- // Is still suspended
- assertThat(initJob.isActive).isTrue()
- assertThat(tiles()).isEmpty()
+ // Is still suspended
+ assertThat(initJob.isActive).isTrue()
+ assertThat(tiles()).isEmpty()
+ }
}
@Test
fun initSuspendedForNotActiveTileWithoutUpdates() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(false)
- customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
- )
- val tiles = collectValues(underTest.tiles)
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+ TEST_TILE,
+ )
+ val tiles = collectValues(underTest.getTiles(TEST_USER))
- val initJob = backgroundScope.launch { underTest.init() }
- advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
+ val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
+ advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
- // Is still suspended
- assertThat(initJob.isActive).isTrue()
- assertThat(tiles()).isEmpty()
+ // Is still suspended
+ assertThat(initJob.isActive).isTrue()
+ assertThat(tiles()).isEmpty()
+ }
}
+ @Test
+ fun toggleableFollowsTheRepository() {
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileToggleable(false)
+ assertThat(underTest.isTileToggleable()).isFalse()
+
+ customTileRepository.setTileToggleable(true)
+ assertThat(underTest.isTileToggleable()).isTrue()
+ }
+ }
+ }
+
private companion object {
val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
- val TEST_TILE_SPEC = TileSpec.create(TEST_COMPONENT)
val TEST_USER = UserHandle.of(1)!!
val TEST_TILE =
Tile().apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt
new file mode 100644
index 0000000..f67c70c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification
+
+import android.app.Notification
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationContentDescriptionTest : SysuiTestCase() {
+
+ private val TITLE = "this is a title"
+ private val TEXT = "this is text"
+ private val TICKER = "this is a ticker"
+
+ @Test
+ fun notificationWithAllDifferentFields_descriptionIsTitle() {
+ val n = createNotification(TITLE, TEXT, TICKER)
+ val description = contentDescForNotification(context, n)
+ assertThat(description).isEqualTo(createDescriptionText(n, TITLE))
+ }
+
+ @Test
+ fun notificationWithAllDifferentFields_titleMatchesAppName_descriptionIsText() {
+ val n = createNotification(getTestAppName(), TEXT, TICKER)
+ val description = contentDescForNotification(context, n)
+ assertThat(description).isEqualTo(createDescriptionText(n, TEXT))
+ }
+
+ @Test
+ fun notificationWithAllDifferentFields_titleMatchesAppNameNoText_descriptionIsTicker() {
+ val n = createNotification(getTestAppName(), null, TICKER)
+ val description = contentDescForNotification(context, n)
+ assertThat(description).isEqualTo(createDescriptionText(n, TICKER))
+ }
+
+ @Test
+ fun notificationWithAllDifferentFields_titleMatchesAppNameNoTextNoTicker_descriptionEmpty() {
+ val appName = getTestAppName()
+ val n = createNotification(appName, null, null)
+ val description = contentDescForNotification(context, n)
+ assertThat(description).isEqualTo(createDescriptionText(n, ""))
+ }
+
+ @Test
+ fun nullNotification_descriptionIsAppName() {
+ val description = contentDescForNotification(context, null)
+ assertThat(description).isEqualTo(createDescriptionText(null, ""))
+ }
+
+ private fun createNotification(
+ title: String? = null,
+ text: String? = null,
+ ticker: String? = null
+ ): Notification =
+ Notification.Builder(context)
+ .setContentTitle(title)
+ .setContentText(text)
+ .setTicker(ticker)
+ .build()
+
+ private fun getTestAppName(): String {
+ return getAppName(createNotification("", "", ""))
+ }
+
+ private fun getAppName(n: Notification?) =
+ n?.let {
+ val builder = Notification.Builder.recoverBuilder(context, it)
+ builder.loadHeaderAppName()
+ }
+ ?: ""
+
+ private fun createDescriptionText(n: Notification?, desc: String?): String {
+ val appName = getAppName(n)
+ return context.getString(R.string.accessibility_desc_notification_icon, appName, desc)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 791a028..1237347 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -16,9 +16,12 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
+import com.android.systemui.Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.coroutines.collectLastValue
@@ -183,7 +186,8 @@
}
@Test
- fun activity_nullSsid_outputsFalse() =
+ @DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
+ fun activity_nullSsid_outputsFalse_staticFlagOff() =
testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
@@ -207,6 +211,33 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
+ fun activity_nullSsid_outputsFalse_staticFlagOn() =
+ testScope.runTest {
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(NETWORK_ID, ssid = null, level = 1)
+ )
+
+ val activityIn by collectLastValue(underTest.isActivityInViewVisible)
+ val activityOut by collectLastValue(underTest.isActivityOutViewVisible)
+ val activityContainer by collectLastValue(underTest.isActivityContainerVisible)
+
+ // WHEN we update the repo to have activity
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+
+ // THEN we still output false because our network's SSID is null
+ assertThat(activityIn).isFalse()
+ assertThat(activityOut).isFalse()
+
+ // THEN the inout indicators are sill showing due to the config being true
+ assertThat(activityContainer).isTrue()
+ }
+
+ @Test
fun activity_allLocationViewModelsReceiveSameData() =
testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
@@ -335,7 +366,8 @@
}
@Test
- fun activityContainer_inAndOutFalse_outputsFalse() =
+ @DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
+ fun activityContainer_inAndOutFalse_outputsTrue_staticFlagOff() =
testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
@@ -350,6 +382,24 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
+ fun activityContainer_inAndOutFalse_outputsTrue_staticFlagOn() =
+ testScope.runTest {
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+ val latest by collectLastValue(underTest.isActivityContainerVisible)
+
+ val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity)
+
+ // The activity container should always be visible, since activity is
+ // shown in UI by changing opacity of the indicators.
+ assertThat(latest).isTrue()
+ }
+
+ @Test
fun airplaneSpacer_notAirplaneMode_outputsFalse() =
testScope.runTest {
val latest by collectLastValue(underTest.isAirplaneSpacerVisible)
diff --git a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
index 7b90270..50d5607 100644
--- a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
+++ b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
@@ -15,7 +15,7 @@
~
-->
-<FrameLayout
+<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:id="@+id/alternate_bouncer"
@@ -32,4 +32,4 @@
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
/>
-</FrameLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
index a8017f6..25cadfb 100644
--- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -39,7 +39,6 @@
android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:src="@drawable/ic_activity_down"
- android:visibility="gone"
android:paddingEnd="2dp"
/>
<ImageView
@@ -49,7 +48,6 @@
android:layout_width="wrap_content"
android:src="@drawable/ic_activity_up"
android:paddingEnd="2dp"
- android:visibility="gone"
/>
</FrameLayout>
<FrameLayout
diff --git a/packages/SystemUI/res/drawable/ic_memory.xml b/packages/SystemUI/res/drawable/ic_memory.xml
deleted file mode 100644
index ada36c5..0000000
--- a/packages/SystemUI/res/drawable/ic_memory.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M16.0,5.0l-8.0,0.0l0.0,14.0l8.0,0.0z"
- android:fillAlpha="0.5"
- android:fillColor="#000000"/>
- <path
- android:pathData="M6,9 L6,7 L4,7 L4,5 L6,5 C6,3.9 6.9,3 8,3 L16,3 C17.1,3 18,3.9 18,5 L20,5 L20,7 L18,7 L18,9 L20,9 L20,11 L18,11 L18,13 L20,13 L20,15 L18,15 L18,17 L20,17 L20,19 L18,19 C18,20.1 17.1,21 16,21 L8,21 C6.9,21 6,20.1 6,19 L4,19 L4,17 L6,17 L6,15 L4,15 L4,13 L6,13 L6,11 L4,11 L4,9 L6,9 Z M16,19 L16,5 L8,5 L8,19 L16,19 Z"
- android:fillColor="#000000"/>
-</vector>
diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
index 4c5cd7d..e8b8922 100644
--- a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
+++ b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
@@ -37,7 +37,6 @@
android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:src="@drawable/ic_activity_down"
- android:visibility="gone"
android:paddingEnd="2dp"
/>
<ImageView
@@ -47,7 +46,6 @@
android:layout_width="wrap_content"
android:src="@drawable/ic_activity_up"
android:paddingEnd="2dp"
- android:visibility="gone"
/>
</FrameLayout>
<FrameLayout
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 87c31c8..dca84b9 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -116,6 +116,10 @@
android:inflatedId="@+id/multi_shade"
android:layout="@layout/multi_shade" />
+ <include layout="@layout/alternate_bouncer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<com.android.systemui.biometrics.AuthRippleView
android:id="@+id/auth_ripple"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index deed6c8..e01a2aa 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -480,9 +480,6 @@
This name is in the ComponentName flattened format (package/class) -->
<string name="config_remoteCopyPackage" translatable="false"></string>
- <!-- On debuggable builds, alert the user if SystemUI PSS goes over this number (in kb) -->
- <integer name="watch_heap_limit">256000</integer>
-
<!-- SystemUI Plugins that can be loaded on user builds. -->
<string-array name="config_pluginAllowlist" translatable="false">
<item>com.android.systemui</item>
@@ -966,14 +963,6 @@
<bool name="config_edgeToEdgeBottomSheetDialog">true</bool>
<!--
- Whether the scene container framework is enabled.
-
- The scene container framework is a newer (2023) way to organize the various "scenes" between the
- bouncer, lockscreen, shade, and quick settings.
- -->
- <bool name="config_sceneContainerFrameworkEnabled">true</bool>
-
- <!--
Time in milliseconds the user has to touch the side FPS sensor to successfully authenticate
TODO(b/302332976) Get this value from the HAL if they can provide an API for it.
-->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b947801..ee89ede 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -274,6 +274,10 @@
<!-- Side padding on the side of notifications -->
<dimen name="notification_side_paddings">16dp</dimen>
+ <!-- Starting translateY offset of the HUN appear and disappear animations. Indicates
+ the amount by the view is positioned above the screen before the animation starts. -->
+ <dimen name="heads_up_appear_y_above_screen">32dp</dimen>
+
<!-- padding between the heads up and the statusbar -->
<dimen name="heads_up_status_bar_padding">8dp</dimen>
@@ -1905,4 +1909,7 @@
<!-- Bouncer user switcher margins -->
<dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
<dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
+
+ <!-- UDFPS view attributes -->
+ <dimen name="udfps_icon_size">6mm</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index cf63cc7..d511cab 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -259,4 +259,11 @@
<item type="id" name="device_entry_icon_view" />
<item type="id" name="device_entry_icon_fg" />
<item type="id" name="device_entry_icon_bg" />
+
+ <!--Id for the device-entry UDFPS icon that lives in the alternate bouncer. -->
+ <item type="id" name="alternate_bouncer_udfps_icon_view" />
+
+ <!-- Id for the udfps accessibility overlay -->
+ <item type="id" name="udfps_accessibility_overlay" />
+ <item type="id" name="udfps_accessibility_overlay_top_guideline" />
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e10925d..f4b25a7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2397,10 +2397,6 @@
<!-- URl of the webpage that explains battery saver. -->
<string name="help_uri_battery_saver_learn_more_link_target" translatable="false"></string>
- <!-- Name for a quick settings tile, used only by platform developers, to extract the SystemUI process memory and send it to another
- app for debugging. Will not be seen by users. [CHAR LIMIT=20] -->
- <string name="heap_dump_tile_name">Dump SysUI Heap</string>
-
<!-- Title for the privacy indicators dialog, only appears as part of a11y descriptions [CHAR LIMIT=NONE] -->
<string name="ongoing_privacy_dialog_a11y_title">In use</string>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
index 8ad32b4..a00cdfa 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
@@ -30,6 +30,10 @@
fun isUdfps(): Boolean {
return (this == UDFPS_OPTICAL) || (this == UDFPS_ULTRASONIC)
}
+
+ fun isPowerButton(): Boolean {
+ return this == POWER_BUTTON
+ }
}
/** Convert [this] to corresponding [FingerprintSensorType] */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index cdd7b80..74b975c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -39,7 +39,6 @@
import androidx.annotation.VisibleForTesting;
import com.android.systemui.Dumpable;
-import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
@@ -48,9 +47,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
-import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.log.dagger.KeyguardClockLog;
@@ -62,17 +59,11 @@
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerAlwaysOnDisplayViewBinder;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.ui.SystemBarUtilsState;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.settings.SecureSettings;
@@ -102,14 +93,7 @@
private final DumpManager mDumpManager;
private final ClockEventController mClockEventController;
private final LogBuffer mLogBuffer;
- private final NotificationIconContainerAlwaysOnDisplayViewModel mAodIconsViewModel;
- private final KeyguardRootViewModel mKeyguardRootViewModel;
- private final ConfigurationState mConfigurationState;
- private final SystemBarUtilsState mSystemBarUtilsState;
- private final DozeParameters mDozeParameters;
- private final ScreenOffAnimationController mScreenOffAnimationController;
- private final AlwaysOnDisplayNotificationIconViewStore mAodIconViewStore;
- private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker;
+ private final NotificationIconContainerAlwaysOnDisplayViewBinder mNicViewBinder;
private FrameLayout mSmallClockFrame; // top aligned clock
private FrameLayout mLargeClockFrame; // centered clock
@@ -183,9 +167,7 @@
KeyguardSliceViewController keyguardSliceViewController,
NotificationIconAreaController notificationIconAreaController,
LockscreenSmartspaceController smartspaceController,
- SystemBarUtilsState systemBarUtilsState,
- ScreenOffAnimationController screenOffAnimationController,
- StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker,
+ NotificationIconContainerAlwaysOnDisplayViewBinder nicViewBinder,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
SecureSettings secureSettings,
@Main DelayableExecutor uiExecutor,
@@ -193,11 +175,6 @@
DumpManager dumpManager,
ClockEventController clockEventController,
@KeyguardClockLog LogBuffer logBuffer,
- NotificationIconContainerAlwaysOnDisplayViewModel aodIconsViewModel,
- KeyguardRootViewModel keyguardRootViewModel,
- ConfigurationState configurationState,
- DozeParameters dozeParameters,
- AlwaysOnDisplayNotificationIconViewStore aodIconViewStore,
KeyguardInteractor keyguardInteractor,
KeyguardClockInteractor keyguardClockInteractor,
FeatureFlagsClassic featureFlags,
@@ -208,9 +185,7 @@
mKeyguardSliceViewController = keyguardSliceViewController;
mNotificationIconAreaController = notificationIconAreaController;
mSmartspaceController = smartspaceController;
- mSystemBarUtilsState = systemBarUtilsState;
- mScreenOffAnimationController = screenOffAnimationController;
- mIconViewBindingFailureTracker = iconViewBindingFailureTracker;
+ mNicViewBinder = nicViewBinder;
mSecureSettings = secureSettings;
mUiExecutor = uiExecutor;
mBgExecutor = bgExecutor;
@@ -218,11 +193,6 @@
mDumpManager = dumpManager;
mClockEventController = clockEventController;
mLogBuffer = logBuffer;
- mAodIconsViewModel = aodIconsViewModel;
- mKeyguardRootViewModel = keyguardRootViewModel;
- mConfigurationState = configurationState;
- mDozeParameters = dozeParameters;
- mAodIconViewStore = aodIconViewStore;
mView.setLogBuffer(mLogBuffer);
mFeatureFlags = featureFlags;
mKeyguardInteractor = keyguardInteractor;
@@ -619,28 +589,7 @@
mAodIconsBindHandle.dispose();
}
if (nic != null) {
- final DisposableHandle viewHandle =
- NotificationIconContainerViewBinder.bindWhileAttached(
- nic,
- mAodIconsViewModel,
- mConfigurationState,
- mSystemBarUtilsState,
- mIconViewBindingFailureTracker,
- mAodIconViewStore);
- final DisposableHandle visHandle = KeyguardRootViewBinder.bindAodIconVisibility(
- nic,
- mKeyguardRootViewModel.isNotifIconContainerVisible(),
- mConfigurationState,
- mFeatureFlags,
- mScreenOffAnimationController);
- if (visHandle == null) {
- mAodIconsBindHandle = viewHandle;
- } else {
- mAodIconsBindHandle = () -> {
- viewHandle.dispose();
- visHandle.dispose();
- };
- }
+ mAodIconsBindHandle = mNicViewBinder.bindWhileAttached(nic);
mAodIconContainer = nic;
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 65668b5..240728a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -86,8 +86,6 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter;
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -115,7 +113,6 @@
import java.util.concurrent.Executor;
import javax.inject.Inject;
-import javax.inject.Provider;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
@@ -152,7 +149,6 @@
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
- @NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
@NonNull private final VibratorHelper mVibrator;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@@ -623,7 +619,7 @@
} else {
onKeyguard = mOverlay != null
&& mOverlay.getAnimationViewController()
- instanceof UdfpsKeyguardViewControllerAdapter;
+ instanceof UdfpsKeyguardViewControllerLegacy;
}
return onKeyguard
&& mKeyguardStateController.canDismissLockScreen()
@@ -666,7 +662,6 @@
@NonNull InputManager inputManager,
@NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
@NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
- @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider,
@NonNull SelectedUserInteractor selectedUserInteractor,
@NonNull FpsUnlockTracker fpsUnlockTracker,
@NonNull KeyguardTransitionInteractor keyguardTransitionInteractor,
@@ -737,7 +732,6 @@
return Unit.INSTANCE;
});
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
- mUdfpsKeyguardViewModels = udfpsKeyguardViewModelsProvider;
final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index dae6d08..aabee93 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -55,7 +55,6 @@
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -438,7 +437,7 @@
if (DeviceEntryUdfpsRefactor.isEnabled) {
!keyguardStateController.isShowing
} else {
- animation !is UdfpsKeyguardViewControllerAdapter
+ animation !is UdfpsKeyguardViewControllerLegacy
}
if (keyguardNotShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 63fe26a..64148f6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -33,7 +33,6 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
@@ -81,8 +80,7 @@
primaryBouncerInteractor,
systemUIDialogManager,
dumpManager,
- ),
- UdfpsKeyguardViewControllerAdapter {
+ ) {
private val uniqueIdentifier = this.toString()
private var showingUdfpsBouncer = false
private var udfpsRequested = false
@@ -199,11 +197,27 @@
listenForAodToOccludedTransitions(this)
listenForAlternateBouncerToAodTransitions(this)
listenForDreamingToAodTransitions(this)
+ listenForPrimaryBouncerToAodTransitions(this)
}
}
}
@VisibleForTesting
+ suspend fun listenForPrimaryBouncerToAodTransitions(scope: CoroutineScope): Job {
+ return scope.launch {
+ transitionInteractor
+ .transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.AOD)
+ .collect { transitionStep ->
+ view.onDozeAmountChanged(
+ transitionStep.value,
+ transitionStep.value,
+ ANIMATE_APPEAR_ON_SCREEN_OFF,
+ )
+ }
+ }
+ }
+
+ @VisibleForTesting
suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job {
return scope.launch {
transitionInteractor.transition(KeyguardState.DREAMING, KeyguardState.AOD).collect {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 38043b4..f4a2811 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics.domain.interactor
+import android.content.Context
import android.view.MotionEvent
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
@@ -23,12 +24,15 @@
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Encapsulates business logic for interacting with the UDFPS overlay. */
@@ -36,10 +40,12 @@
class UdfpsOverlayInteractor
@Inject
constructor(
+ @Application context: Context,
private val authController: AuthController,
private val selectedUserInteractor: SelectedUserInteractor,
@Application scope: CoroutineScope
) {
+ private val iconSize: Int = context.resources.getDimensionPixelSize(R.dimen.udfps_icon_size)
/** Whether a touch is within the under-display fingerprint sensor area */
fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean {
@@ -70,6 +76,14 @@
}
.stateIn(scope, started = SharingStarted.Eagerly, initialValue = UdfpsOverlayParams())
+ // Padding between the fingerprint icon and its bounding box in pixels.
+ val iconPadding: Flow<Int> =
+ udfpsOverlayParams.map { params ->
+ val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left
+ val nativePadding = (sensorWidth - iconSize) / 2
+ (nativePadding * params.scaleFactor).toInt()
+ }
+
companion object {
private const val TAG = "UdfpsOverlayInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
deleted file mode 100644
index 6f4e1a3..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2023 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.biometrics.ui.controller
-
-import com.android.systemui.biometrics.UdfpsAnimationViewController
-import com.android.systemui.biometrics.UdfpsKeyguardView
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-/** Class that coordinates non-HBM animations during keyguard authentication. */
-@ExperimentalCoroutinesApi
-open class UdfpsKeyguardViewController(
- val view: UdfpsKeyguardView,
- statusBarStateController: StatusBarStateController,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
- systemUIDialogManager: SystemUIDialogManager,
- dumpManager: DumpManager,
- private val alternateBouncerInteractor: AlternateBouncerInteractor,
- udfpsKeyguardViewModels: UdfpsKeyguardViewModels,
-) :
- UdfpsAnimationViewController<UdfpsKeyguardView>(
- view,
- statusBarStateController,
- primaryBouncerInteractor,
- systemUIDialogManager,
- dumpManager,
- ),
- UdfpsKeyguardViewControllerAdapter {
- private val uniqueIdentifier = this.toString()
- override val tag: String
- get() = TAG
-
- init {
- udfpsKeyguardViewModels.bindViews(view)
- }
-
- public override fun onViewAttached() {
- super.onViewAttached()
- alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, uniqueIdentifier)
- }
-
- public override fun onViewDetached() {
- super.onViewDetached()
- alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier)
- }
-
- override fun shouldPauseAuth(): Boolean {
- return !view.isVisible()
- }
-
- override fun listenForTouchesOutsideView(): Boolean {
- return true
- }
-
- companion object {
- private const val TAG = "UdfpsKeyguardViewController"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
index c19ea19..7106674 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics.ui.viewmodel
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.hideAffordancesRequest
@@ -26,22 +27,30 @@
/**
* View model for the UdfpsTouchOverlay for when UDFPS is being requested for device entry. Handles
- * touches as long as the device entry views are visible.
+ * touches as long as the device entry view is visible (the lockscreen or the alternate bouncer
+ * view).
*/
@ExperimentalCoroutinesApi
class DeviceEntryUdfpsTouchOverlayViewModel
@Inject
constructor(
deviceEntryIconViewModel: DeviceEntryIconViewModel,
+ alternateBouncerInteractor: AlternateBouncerInteractor,
systemUIDialogManager: SystemUIDialogManager,
) : UdfpsTouchOverlayViewModel {
- // TODO (b/305234447): AlternateBouncer showing overrides sysuiDialogHideAffordancesRequest
- override val shouldHandleTouches: Flow<Boolean> =
+ private val showingUdfpsAffordance: Flow<Boolean> =
combine(
deviceEntryIconViewModel.deviceEntryViewAlpha,
+ alternateBouncerInteractor.isVisible,
+ ) { deviceEntryViewAlpha, alternateBouncerVisible ->
+ deviceEntryViewAlpha > ALLOW_TOUCH_ALPHA_THRESHOLD || alternateBouncerVisible
+ }
+ override val shouldHandleTouches: Flow<Boolean> =
+ combine(
+ showingUdfpsAffordance,
systemUIDialogManager.hideAffordancesRequest,
- ) { deviceEntryViewAlpha, dialogRequestingHideAffordances ->
- deviceEntryViewAlpha > ALLOW_TOUCH_ALPHA_THRESHOLD && !dialogRequestingHideAffordances
+ ) { showingUdfpsAffordance, dialogRequestingHideAffordances ->
+ showingUdfpsAffordance && !dialogRequestingHideAffordances
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 84f7dd5..af32eb5 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -29,9 +29,10 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */
@SysUISingleton
@@ -52,19 +53,13 @@
private val alternateBouncerUiAvailableFromSource: HashSet<String> = HashSet()
private val alternateBouncerSupported: StateFlow<Boolean> =
if (DeviceEntryUdfpsRefactor.isEnabled) {
- // The device entry udfps refactor doesn't currently support the alternate bouncer.
- // TODO: Re-enable when b/287599719 is ready.
- MutableStateFlow(false).asStateFlow()
- // fingerprintPropertyRepository.sensorType
- // .map { sensorType ->
- // sensorType.isUdfps() || sensorType ==
- // FingerprintSensorType.POWER_BUTTON
- // }
- // .stateIn(
- // scope = scope,
- // started = SharingStarted.Eagerly,
- // initialValue = false,
- // )
+ fingerprintPropertyRepository.sensorType
+ .map { sensorType -> sensorType.isUdfps() || sensorType.isPowerButton() }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
} else {
bouncerRepository.alternateBouncerUIAvailable
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt
deleted file mode 100644
index 7be2eaf..0000000
--- a/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 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.common.domain
-
-import com.android.systemui.common.domain.interactor.ConfigurationInteractor
-import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl
-import dagger.Binds
-import dagger.Module
-
-@Module
-abstract class CommonDomainLayerModule {
- @Binds abstract fun bindInteractor(impl: ConfigurationInteractorImpl): ConfigurationInteractor
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/ConfigurationInteractor.kt
deleted file mode 100644
index 89053d1..0000000
--- a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/ConfigurationInteractor.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2023 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.common.domain.interactor
-
-import android.content.res.Configuration
-import android.graphics.Rect
-import android.view.Surface
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-
-interface ConfigurationInteractor {
- /**
- * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
- * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
- * foldable devices)
- */
- val naturalMaxBounds: Flow<Rect>
-}
-
-class ConfigurationInteractorImpl
-@Inject
-constructor(private val repository: ConfigurationRepository) : ConfigurationInteractor {
-
- override val naturalMaxBounds: Flow<Rect>
- get() = repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged()
-
- /**
- * Returns screen size adjusted to rotation, so returned screen size is stable across all
- * rotations
- */
- private val Configuration.naturalScreenBounds: Rect
- get() {
- val rotation = windowConfiguration.displayRotation
- val maxBounds = windowConfiguration.maxBounds
- return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- Rect(0, 0, maxBounds.width(), maxBounds.height())
- } else {
- Rect(0, 0, maxBounds.height(), maxBounds.width())
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index 3648f3b..1353985 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -17,17 +17,45 @@
package com.android.systemui.common.ui.domain.interactor
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.view.Surface
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onStart
/** Business logic related to configuration changes. */
@SysUISingleton
class ConfigurationInteractor @Inject constructor(private val repository: ConfigurationRepository) {
+ /**
+ * Returns screen size adjusted to rotation, so returned screen size is stable across all
+ * rotations
+ */
+ private val Configuration.naturalScreenBounds: Rect
+ get() {
+ val rotation = windowConfiguration.displayRotation
+ val maxBounds = windowConfiguration.maxBounds
+ return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
+ Rect(0, 0, maxBounds.width(), maxBounds.height())
+ } else {
+ Rect(0, 0, maxBounds.height(), maxBounds.width())
+ }
+ }
+
+ /**
+ * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
+ * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
+ * foldable devices)
+ */
+ val naturalMaxBounds: Flow<Rect> =
+ repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged()
+
/** Given [resourceId], emit the dimension pixel size on config change */
fun dimensionPixelSize(resourceId: Int): Flow<Int> {
return onAnyConfigurationChange.mapLatest { repository.getDimensionPixelSize(resourceId) }
@@ -43,4 +71,7 @@
/** Emit an event on any config change */
val onAnyConfigurationChange: Flow<Unit> =
repository.onAnyConfigurationChange.onStart { emit(Unit) }
+
+ /** Emits the current resolution scaling factor */
+ val scaleForResolution: Flow<Float> = repository.scaleForResolution
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index ca8268d..a25c788 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -43,7 +43,6 @@
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
import com.android.systemui.common.data.CommonDataLayerModule;
-import com.android.systemui.common.domain.CommonDomainLayerModule;
import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.controls.dagger.ControlsModule;
@@ -179,7 +178,6 @@
ClockRegistryModule.class,
CommunalModule.class,
CommonDataLayerModule.class,
- CommonDomainLayerModule.class,
ConnectivityModule.class,
ControlsModule.class,
CoroutinesModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt
new file mode 100644
index 0000000..ef2537c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 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.deviceentry.ui.binder
+
+import android.annotation.SuppressLint
+import androidx.core.view.isInvisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
+import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+object UdfpsAccessibilityOverlayBinder {
+
+ /** Forwards hover events to the view model to make guided announcements for accessibility. */
+ @SuppressLint("ClickableViewAccessibility")
+ @JvmStatic
+ fun bind(
+ view: UdfpsAccessibilityOverlay,
+ viewModel: UdfpsAccessibilityOverlayViewModel,
+ ) {
+ view.setOnHoverListener { v, event -> viewModel.onHoverEvent(v, event) }
+ view.repeatWhenAttached {
+ // Repeat on CREATED because we update the visibility of the view
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ viewModel.visible.collect { visible -> view.isInvisible = !visible }
+ }
+ }
+ }
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt
similarity index 71%
copy from core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
copy to packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt
index 6c1f0fc..7be3230 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package android.companion.virtual.camera;
+package com.android.systemui.deviceentry.ui.view
-/**
- * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with
- * VirtualCamera.
- * @hide
- */
-parcelable VirtualCameraMetadata;
+import android.content.Context
+import android.view.View
+
+/** Overlay to handle under-fingerprint sensor accessibility events. */
+class UdfpsAccessibilityOverlay(context: Context?) : View(context)
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt
new file mode 100644
index 0000000..80684b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 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.deviceentry.ui.viewmodel
+
+import android.graphics.Point
+import android.view.MotionEvent
+import android.view.View
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
+import com.android.systemui.biometrics.UdfpsUtils
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+
+/** Models the UI state for the UDFPS accessibility overlay */
+@ExperimentalCoroutinesApi
+class UdfpsAccessibilityOverlayViewModel
+@Inject
+constructor(
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
+ accessibilityInteractor: AccessibilityInteractor,
+ deviceEntryIconViewModel: DeviceEntryIconViewModel,
+ deviceEntryFgIconViewModel: DeviceEntryForegroundViewModel,
+) {
+ private val udfpsUtils = UdfpsUtils()
+ private val udfpsOverlayParams: StateFlow<UdfpsOverlayParams> =
+ udfpsOverlayInteractor.udfpsOverlayParams
+
+ /** Overlay is only visible if touch exploration is enabled and UDFPS can be used. */
+ val visible: Flow<Boolean> =
+ accessibilityInteractor.isTouchExplorationEnabled.flatMapLatest { touchExplorationEnabled ->
+ if (touchExplorationEnabled) {
+ combine(
+ deviceEntryFgIconViewModel.viewModel,
+ deviceEntryIconViewModel.deviceEntryViewAlpha,
+ ) { iconViewModel, alpha ->
+ iconViewModel.type == DeviceEntryIconView.IconType.FINGERPRINT &&
+ !iconViewModel.useAodVariant &&
+ alpha == 1f
+ }
+ } else {
+ flowOf(false)
+ }
+ }
+
+ /** Give directional feedback to help the user authenticate with UDFPS. */
+ fun onHoverEvent(v: View, event: MotionEvent): Boolean {
+ val overlayParams = udfpsOverlayParams.value
+ val scaledTouch: Point =
+ udfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0), event, overlayParams)
+
+ if (!udfpsUtils.isWithinSensorArea(event.getPointerId(0), event, overlayParams)) {
+ // view only receives motionEvents when [visible] which requires touchExplorationEnabled
+ val announceStr =
+ udfpsUtils.onTouchOutsideOfSensorArea(
+ /* touchExplorationEnabled */ true,
+ v.context,
+ scaledTouch.x,
+ scaledTouch.y,
+ overlayParams,
+ )
+ if (announceStr != null) {
+ v.announceForAccessibility(announceStr)
+ }
+ }
+ // always let the motion events go through to underlying views
+ return false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 5ec51f4..1540423 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -25,7 +25,9 @@
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
@@ -36,20 +38,28 @@
class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, handler: Handler) :
FlagDependenciesBase(featureFlags, handler) {
override fun defineDependencies() {
+ // Internal notification backend dependencies
+ crossAppPoliteNotifications dependsOn politeNotifications
+ vibrateWhileUnlockedToken dependsOn politeNotifications
+
+ // Internal notification frontend dependencies
NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
- val keyguardBottomAreaRefactor = FlagToken(
- FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
+ // Internal keyguard dependencies
KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor
- val crossAppPoliteNotifToken =
- FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications())
- val politeNotifToken = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications())
- crossAppPoliteNotifToken dependsOn politeNotifToken
-
- val vibrateWhileUnlockedToken =
- FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
- vibrateWhileUnlockedToken dependsOn politeNotifToken
+ // SceneContainer dependencies
+ SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
+ SceneContainerFlag.getMainStaticFlag() dependsOn MIGRATE_KEYGUARD_STATUS_BAR_VIEW
}
+
+ private inline val politeNotifications
+ get() = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications())
+ private inline val crossAppPoliteNotifications
+ get() = FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications())
+ private inline val vibrateWhileUnlockedToken: FlagToken
+ get() = FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
+ private inline val keyguardBottomAreaRefactor
+ get() = FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
index e96660f..6560ee3 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
@@ -133,6 +133,7 @@
.setContentTitle(title)
.setContentText(details)
.setStyle(Notification.BigTextStyle().bigText(details))
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
.build()
notifManager.createNotificationChannel(channel)
notifManager.notify("flags", 0, notification)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b43f54d..f6db978 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -17,11 +17,11 @@
import android.provider.DeviceConfig
import com.android.internal.annotations.Keep
-import com.android.systemui.res.R
import com.android.systemui.flags.FlagsFactory.releasedFlag
import com.android.systemui.flags.FlagsFactory.resourceBooleanFlag
import com.android.systemui.flags.FlagsFactory.sysPropBooleanFlag
import com.android.systemui.flags.FlagsFactory.unreleasedFlag
+import com.android.systemui.res.R
/**
* List of [Flag] objects for use in SystemUI.
@@ -483,9 +483,6 @@
// TODO(b/264916608): Tracking Bug
@JvmField val SCREENSHOT_METADATA = unreleasedFlag("screenshot_metadata")
- // TODO(b/266955521): Tracking bug
- @JvmField val SCREENSHOT_DETECTION = releasedFlag("screenshot_detection")
-
// TODO(b/251205791): Tracking Bug
@JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips")
@@ -607,9 +604,6 @@
@JvmField
val LOCKSCREEN_WALLPAPER_DREAM_ENABLED = unreleasedFlag("enable_lockscreen_wallpaper_dream")
- // TODO(b/283084712): Tracking Bug
- @JvmField val IMPROVED_HUN_ANIMATIONS = unreleasedFlag("improved_hun_animations")
-
// TODO(b/283447257): Tracking bug
@JvmField
val BIGPICTURE_NOTIFICATION_LAZY_LOADING =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 20da00e..af5d48d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -124,7 +124,7 @@
indicationAreaHandle =
KeyguardIndicationAreaBinder.bind(
- notificationShadeWindowView,
+ notificationShadeWindowView.requireViewById(R.id.keyguard_indication_area),
keyguardIndicationAreaViewModel,
keyguardRootViewModel,
indicationController,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index de15fd6..c98f637 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -22,6 +22,7 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.res.R
import javax.inject.Inject
@@ -48,7 +49,9 @@
) {
init {
- alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+ if (!DeviceEntryUdfpsRefactor.isEnabled) {
+ alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+ }
}
private val showIndicatorForPrimaryBouncer: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 7e360cf..52f2759 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -141,5 +141,6 @@
companion object {
val TRANSITION_DURATION_MS = 300.milliseconds
val TO_GONE_DURATION = 500.milliseconds
+ val TO_AOD_DURATION = TRANSITION_DURATION_MS
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt
deleted file mode 100644
index ebf1beb..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2023 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.keyguard.ui.adapter
-
-/**
- * Temporary adapter class while
- * [com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController] is being refactored
- * before [com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy] is removed.
- *
- * TODO (b/278719514): Delete once udfps keyguard view is fully refactored.
- */
-interface UdfpsKeyguardViewControllerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
new file mode 100644
index 0000000..c749818
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.binder
+
+import android.content.res.ColorStateList
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object AlternateBouncerUdfpsViewBinder {
+
+ /** Updates UI for the UDFPS icon on the alternate bouncer. */
+ @JvmStatic
+ fun bind(
+ view: DeviceEntryIconView,
+ viewModel: AlternateBouncerUdfpsIconViewModel,
+ ) {
+ if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) {
+ return
+ }
+ val fgIconView = view.iconView
+ val bgView = view.bgView
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.accessibilityDelegateHint.collect { hint ->
+ view.accessibilityHintType = hint
+ }
+ }
+ }
+
+ fgIconView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.fgViewModel.collect { fgViewModel ->
+ fgIconView.setImageState(
+ view.getIconState(fgViewModel.type, fgViewModel.useAodVariant),
+ /* merge */ false
+ )
+ fgIconView.imageTintList = ColorStateList.valueOf(fgViewModel.tint)
+ fgIconView.setPadding(
+ fgViewModel.padding,
+ fgViewModel.padding,
+ fgViewModel.padding,
+ fgViewModel.padding,
+ )
+ }
+ }
+ }
+
+ bgView.visibility = View.VISIBLE
+ bgView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.bgColor.collect { color ->
+ bgView.imageTintList = ColorStateList.valueOf(color)
+ }
+ }
+ launch { viewModel.bgAlpha.collect { alpha -> bgView.alpha = alpha } }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index d1d323f..78906ac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -17,47 +17,49 @@
package com.android.systemui.keyguard.ui.binder
import android.view.View
-import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.classifier.Classifier
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.scrim.ScrimView
-import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.gesture.TapGestureDetector
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
-/** Binds the alternate bouncer view to its view-model. */
+/**
+ * Binds the alternate bouncer view to its view-model.
+ *
+ * For devices that support UDFPS, this includes a UDFPS view.
+ */
@ExperimentalCoroutinesApi
object AlternateBouncerViewBinder {
/** Binds the view to the view-model, continuing to update the former based on the latter. */
@JvmStatic
fun bind(
- view: ViewGroup,
+ view: ConstraintLayout,
viewModel: AlternateBouncerViewModel,
- scope: CoroutineScope,
- notificationShadeWindowController: NotificationShadeWindowController,
falsingManager: FalsingManager,
swipeUpAnywhereGestureHandler: SwipeUpAnywhereGestureHandler,
tapGestureDetector: TapGestureDetector,
+ alternateBouncerUdfpsIconViewModel: AlternateBouncerUdfpsIconViewModel,
) {
- if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) return
- scope.launch {
- // forcePluginOpen is necessary to show over occluded apps.
- // This cannot be tied to the view's lifecycle because setting this allows the view
- // to be started in the first place.
- viewModel.forcePluginOpen.collect {
- notificationShadeWindowController.setForcePluginOpen(it, this)
- }
+ if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) {
+ return
}
+ optionallyAddUdfpsView(
+ view = view,
+ alternateBouncerUdfpsIconViewModel = alternateBouncerUdfpsIconViewModel,
+ )
val scrim = view.requireViewById(R.id.alternate_bouncer_scrim) as ScrimView
view.repeatWhenAttached { alternateBouncerViewContainer ->
@@ -99,6 +101,52 @@
}
}
}
+
+ private fun optionallyAddUdfpsView(
+ view: ConstraintLayout,
+ alternateBouncerUdfpsIconViewModel: AlternateBouncerUdfpsIconViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ alternateBouncerUdfpsIconViewModel.iconLocation.collect { iconLocation ->
+ val viewId = R.id.alternate_bouncer_udfps_icon_view
+ var udfpsView = view.getViewById(viewId)
+ if (udfpsView == null) {
+ udfpsView =
+ DeviceEntryIconView(view.context, null).apply { id = viewId }
+ view.addView(udfpsView)
+ AlternateBouncerUdfpsViewBinder.bind(
+ udfpsView,
+ alternateBouncerUdfpsIconViewModel,
+ )
+ }
+
+ val constraintSet = ConstraintSet().apply { clone(view) }
+ constraintSet.apply {
+ constrainWidth(viewId, iconLocation.width)
+ constrainHeight(viewId, iconLocation.height)
+ connect(
+ viewId,
+ ConstraintSet.TOP,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.TOP,
+ iconLocation.top,
+ )
+ connect(
+ viewId,
+ ConstraintSet.START,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.START,
+ iconLocation.left
+ )
+ }
+ constraintSet.applyTo(view)
+ }
+ }
+ }
+ }
+ }
}
private const val swipeTag = "AlternateBouncer-SWIPE"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index dcf4284..a02e8ac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -131,10 +131,10 @@
bgView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch { bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha } }
launch {
- bgViewModel.viewModel.collect { bgViewModel ->
- bgView.alpha = bgViewModel.alpha
- bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint)
+ bgViewModel.color.collect { color ->
+ bgView.imageTintList = ColorStateList.valueOf(color)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 4efd9ef..4c33d90 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -54,12 +54,11 @@
keyguardRootViewModel: KeyguardRootViewModel,
indicationController: KeyguardIndicationController,
): DisposableHandle {
- val indicationArea: ViewGroup = view.requireViewById(R.id.keyguard_indication_area)
- indicationController.setIndicationArea(indicationArea)
+ indicationController.setIndicationArea(view)
- val indicationText: TextView = indicationArea.requireViewById(R.id.keyguard_indication_text)
+ val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text)
val indicationTextBottom: TextView =
- indicationArea.requireViewById(R.id.keyguard_indication_text_bottom)
+ view.requireViewById(R.id.keyguard_indication_text_bottom)
view.clipChildren = false
view.clipToPadding = false
@@ -71,7 +70,7 @@
launch {
if (keyguardBottomAreaRefactor()) {
keyguardRootViewModel.alpha.collect { alpha ->
- indicationArea.apply {
+ view.apply {
this.importantForAccessibility =
if (alpha == 0f) {
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
@@ -83,7 +82,7 @@
}
} else {
viewModel.alpha.collect { alpha ->
- indicationArea.apply {
+ view.apply {
this.importantForAccessibility =
if (alpha == 0f) {
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
@@ -98,7 +97,7 @@
launch {
viewModel.indicationAreaTranslationX.collect { translationX ->
- indicationArea.translationX = translationX
+ view.translationX = translationX
}
}
@@ -113,9 +112,7 @@
0
}
}
- .collect { paddingPx ->
- indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
- }
+ .collect { paddingPx -> view.setPadding(paddingPx, 0, paddingPx, 0) }
}
launch {
@@ -124,7 +121,7 @@
.flatMapLatest { defaultBurnInOffsetY ->
viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
}
- .collect { translationY -> indicationArea.translationY = translationY }
+ .collect { translationY -> view.translationY = translationY }
}
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 01a1ca3..362e7e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -29,7 +29,6 @@
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
import com.android.internal.jank.InteractionJankMonitor
@@ -67,6 +66,7 @@
import javax.inject.Provider
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -205,7 +205,6 @@
childViews[aodNotificationIconContainerId]
?.setAodNotifIconContainerIsVisible(
isVisible,
- featureFlags,
iconsAppearTranslationPx.value,
screenOffAnimationController,
)
@@ -359,41 +358,32 @@
}
}
- @JvmStatic
- fun bindAodIconVisibility(
+ suspend fun bindAodNotifIconVisibility(
view: View,
isVisible: Flow<AnimatedValue<Boolean>>,
configuration: ConfigurationState,
- featureFlags: FeatureFlagsClassic,
screenOffAnimationController: ScreenOffAnimationController,
- ): DisposableHandle? {
+ ) {
KeyguardShadeMigrationNssl.assertInLegacyMode()
- if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return null
- return view.repeatWhenAttached {
- lifecycleScope.launch {
- val iconAppearTranslationPx =
- configuration
- .getDimensionPixelSize(R.dimen.shelf_appear_translation)
- .stateIn(this)
- isVisible.collect { isVisible ->
- view.setAodNotifIconContainerIsVisible(
- isVisible,
- featureFlags,
- iconAppearTranslationPx.value,
- screenOffAnimationController,
- )
- }
+ if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return
+ coroutineScope {
+ val iconAppearTranslationPx =
+ configuration.getDimensionPixelSize(R.dimen.shelf_appear_translation).stateIn(this)
+ isVisible.collect { isVisible ->
+ view.setAodNotifIconContainerIsVisible(
+ isVisible = isVisible,
+ iconsAppearTranslationPx = iconAppearTranslationPx.value,
+ screenOffAnimationController = screenOffAnimationController,
+ )
}
}
}
private fun View.setAodNotifIconContainerIsVisible(
isVisible: AnimatedValue<Boolean>,
- featureFlags: FeatureFlagsClassic,
iconsAppearTranslationPx: Int,
screenOffAnimationController: ScreenOffAnimationController,
) {
- val statusViewMigrated = KeyguardShadeMigrationNssl.isEnabled
animate().cancel()
val animatorListener =
object : AnimatorListenerAdapter() {
@@ -404,13 +394,13 @@
when {
!isVisible.isAnimating -> {
alpha = 1f
- if (!statusViewMigrated) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
translationY = 0f
}
visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE
}
newAodTransition() -> {
- animateInIconTranslation(statusViewMigrated)
+ animateInIconTranslation()
if (isVisible.value) {
CrossFadeHelper.fadeIn(this, animatorListener)
} else {
@@ -419,7 +409,7 @@
}
!isVisible.value -> {
// Let's make sure the icon are translated to 0, since we cancelled it above
- animateInIconTranslation(statusViewMigrated)
+ animateInIconTranslation()
CrossFadeHelper.fadeOut(this, animatorListener)
}
visibility != View.VISIBLE -> {
@@ -429,13 +419,12 @@
appearIcons(
animate = screenOffAnimationController.shouldAnimateAodIcons(),
iconsAppearTranslationPx,
- statusViewMigrated,
animatorListener,
)
}
else -> {
// Let's make sure the icons are translated to 0, since we cancelled it above
- animateInIconTranslation(statusViewMigrated)
+ animateInIconTranslation()
// We were fading out, let's fade in instead
CrossFadeHelper.fadeIn(this, animatorListener)
}
@@ -445,11 +434,10 @@
private fun View.appearIcons(
animate: Boolean,
iconAppearTranslation: Int,
- statusViewMigrated: Boolean,
animatorListener: Animator.AnimatorListener,
) {
if (animate) {
- if (!statusViewMigrated) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
translationY = -iconAppearTranslation.toFloat()
}
alpha = 0f
@@ -457,19 +445,19 @@
.alpha(1f)
.setInterpolator(Interpolators.LINEAR)
.setDuration(AOD_ICONS_APPEAR_DURATION)
- .apply { if (statusViewMigrated) animateInIconTranslation() }
+ .apply { if (KeyguardShadeMigrationNssl.isEnabled) animateInIconTranslation() }
.setListener(animatorListener)
.start()
} else {
alpha = 1.0f
- if (!statusViewMigrated) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
translationY = 0f
}
}
}
- private fun View.animateInIconTranslation(statusViewMigrated: Boolean) {
- if (!statusViewMigrated) {
+ private fun View.animateInIconTranslation() {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
deleted file mode 100644
index 52d87d3..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2023 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.keyguard.ui.binder
-
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
-
-@ExperimentalCoroutinesApi
-object UdfpsAodFingerprintViewBinder {
-
- /**
- * Drives UI for the UDFPS aod fingerprint view. See [UdfpsFingerprintViewBinder] and
- * [UdfpsBackgroundViewBinder].
- */
- @JvmStatic
- fun bind(
- view: LottieAnimationView,
- viewModel: UdfpsAodViewModel,
- ) {
- view.alpha = 0f
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.burnInOffsets.collect { burnInOffsets ->
- view.progress = burnInOffsets.progress
- view.translationX = burnInOffsets.x.toFloat()
- view.translationY = burnInOffsets.y.toFloat()
- }
- }
-
- launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
-
- launch {
- viewModel.padding.collect { padding ->
- view.setPadding(padding, padding, padding, padding)
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
deleted file mode 100644
index 0113628..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2023 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.keyguard.ui.binder
-
-import android.content.res.ColorStateList
-import android.widget.ImageView
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
-
-@ExperimentalCoroutinesApi
-object UdfpsBackgroundViewBinder {
-
- /**
- * Drives UI for the udfps background view. See [UdfpsAodFingerprintViewBinder] and
- * [UdfpsFingerprintViewBinder].
- */
- @JvmStatic
- fun bind(
- view: ImageView,
- viewModel: BackgroundViewModel,
- ) {
- view.alpha = 0f
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.transition.collect {
- view.alpha = it.alpha
- view.scaleX = it.scale
- view.scaleY = it.scale
- view.imageTintList = ColorStateList.valueOf(it.color)
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
deleted file mode 100644
index d4621e6..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2023 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.keyguard.ui.binder
-
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieProperty
-import com.airbnb.lottie.model.KeyPath
-import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
-
-@ExperimentalCoroutinesApi
-object UdfpsFingerprintViewBinder {
- private var udfpsIconColor = 0
-
- /**
- * Drives UI for the UDFPS fingerprint view when it's NOT on aod. See
- * [UdfpsAodFingerprintViewBinder] and [UdfpsBackgroundViewBinder].
- */
- @JvmStatic
- fun bind(
- view: LottieAnimationView,
- viewModel: FingerprintViewModel,
- ) {
- view.alpha = 0f
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.transition.collect {
- view.alpha = it.alpha
- view.scaleX = it.scale
- view.scaleY = it.scale
- if (udfpsIconColor != (it.color)) {
- udfpsIconColor = it.color
- view.invalidate()
- }
- }
- }
-
- launch {
- viewModel.burnInOffsets.collect { burnInOffsets ->
- view.translationX = burnInOffsets.x.toFloat()
- view.translationY = burnInOffsets.y.toFloat()
- }
- }
-
- launch {
- viewModel.dozeAmount.collect { dozeAmount ->
- // Lottie progress represents: aod=0 to lockscreen=1
- view.progress = 1f - dozeAmount
- }
- }
-
- launch {
- viewModel.padding.collect { padding ->
- view.setPadding(padding, padding, padding, padding)
- }
- }
- }
- }
-
- // Add a callback that updates the color to `udfpsIconColor` whenever invalidate is called
- view.addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(udfpsIconColor, PorterDuff.Mode.SRC_ATOP)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
deleted file mode 100644
index aabb3f4..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2023 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.biometrics.ui.binder
-
-import android.view.View
-import com.android.systemui.res.R
-import com.android.systemui.keyguard.ui.binder.UdfpsAodFingerprintViewBinder
-import com.android.systemui.keyguard.ui.binder.UdfpsBackgroundViewBinder
-import com.android.systemui.keyguard.ui.binder.UdfpsFingerprintViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
-import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-@ExperimentalCoroutinesApi
-object UdfpsKeyguardInternalViewBinder {
-
- @JvmStatic
- fun bind(
- view: View,
- viewModel: UdfpsKeyguardInternalViewModel,
- aodViewModel: UdfpsAodViewModel,
- fingerprintViewModel: FingerprintViewModel,
- backgroundViewModel: BackgroundViewModel,
- ) {
- view.accessibilityDelegate = viewModel.accessibilityDelegate
-
- // bind child views
- UdfpsAodFingerprintViewBinder.bind(view.requireViewById(R.id.udfps_aod_fp), aodViewModel)
- UdfpsFingerprintViewBinder.bind(
- view.requireViewById(R.id.udfps_lockscreen_fp),
- fingerprintViewModel
- )
- UdfpsBackgroundViewBinder.bind(
- view.requireViewById(R.id.udfps_keyguard_fp_bg),
- backgroundViewModel
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
deleted file mode 100644
index 475d26f..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 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.keyguard.ui.binder
-
-import android.graphics.RectF
-import android.view.View
-import android.widget.FrameLayout
-import androidx.asynclayoutinflater.view.AsyncLayoutInflater
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.biometrics.UdfpsKeyguardView
-import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
-import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.res.R
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.launch
-
-@ExperimentalCoroutinesApi
-object UdfpsKeyguardViewBinder {
- /**
- * Drives UI for the keyguard UDFPS view. Inflates child views on a background thread. For view
- * binders for its child views, see [UdfpsFingerprintViewBinder], [UdfpsBackgroundViewBinder] &
- * [UdfpsAodFingerprintViewBinder].
- */
- @JvmStatic
- fun bind(
- view: UdfpsKeyguardView,
- viewModel: UdfpsKeyguardViewModel,
- udfpsKeyguardInternalViewModel: UdfpsKeyguardInternalViewModel,
- aodViewModel: UdfpsAodViewModel,
- fingerprintViewModel: FingerprintViewModel,
- backgroundViewModel: BackgroundViewModel,
- ) {
- val layoutInflaterFinishListener =
- AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent ->
- UdfpsKeyguardInternalViewBinder.bind(
- inflatedInternalView,
- udfpsKeyguardInternalViewModel,
- aodViewModel,
- fingerprintViewModel,
- backgroundViewModel,
- )
- val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams
- lp.width = viewModel.sensorBounds.width()
- lp.height = viewModel.sensorBounds.height()
- val relativeToView =
- getBoundsRelativeToView(
- inflatedInternalView,
- RectF(viewModel.sensorBounds),
- )
- lp.setMarginsRelative(
- relativeToView.left.toInt(),
- relativeToView.top.toInt(),
- relativeToView.right.toInt(),
- relativeToView.bottom.toInt(),
- )
- parent!!.addView(inflatedInternalView, lp)
- }
- val inflater = AsyncLayoutInflater(view.context)
- inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener)
-
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
- combine(aodViewModel.isVisible, fingerprintViewModel.visible) {
- isAodVisible,
- isFingerprintVisible ->
- isAodVisible || isFingerprintVisible
- }
- .collect { view.setVisible(it) }
- }
- }
- }
- }
-
- /**
- * Converts coordinates of RectF relative to the screen to coordinates relative to this view.
- *
- * @param bounds RectF based off screen coordinates in current orientation
- */
- private fun getBoundsRelativeToView(view: View, bounds: RectF): RectF {
- val pos: IntArray = view.locationOnScreen
- return RectF(
- bounds.left - pos[0],
- bounds.top - pos[1],
- bounds.right - pos[0],
- bounds.bottom - pos[1]
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index 9d557bb..920fc04 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -15,6 +15,8 @@
*/
package com.android.systemui.keyguard.ui.transitions
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToAodTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
@@ -39,6 +41,18 @@
abstract class DeviceEntryIconTransitionModule {
@Binds
@IntoSet
+ abstract fun alternateBouncerToAod(
+ impl: AlternateBouncerToAodTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun alternateBouncerToGone(
+ impl: AlternateBouncerToGoneTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
abstract fun aodToLockscreen(
impl: AodToLockscreenTransitionViewModel
): DeviceEntryIconTransition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index 1c6a2ab..bc9671e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -31,18 +31,21 @@
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection
import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule.Companion.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION
import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
import java.util.Optional
import javax.inject.Inject
import javax.inject.Named
import kotlin.jvm.optionals.getOrNull
+import kotlinx.coroutines.ExperimentalCoroutinesApi
/**
* Positions elements of the lockscreen to the default position.
*
* This will be the most common use case for phones in portrait mode.
*/
+@ExperimentalCoroutinesApi
@SysUISingleton
@JvmSuppressWildcards
class DefaultKeyguardBlueprint
@@ -62,6 +65,7 @@
communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
clockSection: ClockSection,
smartspaceSection: SmartspaceSection,
+ udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection,
) : KeyguardBlueprint {
override val id: String = DEFAULT
@@ -79,7 +83,8 @@
aodBurnInSection,
communalTutorialIndicatorSection,
clockSection,
- defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views.
+ defaultDeviceEntrySection,
+ udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others
)
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index bf70682..9b40433 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -29,14 +29,17 @@
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection
import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
import javax.inject.Inject
import javax.inject.Named
+import kotlinx.coroutines.ExperimentalCoroutinesApi
/** Vertically aligns the shortcuts with the udfps. */
+@ExperimentalCoroutinesApi
@SysUISingleton
class ShortcutsBesideUdfpsKeyguardBlueprint
@Inject
@@ -53,6 +56,7 @@
defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
aodNotificationIconsSection: AodNotificationIconsSection,
aodBurnInSection: AodBurnInSection,
+ udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection,
) : KeyguardBlueprint {
override val id: String = SHORTCUTS_BESIDE_UDFPS
@@ -68,7 +72,8 @@
splitShadeGuidelines,
aodNotificationIconsSection,
aodBurnInSection,
- defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views.
+ defaultDeviceEntrySection,
+ udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others
)
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index a693ec9..0bf9ad0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -23,7 +23,6 @@
import android.util.DisplayMetrics
import android.view.View
import android.view.WindowManager
-import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
@@ -32,31 +31,24 @@
import com.android.keyguard.LockIconViewController
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.biometrics.AuthController
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
-import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder
import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
-import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.gesture.TapGestureDetector
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-/** Includes both the device entry icon and the alternate bouncer scrim. */
+/** Includes the device entry icon. */
@ExperimentalCoroutinesApi
class DefaultDeviceEntrySection
@Inject
@@ -72,27 +64,15 @@
private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
private val falsingManager: Lazy<FalsingManager>,
- private val alternateBouncerViewModel: Lazy<AlternateBouncerViewModel>,
- private val notificationShadeWindowController: Lazy<NotificationShadeWindowController>,
- @Application private val scope: CoroutineScope,
- private val swipeUpAnywhereGestureHandler: Lazy<SwipeUpAnywhereGestureHandler>,
- private val tapGestureDetector: Lazy<TapGestureDetector>,
private val vibratorHelper: Lazy<VibratorHelper>,
) : KeyguardSection() {
private val deviceEntryIconViewId = R.id.device_entry_icon_view
- private val alternateBouncerViewId = R.id.alternate_bouncer
override fun addViews(constraintLayout: ConstraintLayout) {
if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) {
return
}
- if (DeviceEntryUdfpsRefactor.isEnabled) {
- // The alternate bouncer scrim needs to be below the device entry icon view, so
- // we add the view here before adding the device entry icon view.
- View.inflate(context, R.layout.alternate_bouncer, constraintLayout)
- }
-
notificationPanelView.findViewById<View>(R.id.lock_icon_view).let {
notificationPanelView.removeView(it)
}
@@ -119,17 +99,6 @@
vibratorHelper.get(),
)
}
- constraintLayout.findViewById<FrameLayout?>(alternateBouncerViewId)?.let {
- AlternateBouncerViewBinder.bind(
- it,
- alternateBouncerViewModel.get(),
- scope,
- notificationShadeWindowController.get(),
- falsingManager.get(),
- swipeUpAnywhereGestureHandler.get(),
- tapGestureDetector.get(),
- )
- }
} else {
constraintLayout.findViewById<LockIconView?>(R.id.lock_icon_view)?.let {
lockIconViewController.get().setLockIconView(it)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
index 56f717d..66c137f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
@@ -54,7 +54,7 @@
if (keyguardBottomAreaRefactor()) {
indicationAreaHandle =
KeyguardIndicationAreaBinder.bind(
- constraintLayout,
+ constraintLayout.requireViewById(R.id.keyguard_indication_area),
keyguardIndicationAreaViewModel,
keyguardRootViewModel,
indicationController,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
new file mode 100644
index 0000000..e1a33de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.Flags
+import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder
+import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
+import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Positions the UDFPS accessibility overlay on the bottom half of the keyguard. */
+@ExperimentalCoroutinesApi
+class DefaultUdfpsAccessibilityOverlaySection
+@Inject
+constructor(
+ private val context: Context,
+ private val viewModel: UdfpsAccessibilityOverlayViewModel,
+) : KeyguardSection() {
+ private val viewId = R.id.udfps_accessibility_overlay
+ private var cachedConstraintLayout: ConstraintLayout? = null
+
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ cachedConstraintLayout = constraintLayout
+ constraintLayout.addView(UdfpsAccessibilityOverlay(context).apply { id = viewId })
+ }
+
+ override fun bindData(constraintLayout: ConstraintLayout) {
+ UdfpsAccessibilityOverlayBinder.bind(
+ constraintLayout.findViewById(viewId)!!,
+ viewModel,
+ )
+ }
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {
+ constraintSet.apply {
+ connect(viewId, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START)
+ connect(viewId, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END)
+
+ create(R.id.udfps_accessibility_overlay_top_guideline, ConstraintSet.HORIZONTAL)
+ setGuidelinePercent(R.id.udfps_accessibility_overlay_top_guideline, .5f)
+ connect(
+ viewId,
+ ConstraintSet.TOP,
+ R.id.udfps_accessibility_overlay_top_guideline,
+ ConstraintSet.BOTTOM,
+ )
+
+ if (Flags.keyguardBottomAreaRefactor()) {
+ connect(
+ viewId,
+ ConstraintSet.BOTTOM,
+ R.id.keyguard_indication_area,
+ ConstraintSet.TOP,
+ )
+ } else {
+ connect(viewId, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
+ }
+ }
+ }
+
+ override fun removeViews(constraintLayout: ConstraintLayout) {
+ constraintLayout.removeView(viewId)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
new file mode 100644
index 0000000..a8e3be7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+
+/**
+ * Breaks down ALTERNATE BOUNCER->AOD transition into discrete steps for corresponding views to
+ * consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class AlternateBouncerToAodTransitionViewModel
+@Inject
+constructor(
+ interactor: KeyguardTransitionInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+ animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
+ stepFlow = interactor.transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD),
+ )
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
+ onStep = { 1 - it },
+ onFinish = { 0f },
+ )
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled
+ ->
+ if (udfpsEnrolledAndEnabled) {
+ transitionAnimation.immediatelyTransitionTo(1f)
+ } else {
+ emptyFlow()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index 023d16ca..5d6b0ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -18,8 +18,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_GONE_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.ScrimAlpha
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -33,10 +37,20 @@
class AlternateBouncerToGoneTransitionViewModel
@Inject
constructor(
+ interactor: KeyguardTransitionInteractor,
bouncerToGoneFlows: BouncerToGoneFlows,
-) {
+ animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = TO_GONE_DURATION,
+ stepFlow = interactor.transition(ALTERNATE_BOUNCER, KeyguardState.GONE),
+ )
/** Scrim alpha values */
val scrimAlpha: Flow<ScrimAlpha> =
bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_BOUNCER)
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
new file mode 100644
index 0000000..f4ae365
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import android.content.Context
+import android.hardware.biometrics.SensorLocationInternal
+import com.android.settingslib.Utils
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Models the UI state for the UDFPS icon view in the alternate bouncer view. */
+@ExperimentalCoroutinesApi
+class AlternateBouncerUdfpsIconViewModel
+@Inject
+constructor(
+ val context: Context,
+ configurationInteractor: ConfigurationInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+ deviceEntryBackgroundViewModel: DeviceEntryBackgroundViewModel,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
+) {
+ private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
+ val iconLocation: Flow<IconLocation> =
+ isSupported.flatMapLatest { supportsUI ->
+ if (supportsUI) {
+ fingerprintPropertyRepository.sensorLocations.map { sensorLocations ->
+ val sensorLocation =
+ sensorLocations.getOrDefault("", SensorLocationInternal.DEFAULT)
+ IconLocation(
+ left = sensorLocation.sensorLocationX - sensorLocation.sensorRadius,
+ top = sensorLocation.sensorLocationY - sensorLocation.sensorRadius,
+ right = sensorLocation.sensorLocationX + sensorLocation.sensorRadius,
+ bottom = sensorLocation.sensorLocationY + sensorLocation.sensorRadius,
+ )
+ }
+ } else {
+ emptyFlow()
+ }
+ }
+ val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
+ flowOf(DeviceEntryIconView.AccessibilityHintType.ENTER)
+
+ private val fgIconColor: Flow<Int> =
+ configurationInteractor.onAnyConfigurationChange
+ .map { Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) }
+ .onStart {
+ emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary))
+ }
+ private val fgIconPadding: Flow<Int> =
+ configurationInteractor.scaleForResolution.map { scale ->
+ (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+ .roundToInt()
+ }
+ val fgViewModel: Flow<DeviceEntryForegroundViewModel.ForegroundIconViewModel> =
+ combine(
+ fgIconColor,
+ fgIconPadding,
+ ) { color, padding ->
+ DeviceEntryForegroundViewModel.ForegroundIconViewModel(
+ type = DeviceEntryIconView.IconType.FINGERPRINT,
+ useAodVariant = false,
+ tint = color,
+ padding = padding,
+ )
+ }
+
+ val bgColor: Flow<Int> = deviceEntryBackgroundViewModel.color
+ val bgAlpha: Flow<Float> = flowOf(1f)
+
+ data class IconLocation(
+ val left: Int,
+ val top: Int,
+ val right: Int,
+ val bottom: Int,
+ ) {
+ val width = right - left
+ val height = bottom - top
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 3e8bbb3..be9ae1d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -19,11 +19,10 @@
import android.content.Context
import com.android.settingslib.Utils
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
@@ -34,7 +33,7 @@
@Inject
constructor(
val context: Context,
- configurationRepository: ConfigurationRepository, // TODO (b/309655554): create & use interactor
+ configurationInteractor: ConfigurationInteractor,
lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
@@ -42,9 +41,10 @@
occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel,
) {
- private val color: Flow<Int> =
- configurationRepository.onAnyConfigurationChange
+ val color: Flow<Int> =
+ configurationInteractor.onAnyConfigurationChange
.map {
Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorSurface)
}
@@ -56,7 +56,7 @@
)
)
}
- private val alpha: Flow<Float> =
+ val alpha: Flow<Float> =
setOf(
lockscreenToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
aodToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
@@ -65,19 +65,7 @@
occludedToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
occludedToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
dreamingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ alternateBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
)
.merge()
-
- val viewModel: Flow<BackgroundViewModel> =
- combine(color, alpha) { color, alpha ->
- BackgroundViewModel(
- alpha = alpha,
- tint = color,
- )
- }
-
- data class BackgroundViewModel(
- val alpha: Float,
- val tint: Int,
- )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 9a50d83..fe0b365 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -19,6 +19,7 @@
import android.content.Context
import com.android.settingslib.Utils
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -26,7 +27,6 @@
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.res.R
import javax.inject.Inject
-import kotlin.math.roundToInt
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -45,6 +45,7 @@
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
transitionInteractor: KeyguardTransitionInteractor,
deviceEntryIconViewModel: DeviceEntryIconViewModel,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
private val isShowingAod: Flow<Boolean> =
transitionInteractor.startedKeyguardState.map { keyguardState ->
@@ -73,11 +74,7 @@
isTransitionToAod && isUdfps
}
.distinctUntilChanged()
- private val padding: Flow<Int> =
- configurationRepository.scaleForResolution.map { scale ->
- (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
- .roundToInt()
- }
+ private val padding: Flow<Int> = udfpsOverlayInteractor.iconPadding
val viewModel: Flow<ForegroundIconViewModel> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index f95713b..eacaa40 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.kotlin.sample
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -52,15 +53,12 @@
transitionInteractor: KeyguardTransitionInteractor,
val keyguardInteractor: KeyguardInteractor,
val viewModel: AodToLockscreenTransitionViewModel,
- val shadeDependentFlows: ShadeDependentFlows,
private val sceneContainerFlags: SceneContainerFlags,
private val keyguardViewController: Lazy<KeyguardViewController>,
private val deviceEntryInteractor: DeviceEntryInteractor,
) {
private val intEvaluator = IntEvaluator()
private val floatEvaluator = FloatEvaluator()
- private val toAodFromState: Flow<KeyguardState> =
- transitionInteractor.transitionStepsToState(KeyguardState.AOD).map { it.from }
private val showingAlternateBouncer: Flow<Boolean> =
transitionInteractor.startedKeyguardState.map { keyguardState ->
keyguardState == KeyguardState.ALTERNATE_BOUNCER
@@ -95,13 +93,31 @@
fullyDozingBurnInProgress,
)
}
+
+ private val dozeAmount: Flow<Float> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ merge(
+ transitionInteractor.transitionStepsFromState(KeyguardState.AOD).map {
+ 1f - it.value
+ },
+ transitionInteractor.transitionStepsToState(KeyguardState.AOD).map { it.value }
+ ),
+ ) { startedKeyguardTransitionStep, aodTransitionAmount ->
+ if (
+ startedKeyguardTransitionStep.to == KeyguardState.AOD ||
+ startedKeyguardTransitionStep.from == KeyguardState.AOD
+ ) {
+ aodTransitionAmount
+ } else {
+ // in case a new transition (ie: to occluded) cancels a transition to or from
+ // aod, then we want to make sure the doze amount is still updated to 0
+ 0f
+ }
+ }
// Burn-in offsets that animate based on the transition amount to AOD
private val animatedBurnInOffsets: Flow<BurnInOffsets> =
- combine(
- nonAnimatedBurnInOffsets,
- transitionInteractor.transitionStepsToState(KeyguardState.AOD)
- ) { burnInOffsets, transitionStepsToAod ->
- val dozeAmount = transitionStepsToAod.value
+ combine(nonAnimatedBurnInOffsets, dozeAmount) { burnInOffsets, dozeAmount ->
BurnInOffsets(
intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.x),
intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.y),
@@ -120,22 +136,35 @@
val burnInOffsets: Flow<BurnInOffsets> =
deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled ->
if (udfpsEnrolled) {
- toAodFromState.flatMapLatest { fromState ->
- when (fromState) {
- KeyguardState.AOD,
- KeyguardState.GONE,
- KeyguardState.OCCLUDED,
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
- KeyguardState.OFF,
- KeyguardState.DOZING,
- KeyguardState.DREAMING,
- KeyguardState.PRIMARY_BOUNCER -> nonAnimatedBurnInOffsets
- KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets
- KeyguardState.LOCKSCREEN ->
- shadeDependentFlows.transitionFlow(
- flowWhenShadeIsExpanded = nonAnimatedBurnInOffsets,
- flowWhenShadeIsNotExpanded = animatedBurnInOffsets,
- )
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep.sample(
+ shadeInteractor.isAnyFullyExpanded,
+ ::Pair
+ ),
+ animatedBurnInOffsets,
+ nonAnimatedBurnInOffsets,
+ ) {
+ (startedTransitionStep, shadeExpanded),
+ animatedBurnInOffsets,
+ nonAnimatedBurnInOffsets ->
+ if (startedTransitionStep.to == KeyguardState.AOD) {
+ when (startedTransitionStep.from) {
+ KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets
+ KeyguardState.LOCKSCREEN ->
+ if (shadeExpanded) {
+ nonAnimatedBurnInOffsets
+ } else {
+ animatedBurnInOffsets
+ }
+ else -> nonAnimatedBurnInOffsets
+ }
+ } else if (startedTransitionStep.from == KeyguardState.AOD) {
+ when (startedTransitionStep.to) {
+ KeyguardState.LOCKSCREEN -> animatedBurnInOffsets
+ else -> BurnInOffsets(x = 0, y = 0, progress = 0f)
+ }
+ } else {
+ BurnInOffsets(x = 0, y = 0, progress = 0f)
}
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
new file mode 100644
index 0000000..d57e569
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class LockscreenContentViewModel
+@Inject
+constructor(
+ private val interactor: KeyguardBlueprintInteractor,
+ private val authController: AuthController,
+) {
+ val isUdfpsVisible: Boolean
+ get() = authController.isUdfpsSupported
+
+ fun blueprintId(scope: CoroutineScope): StateFlow<String> {
+ return interactor.blueprint
+ .map { it.id }
+ .distinctUntilChanged()
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = interactor.getCurrentBlueprint().id,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
deleted file mode 100644
index 6e77e13e..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 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.keyguard.ui.viewmodel
-
-import android.content.Context
-import com.android.systemui.keyguard.domain.interactor.Offsets
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-import com.android.systemui.res.R
-import javax.inject.Inject
-import kotlin.math.roundToInt
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-/** View-model for UDFPS AOD view. */
-@ExperimentalCoroutinesApi
-class UdfpsAodViewModel
-@Inject
-constructor(
- val interactor: UdfpsKeyguardInteractor,
- val context: Context,
-) {
- val alpha: Flow<Float> = interactor.dozeAmount
- val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets
- val isVisible: Flow<Boolean> = alpha.map { it != 0f }
-
- // Padding between the fingerprint icon and its bounding box in pixels.
- val padding: Flow<Int> =
- interactor.scaleForResolution.map { scale ->
- (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
- .roundToInt()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt
deleted file mode 100644
index d894a11..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2023 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.keyguard.ui.viewmodel
-
-import com.android.systemui.biometrics.UdfpsKeyguardAccessibilityDelegate
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-@ExperimentalCoroutinesApi
-class UdfpsKeyguardInternalViewModel
-@Inject
-constructor(val accessibilityDelegate: UdfpsKeyguardAccessibilityDelegate)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
deleted file mode 100644
index 098b481..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.android.systemui.keyguard.ui.viewmodel
-
-import android.graphics.Rect
-import com.android.systemui.biometrics.UdfpsKeyguardView
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.ui.binder.UdfpsKeyguardViewBinder
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-@ExperimentalCoroutinesApi
-@SysUISingleton
-class UdfpsKeyguardViewModels
-@Inject
-constructor(
- private val viewModel: UdfpsKeyguardViewModel,
- private val internalViewModel: UdfpsKeyguardInternalViewModel,
- private val aodViewModel: UdfpsAodViewModel,
- private val lockscreenFingerprintViewModel: FingerprintViewModel,
- private val lockscreenBackgroundViewModel: BackgroundViewModel,
-) {
-
- fun setSensorBounds(sensorBounds: Rect) {
- viewModel.sensorBounds = sensorBounds
- }
-
- fun bindViews(view: UdfpsKeyguardView) {
- UdfpsKeyguardViewBinder.bind(
- view,
- viewModel,
- internalViewModel,
- aodViewModel,
- lockscreenFingerprintViewModel,
- lockscreenBackgroundViewModel
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
deleted file mode 100644
index 642904d..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2023 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.keyguard.ui.viewmodel
-
-import android.content.Context
-import androidx.annotation.ColorInt
-import com.android.settingslib.Utils.getColorAttrDefaultColor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.Offsets
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.res.R
-import com.android.wm.shell.animation.Interpolators
-import javax.inject.Inject
-import kotlin.math.roundToInt
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-
-/** View-model for UDFPS lockscreen views. */
-@ExperimentalCoroutinesApi
-open class UdfpsLockscreenViewModel(
- context: Context,
- lockscreenColorResId: Int,
- alternateBouncerColorResId: Int,
- transitionInteractor: KeyguardTransitionInteractor,
- udfpsKeyguardInteractor: UdfpsKeyguardInteractor,
- keyguardInteractor: KeyguardInteractor,
-) {
- private val toLockscreen: Flow<TransitionViewModel> =
- transitionInteractor.anyStateToLockscreenTransition.map {
- TransitionViewModel(
- alpha =
- if (it.from == KeyguardState.AOD) {
- it.value // animate
- } else {
- 1f
- },
- scale = 1f,
- color = getColorAttrDefaultColor(context, lockscreenColorResId),
- )
- }
-
- private val toAlternateBouncer: Flow<TransitionViewModel> =
- keyguardInteractor.statusBarState.flatMapLatest { statusBarState ->
- transitionInteractor.transitionStepsToState(KeyguardState.ALTERNATE_BOUNCER).map {
- TransitionViewModel(
- alpha = 1f,
- scale =
- if (visibleInKeyguardState(it.from, statusBarState)) {
- 1f
- } else {
- Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value)
- },
- color = getColorAttrDefaultColor(context, alternateBouncerColorResId),
- )
- }
- }
-
- private val fadeOut: Flow<TransitionViewModel> =
- keyguardInteractor.statusBarState.flatMapLatest { statusBarState ->
- merge(
- transitionInteractor.anyStateToGoneTransition,
- transitionInteractor.anyStateToAodTransition,
- transitionInteractor.anyStateToOccludedTransition,
- transitionInteractor.anyStateToPrimaryBouncerTransition,
- transitionInteractor.anyStateToDreamingTransition,
- )
- .map {
- TransitionViewModel(
- alpha =
- if (visibleInKeyguardState(it.from, statusBarState)) {
- 1f - it.value
- } else {
- 0f
- },
- scale = 1f,
- color =
- if (it.from == KeyguardState.ALTERNATE_BOUNCER) {
- getColorAttrDefaultColor(context, alternateBouncerColorResId)
- } else {
- getColorAttrDefaultColor(context, lockscreenColorResId)
- },
- )
- }
- }
-
- private fun visibleInKeyguardState(
- state: KeyguardState,
- statusBarState: StatusBarState
- ): Boolean {
- return when (state) {
- KeyguardState.OFF,
- KeyguardState.DOZING,
- KeyguardState.DREAMING,
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
- KeyguardState.AOD,
- KeyguardState.PRIMARY_BOUNCER,
- KeyguardState.GONE,
- KeyguardState.OCCLUDED -> false
- KeyguardState.LOCKSCREEN -> statusBarState == StatusBarState.KEYGUARD
- KeyguardState.ALTERNATE_BOUNCER -> true
- }
- }
-
- private val keyguardStateTransition =
- merge(
- toAlternateBouncer,
- toLockscreen,
- fadeOut,
- )
-
- private val dialogHideAffordancesAlphaMultiplier: Flow<Float> =
- udfpsKeyguardInteractor.dialogHideAffordancesRequest.map { hideAffordances ->
- if (hideAffordances) {
- 0f
- } else {
- 1f
- }
- }
-
- private val alphaMultiplier: Flow<Float> =
- combine(
- transitionInteractor.startedKeyguardState,
- dialogHideAffordancesAlphaMultiplier,
- udfpsKeyguardInteractor.shadeExpansion,
- udfpsKeyguardInteractor.qsProgress,
- ) { startedKeyguardState, dialogHideAffordancesAlphaMultiplier, shadeExpansion, qsProgress
- ->
- if (startedKeyguardState == KeyguardState.ALTERNATE_BOUNCER) {
- 1f
- } else {
- dialogHideAffordancesAlphaMultiplier * (1f - shadeExpansion) * (1f - qsProgress)
- }
- }
-
- val transition: Flow<TransitionViewModel> =
- combine(
- alphaMultiplier,
- keyguardStateTransition,
- ) { alphaMultiplier, keyguardStateTransition ->
- TransitionViewModel(
- alpha = keyguardStateTransition.alpha * alphaMultiplier,
- scale = keyguardStateTransition.scale,
- color = keyguardStateTransition.color,
- )
- }
- val visible: Flow<Boolean> = transition.map { it.alpha != 0f }
-}
-
-@ExperimentalCoroutinesApi
-class FingerprintViewModel
-@Inject
-constructor(
- val context: Context,
- transitionInteractor: KeyguardTransitionInteractor,
- interactor: UdfpsKeyguardInteractor,
- keyguardInteractor: KeyguardInteractor,
-) :
- UdfpsLockscreenViewModel(
- context,
- android.R.attr.textColorPrimary,
- com.android.internal.R.attr.materialColorOnPrimaryFixed,
- transitionInteractor,
- interactor,
- keyguardInteractor,
- ) {
- val dozeAmount: Flow<Float> = interactor.dozeAmount
- val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets
-
- // Padding between the fingerprint icon and its bounding box in pixels.
- val padding: Flow<Int> =
- interactor.scaleForResolution.map { scale ->
- (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
- .roundToInt()
- }
-}
-
-@ExperimentalCoroutinesApi
-class BackgroundViewModel
-@Inject
-constructor(
- val context: Context,
- transitionInteractor: KeyguardTransitionInteractor,
- interactor: UdfpsKeyguardInteractor,
- keyguardInteractor: KeyguardInteractor,
-) :
- UdfpsLockscreenViewModel(
- context,
- com.android.internal.R.attr.colorSurface,
- com.android.internal.R.attr.materialColorPrimaryFixed,
- transitionInteractor,
- interactor,
- keyguardInteractor,
- )
-
-data class TransitionViewModel(
- val alpha: Float,
- val scale: Float,
- @ColorInt val color: Int,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index a99c51c2..be93936 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -405,12 +405,15 @@
}
widgetGroupIds.forEach { id ->
squishedViewState.widgetStates.get(id)?.let { state ->
- state.alpha =
- calculateAlpha(
- squishFraction,
- startPosition / nonsquishedHeight,
- endPosition / nonsquishedHeight
- )
+ // Don't modify alpha for elements that should be invisible (e.g. disabled seekbar)
+ if (state.alpha != 0f) {
+ state.alpha =
+ calculateAlpha(
+ squishFraction,
+ startPosition / nonsquishedHeight,
+ endPosition / nonsquishedHeight
+ )
+ }
}
}
return groupTop // used for the widget group above this group
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
index 898298c..77279f2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.util
import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the media_in_scene_container flag state. */
@@ -25,6 +26,10 @@
/** The aconfig flag name */
const val FLAG_NAME = Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
/** Is the flag enabled? */
@JvmStatic
inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 0e7e69b..270bfbe 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -263,11 +263,7 @@
* If the shortcut entry `android:enabled` is set to `true`, the shortcut will be visible in the
* Widget Picker to all users.
*/
- // TODO(b/316332684)
- @Suppress("UNREACHABLE_CODE")
fun setNoteTaskShortcutEnabled(value: Boolean, user: UserHandle) {
- return // shortcut should not be enabled until additional features are implemented.
-
if (!userManager.isUserUnlocked(user)) {
debugLog { "setNoteTaskShortcutEnabled call but user locked: user=$user" }
return
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 1ab64b7..ba3357c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -17,12 +17,10 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
-import android.os.Build;
import android.provider.Settings;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.util.leak.GarbageMonitor;
+import com.android.systemui.res.R;
import java.util.ArrayList;
import java.util.Arrays;
@@ -44,10 +42,6 @@
final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
tiles.addAll(Arrays.asList(defaultTileList.split(",")));
- if (Build.IS_DEBUGGABLE
- && GarbageMonitor.ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS) {
- tiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
- }
return tiles;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 2af7ae0..47b0624 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -23,7 +23,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
@@ -33,7 +32,6 @@
import androidx.annotation.Nullable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.qs.QSTile;
@@ -42,8 +40,8 @@
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.util.leak.GarbageMonitor;
import java.util.ArrayList;
import java.util.Arrays;
@@ -114,9 +112,6 @@
possibleTiles.add(spec);
}
}
- if (Build.IS_DEBUGGABLE && !current.contains(GarbageMonitor.MemoryTile.TILE_SPEC)) {
- possibleTiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
- }
final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
possibleTiles.remove("cell");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 17e6375..bdcbac0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -14,7 +14,6 @@
package com.android.systemui.qs.tileimpl;
-import android.os.Build;
import android.util.Log;
import androidx.annotation.Nullable;
@@ -25,15 +24,14 @@
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.util.leak.GarbageMonitor;
-
-import dagger.Lazy;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Provider;
+import dagger.Lazy;
+
/**
* A factory that creates Quick Settings tiles based on a tileSpec
*
@@ -79,9 +77,7 @@
@Nullable
protected QSTileImpl createTileInternal(String tileSpec) {
// Stock tiles.
- if (mTileMap.containsKey(tileSpec)
- // We should not return a Garbage Monitory Tile if the build is not Debuggable
- && (!tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC) || Build.IS_DEBUGGABLE)) {
+ if (mTileMap.containsKey(tileSpec)) {
return mTileMap.get(tileSpec).get();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
index ca5302e..c932cee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
@@ -16,11 +16,15 @@
package com.android.systemui.qs.tiles.impl.custom.data.repository
+import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
import android.graphics.drawable.Icon
import android.os.UserHandle
import android.service.quicksettings.Tile
+import android.service.quicksettings.TileService
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.external.CustomTileStatePersister
+import com.android.systemui.qs.external.PackageManagerAdapter
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.commons.copy
@@ -63,6 +67,12 @@
*/
fun getTile(user: UserHandle): Tile?
+ /** @see [com.android.systemui.qs.external.TileLifecycleManager.isActiveTile] */
+ suspend fun isTileActive(): Boolean
+
+ /** @see [com.android.systemui.qs.external.TileLifecycleManager.isToggleableTile] */
+ suspend fun isTileToggleable(): Boolean
+
/**
* Updates tile with the non-null values from [newTile]. Overwrites the current cache when
* [user] differs from the cached one. [isPersistable] tile will be persisted to be possibly
@@ -92,6 +102,7 @@
constructor(
private val tileSpec: TileSpec.CustomTileSpec,
private val customTileStatePersister: CustomTileStatePersister,
+ private val packageManagerAdapter: PackageManagerAdapter,
@Background private val backgroundContext: CoroutineContext,
) : CustomTileRepository {
@@ -149,6 +160,34 @@
}
}
+ override suspend fun isTileActive(): Boolean =
+ withContext(backgroundContext) {
+ try {
+ val info: ServiceInfo =
+ packageManagerAdapter.getServiceInfo(
+ tileSpec.componentName,
+ META_DATA_QUERY_FLAGS
+ )
+ info.metaData?.getBoolean(TileService.META_DATA_ACTIVE_TILE, false) == true
+ } catch (e: PackageManager.NameNotFoundException) {
+ false
+ }
+ }
+
+ override suspend fun isTileToggleable(): Boolean =
+ withContext(backgroundContext) {
+ try {
+ val info: ServiceInfo =
+ packageManagerAdapter.getServiceInfo(
+ tileSpec.componentName,
+ META_DATA_QUERY_FLAGS
+ )
+ info.metaData?.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false) == true
+ } catch (e: PackageManager.NameNotFoundException) {
+ false
+ }
+ }
+
private suspend fun updateTile(
user: UserHandle,
isPersistable: Boolean,
@@ -193,4 +232,12 @@
private fun UserHandle.getKey() = TileServiceKey(tileSpec.componentName, this.identifier)
private data class TileWithUser(val user: UserHandle, val tile: Tile)
+
+ private companion object {
+ const val META_DATA_QUERY_FLAGS =
+ (PackageManager.GET_META_DATA or
+ PackageManager.MATCH_UNINSTALLED_PACKAGES or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
+ PackageManager.MATCH_DIRECT_BOOT_AWARE)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
index 351bba5..10b012d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
@@ -19,10 +19,9 @@
import android.os.UserHandle
import android.service.quicksettings.Tile
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.qs.external.TileServiceManager
import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository
-import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundScope
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
@@ -35,15 +34,13 @@
import kotlinx.coroutines.flow.onEach
/** Manages updates of the [Tile] assigned for the current custom tile. */
-@CustomTileBoundScope
+@QSTileScope
class CustomTileInteractor
@Inject
constructor(
- private val user: UserHandle,
private val defaultsRepository: CustomTileDefaultsRepository,
private val customTileRepository: CustomTileRepository,
- private val tileServiceManager: TileServiceManager,
- @CustomTileBoundScope private val boundScope: CoroutineScope,
+ @QSTileScope private val tileScope: CoroutineScope,
@Background private val backgroundContext: CoroutineContext,
) {
@@ -51,8 +48,7 @@
MutableSharedFlow<Tile>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
/** [Tile] updates. [updateTile] to emit a new one. */
- val tiles: Flow<Tile>
- get() = customTileRepository.getTiles(user)
+ fun getTiles(user: UserHandle): Flow<Tile> = customTileRepository.getTiles(user)
/**
* Current [Tile]
@@ -61,10 +57,14 @@
* the tile hasn't been updated for the current user. Can happen when this is accessed before
* [init] returns.
*/
- val tile: Tile
- get() =
- customTileRepository.getTile(user)
- ?: throw IllegalStateException("Attempt to get a tile for a wrong user")
+ fun getTile(user: UserHandle): Tile =
+ customTileRepository.getTile(user)
+ ?: throw IllegalStateException("Attempt to get a tile for a wrong user")
+
+ /**
+ * True if the tile is toggleable like a switch and false if it operates as a clickable button.
+ */
+ suspend fun isTileToggleable(): Boolean = customTileRepository.isTileToggleable()
/**
* Initializes the repository for the current user. Suspends until it's safe to call [tile]
@@ -73,36 +73,36 @@
* - receive tile update in [updateTile];
* - restoration happened for a persisted tile.
*/
- suspend fun init() {
- launchUpdates()
- customTileRepository.restoreForTheUserIfNeeded(user, tileServiceManager.isActiveTile)
+ suspend fun initForUser(user: UserHandle) {
+ launchUpdates(user)
+ customTileRepository.restoreForTheUserIfNeeded(user, customTileRepository.isTileActive())
// Suspend to make sure it gets the tile from one of the sources: restoration, defaults, or
// tile update.
customTileRepository.getTiles(user).firstOrNull()
}
- private fun launchUpdates() {
+ private fun launchUpdates(user: UserHandle) {
tileUpdates
.onEach {
customTileRepository.updateWithTile(
user,
it,
- tileServiceManager.isActiveTile,
+ customTileRepository.isTileActive(),
)
}
.flowOn(backgroundContext)
- .launchIn(boundScope)
+ .launchIn(tileScope)
defaultsRepository
.defaults(user)
.onEach {
customTileRepository.updateWithDefaults(
user,
it,
- tileServiceManager.isActiveTile,
+ customTileRepository.isTileActive(),
)
}
.flowOn(backgroundContext)
- .launchIn(boundScope)
+ .launchIn(tileScope)
}
/** Updates current [Tile]. Emits a new event in [tiles]. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
index 580c421..ef3df48 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
@@ -22,8 +22,8 @@
/**
* Represents tiles behaviour logic. This ViewModel is a connection between tile view and data
- * layers. All direct inheritors must be added to the [QSTileViewModelInterfaceComplianceTest] class
- * to pass compliance tests.
+ * layers. All direct inheritors must be added to the
+ * [com.android.systemui.qs.tiles.viewmodel.QSTileViewModelTest] class to pass interface tests.
*
* All methods of this view model should be considered running on the main thread. This means no
* synchronous long running operations are permitted in any method.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 1156250..8c3e4a5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -14,30 +14,94 @@
* limitations under the License.
*/
+@file:Suppress("NOTHING_TO_INLINE")
+
package com.android.systemui.scene.shared.flag
-import android.content.Context
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.Flags.sceneContainer
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flag
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.ReleasedFlag
-import com.android.systemui.flags.ResourceBooleanFlag
-import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.Flags.SCENE_CONTAINER_ENABLED
+import com.android.systemui.flags.RefactorFlagUtils
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
-import com.android.systemui.res.R
import dagger.Module
import dagger.Provides
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+
+/** Helper for reading or using the scene container flag state. */
+object SceneContainerFlag {
+ /** The flag description -- not an aconfig flag name */
+ const val DESCRIPTION = "SceneContainerFlag"
+
+ inline val isEnabled
+ get() =
+ SCENE_CONTAINER_ENABLED && // mainStaticFlag
+ sceneContainer() && // mainAconfigFlag
+ keyguardBottomAreaRefactor() &&
+ KeyguardShadeMigrationNssl.isEnabled &&
+ MediaInSceneContainerFlag.isEnabled &&
+ ComposeFacade.isComposeAvailable()
+
+ /**
+ * The main static flag, SCENE_CONTAINER_ENABLED. This is an explicit static flag check that
+ * helps with downstream optimizations (like unused code stripping) in builds where aconfig
+ * flags are still writable. Do not remove!
+ */
+ inline fun getMainStaticFlag() =
+ FlagToken("Flags.SCENE_CONTAINER_ENABLED", SCENE_CONTAINER_ENABLED)
+
+ /** The main aconfig flag. */
+ inline fun getMainAconfigFlag() = FlagToken(FLAG_SCENE_CONTAINER, sceneContainer())
+
+ /** The set of secondary flags which must be enabled for scene container to work properly */
+ inline fun getSecondaryFlags(): Sequence<FlagToken> =
+ sequenceOf(
+ FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()),
+ KeyguardShadeMigrationNssl.token,
+ MediaInSceneContainerFlag.token,
+ )
+
+ /** The full set of requirements for SceneContainer */
+ inline fun getAllRequirements(): Sequence<FlagToken> {
+ val composeRequirement =
+ FlagToken("ComposeFacade.isComposeAvailable()", ComposeFacade.isComposeAvailable())
+ return sequenceOf(getMainStaticFlag(), getMainAconfigFlag()) +
+ getSecondaryFlags() +
+ composeRequirement
+ }
+
+ /** Return all dependencies of this flag in pairs where [Pair.first] depends on [Pair.second] */
+ inline fun getFlagDependencies(): Sequence<Pair<FlagToken, FlagToken>> {
+ val mainStaticFlag = getMainStaticFlag()
+ val mainAconfigFlag = getMainAconfigFlag()
+ return sequence {
+ // The static and aconfig flags should be equal; make them co-dependent
+ yield(mainAconfigFlag to mainStaticFlag)
+ yield(mainStaticFlag to mainAconfigFlag)
+ // all other flags depend on the static flag for brevity
+ } + getSecondaryFlags().map { mainStaticFlag to it }
+ }
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, DESCRIPTION)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, DESCRIPTION)
+}
/**
* Defines interface for classes that can check whether the scene container framework feature is
@@ -52,133 +116,25 @@
fun requirementDescription(): String
}
-class SceneContainerFlagsImpl
-@AssistedInject
-constructor(
- @Application private val context: Context,
- private val featureFlagsClassic: FeatureFlagsClassic,
- @Assisted private val isComposeAvailable: Boolean,
-) : SceneContainerFlags {
-
- companion object {
- @VisibleForTesting
- val classicFlagTokens: List<Flag<Boolean>> =
- listOf(
- Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW,
- )
- }
-
- /** The list of requirements, all must be met for the feature to be enabled. */
- private val requirements =
- listOf(
- AconfigFlagMustBeEnabled(
- flagName = AConfigFlags.FLAG_SCENE_CONTAINER,
- flagValue = sceneContainer(),
- ),
- AconfigFlagMustBeEnabled(
- flagName = AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
- flagValue = keyguardBottomAreaRefactor(),
- ),
- AconfigFlagMustBeEnabled(
- flagName = KeyguardShadeMigrationNssl.FLAG_NAME,
- flagValue = KeyguardShadeMigrationNssl.isEnabled,
- ),
- AconfigFlagMustBeEnabled(
- flagName = MediaInSceneContainerFlag.FLAG_NAME,
- flagValue = MediaInSceneContainerFlag.isEnabled,
- ),
- ) +
- classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } +
- listOf(
- ComposeMustBeAvailable(),
- CompileTimeFlagMustBeEnabled(),
- ResourceConfigMustBeEnabled()
- )
+class SceneContainerFlagsImpl : SceneContainerFlags {
override fun isEnabled(): Boolean {
- // SCENE_CONTAINER_ENABLED is an explicit static flag check that helps with downstream
- // optimizations, e.g., unused code stripping. Do not remove!
- return Flags.SCENE_CONTAINER_ENABLED && requirements.all { it.isMet() }
+ return SceneContainerFlag.isEnabled
}
override fun requirementDescription(): String {
return buildString {
- requirements.forEach { requirement ->
+ SceneContainerFlag.getAllRequirements().forEach { requirement ->
append('\n')
- append(if (requirement.isMet()) " [MET]" else "[NOT MET]")
+ append(if (requirement.isEnabled) " [MET]" else "[NOT MET]")
append(" ${requirement.name}")
}
}
}
-
- private interface Requirement {
- val name: String
-
- fun isMet(): Boolean
- }
-
- private inner class ComposeMustBeAvailable : Requirement {
- override val name = "Jetpack Compose must be available"
-
- override fun isMet(): Boolean {
- return isComposeAvailable
- }
- }
-
- private inner class CompileTimeFlagMustBeEnabled : Requirement {
- override val name = "Flags.SCENE_CONTAINER_ENABLED must be enabled in code"
-
- override fun isMet(): Boolean {
- return Flags.SCENE_CONTAINER_ENABLED
- }
- }
-
- private inner class FlagMustBeEnabled<FlagType : Flag<*>>(
- private val flag: FlagType,
- ) : Requirement {
- override val name = "Flag ${flag.name} must be enabled"
-
- override fun isMet(): Boolean {
- return when (flag) {
- is ResourceBooleanFlag -> featureFlagsClassic.isEnabled(flag)
- is ReleasedFlag -> featureFlagsClassic.isEnabled(flag)
- is UnreleasedFlag -> featureFlagsClassic.isEnabled(flag)
- else -> error("Unsupported flag type ${flag.javaClass}")
- }
- }
- }
-
- private inner class AconfigFlagMustBeEnabled(
- flagName: String,
- private val flagValue: Boolean,
- ) : Requirement {
- override val name: String = "Aconfig flag $flagName must be enabled"
-
- override fun isMet(): Boolean {
- return flagValue
- }
- }
-
- private inner class ResourceConfigMustBeEnabled : Requirement {
- override val name: String = "R.bool.config_sceneContainerFrameworkEnabled must be true"
-
- override fun isMet(): Boolean {
- return context.resources.getBoolean(R.bool.config_sceneContainerFrameworkEnabled)
- }
- }
-
- @AssistedFactory
- interface Factory {
- fun create(isComposeAvailable: Boolean): SceneContainerFlagsImpl
- }
}
@Module
object SceneContainerFlagsModule {
- @Provides
- @SysUISingleton
- fun impl(factory: SceneContainerFlagsImpl.Factory): SceneContainerFlags {
- return factory.create(ComposeFacade.isComposeAvailable())
- }
+ @Provides @SysUISingleton fun impl(): SceneContainerFlags = SceneContainerFlagsImpl()
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
index 5cbea90..7130fa1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -11,8 +11,6 @@
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.constraintlayout.widget.Guideline
import com.android.systemui.res.R
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import javax.inject.Inject
/**
@@ -23,7 +21,6 @@
constructor(
private val workProfileMessageController: WorkProfileMessageController,
private val screenshotDetectionController: ScreenshotDetectionController,
- private val featureFlags: FeatureFlags,
) {
private lateinit var container: ViewGroup
private lateinit var guideline: Guideline
@@ -63,10 +60,8 @@
fun onScreenshotTaken(screenshot: ScreenshotData) {
val workProfileData = workProfileMessageController.onScreenshotTaken(screenshot.userHandle)
- var notifiedApps: List<CharSequence> = listOf()
- if (featureFlags.isEnabled(Flags.SCREENSHOT_DETECTION)) {
- notifiedApps = screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
- }
+ var notifiedApps: List<CharSequence> =
+ screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
// If work profile first run needs to show, bias towards that, otherwise show screenshot
// detection notification if needed.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 95f7c94a..878e6fa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1096,7 +1096,7 @@
}
@Override
- public void onPulseExpansionChanged(boolean expandingChanged) {
+ public void onPulseExpansionAmountChanged(boolean expandingChanged) {
if (mKeyguardBypassController.getBypassEnabled()) {
// Position the notifications while dragging down while pulsing
requestScrollerTopPaddingUpdate(false /* animate */);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 07ce577..3cf468f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -54,8 +54,13 @@
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler;
+import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.log.BouncerLogger;
+import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
import com.android.systemui.statusbar.DragDownHelper;
@@ -64,6 +69,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.gesture.TapGestureDetector;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -76,14 +82,19 @@
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.SystemClock;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.util.Optional;
import java.util.function.Consumer;
import javax.inject.Inject;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
/**
* Controller for {@link NotificationShadeWindowView}.
*/
@@ -149,6 +160,7 @@
};
private final SystemClock mClock;
+ @ExperimentalCoroutinesApi
@Inject
public NotificationShadeWindowViewController(
LockscreenShadeTransitionController transitionController,
@@ -190,7 +202,13 @@
QuickSettingsController quickSettingsController,
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor,
+ Lazy<JavaAdapter> javaAdapter,
+ Lazy<AlternateBouncerViewModel> alternateBouncerViewModel,
+ Lazy<FalsingManager> falsingManager,
+ Lazy<SwipeUpAnywhereGestureHandler> swipeUpAnywhereGestureHandler,
+ Lazy<TapGestureDetector> tapGestureDetector,
+ Lazy<AlternateBouncerUdfpsIconViewModel> alternateBouncerUdfpsIconViewModel) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
@@ -235,6 +253,25 @@
featureFlagsClassic,
selectedUserInteractor);
+ if (DeviceEntryUdfpsRefactor.isEnabled()) {
+ AlternateBouncerViewBinder.bind(
+ mView.findViewById(R.id.alternate_bouncer),
+ alternateBouncerViewModel.get(),
+ falsingManager.get(),
+ swipeUpAnywhereGestureHandler.get(),
+ tapGestureDetector.get(),
+ alternateBouncerUdfpsIconViewModel.get()
+ );
+ javaAdapter.get().alwaysCollectFlow(
+ alternateBouncerViewModel.get().getForcePluginOpen(),
+ forcePluginOpen ->
+ mNotificationShadeWindowController.setForcePluginOpen(
+ forcePluginOpen,
+ alternateBouncerViewModel.get()
+ )
+ );
+ }
+
collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
mLockscreenToDreamingTransition);
collectFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 843a454..984fcad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -27,7 +27,6 @@
import android.app.Notification;
import android.content.Context;
import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -59,6 +58,7 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.NotificationContentDescription;
import com.android.systemui.statusbar.notification.NotificationDozeHelper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
@@ -350,9 +350,22 @@
}
public void setNotification(StatusBarNotification notification) {
- mNotification = notification;
+ CharSequence contentDescription = null;
if (notification != null) {
- setContentDescription(notification.getNotification());
+ contentDescription = NotificationContentDescription
+ .contentDescForNotification(mContext, notification.getNotification());
+ }
+ setNotification(notification, contentDescription);
+ }
+
+ /**
+ * Sets the notification with a pre-set content description.
+ */
+ public void setNotification(@Nullable StatusBarNotification notification,
+ @Nullable CharSequence notificationContentDescription) {
+ mNotification = notification;
+ if (!TextUtils.isEmpty(notificationContentDescription)) {
+ setContentDescription(notificationContentDescription);
}
maybeUpdateIconScaleDimens();
}
@@ -621,15 +634,6 @@
mNumberBackground.setBounds(w-dw, h-dh, w, h);
}
- private void setContentDescription(Notification notification) {
- if (notification != null) {
- String d = contentDescForNotification(mContext, notification);
- if (!TextUtils.isEmpty(d)) {
- setContentDescription(d);
- }
- }
- }
-
@Override
public String toString() {
return "StatusBarIconView("
@@ -647,35 +651,6 @@
return mSlot;
}
-
- public static String contentDescForNotification(Context c, Notification n) {
- String appName = "";
- try {
- Notification.Builder builder = Notification.Builder.recoverBuilder(c, n);
- appName = builder.loadHeaderAppName();
- } catch (RuntimeException e) {
- Log.e(TAG, "Unable to recover builder", e);
- // Trying to get the app name from the app info instead.
- ApplicationInfo appInfo = n.extras.getParcelable(
- Notification.EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
- if (appInfo != null) {
- appName = String.valueOf(appInfo.loadLabel(c.getPackageManager()));
- }
- }
-
- CharSequence title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
- CharSequence text = n.extras.getCharSequence(Notification.EXTRA_TEXT);
- CharSequence ticker = n.tickerText;
-
- // Some apps just put the app name into the title
- CharSequence titleOrText = TextUtils.equals(title, appName) ? text : title;
-
- CharSequence desc = !TextUtils.isEmpty(titleOrText) ? titleOrText
- : !TextUtils.isEmpty(ticker) ? ticker : "";
-
- return c.getString(R.string.accessibility_desc_notification_icon, appName, desc);
- }
-
/**
* Set the color that is used to draw decoration like the overflow dot. This will not be applied
* to the drawable.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt
new file mode 100644
index 0000000..e7012ea51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+@file:JvmName("NotificationContentDescription")
+
+package com.android.systemui.statusbar.notification
+
+import android.app.Notification
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.text.TextUtils
+import android.util.Log
+import androidx.annotation.MainThread
+import com.android.systemui.res.R
+
+/**
+ * Returns accessibility content description for a given notification.
+ *
+ * NOTE: This is a relatively slow call.
+ */
+@MainThread
+fun contentDescForNotification(c: Context, n: Notification?): CharSequence {
+ var appName = ""
+ try {
+ val builder = Notification.Builder.recoverBuilder(c, n)
+ appName = builder.loadHeaderAppName()
+ } catch (e: RuntimeException) {
+ Log.e("ContentDescription", "Unable to recover builder", e)
+ // Trying to get the app name from the app info instead.
+ val appInfo =
+ n?.extras?.getParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo::class.java
+ )
+ if (appInfo != null) {
+ appName = appInfo.loadLabel(c.packageManager).toString()
+ }
+ }
+ val title = n?.extras?.getCharSequence(Notification.EXTRA_TITLE)
+ val text = n?.extras?.getCharSequence(Notification.EXTRA_TEXT)
+ val ticker = n?.tickerText
+
+ // Some apps just put the app name into the title
+ val titleOrText = if (TextUtils.equals(title, appName)) text else title
+ val desc =
+ if (!TextUtils.isEmpty(titleOrText)) titleOrText
+ else if (!TextUtils.isEmpty(ticker)) ticker else ""
+ return c.getString(R.string.accessibility_desc_notification_icon, appName, desc)
+}
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 8d1e8d0..0c67279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -20,9 +20,9 @@
import android.view.animation.Interpolator
import androidx.annotation.VisibleForTesting
import androidx.core.animation.ObjectAnimator
-import com.android.systemui.Dumpable
import com.android.app.animation.Interpolators
import com.android.app.animation.InterpolatorsAndroidX
+import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -31,6 +31,7 @@
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.phone.DozeParameters
@@ -206,8 +207,15 @@
val nowExpanding = isPulseExpanding()
val changed = nowExpanding != pulseExpanding
pulseExpanding = nowExpanding
- for (listener in wakeUpListeners) {
- listener.onPulseExpansionChanged(changed)
+ if (!NotificationIconContainerRefactor.isEnabled) {
+ for (listener in wakeUpListeners) {
+ listener.onPulseExpansionAmountChanged(changed)
+ }
+ }
+ if (changed) {
+ for (listener in wakeUpListeners) {
+ listener.onPulseExpandingChanged(pulseExpanding)
+ }
}
}
}
@@ -620,13 +628,20 @@
*
* @param expandingChanged if the user has started or stopped expanding
*/
- fun onPulseExpansionChanged(expandingChanged: Boolean) {}
+ @Deprecated(
+ message = "Use onPulseExpandedChanged instead.",
+ replaceWith = ReplaceWith("onPulseExpandedChanged"),
+ )
+ fun onPulseExpansionAmountChanged(expandingChanged: Boolean) {}
/**
* Called when the animator started by [scheduleDelayedDozeAmountAnimation] begins running
* after the start delay, or after it ends/is cancelled.
*/
fun onDelayedDozeAmountAnimationRunning(running: Boolean) {}
+
+ /** Called whenever a pulse has started or stopped expanding. */
+ fun onPulseExpandingChanged(isPulseExpanding: Boolean) {}
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index 3e9c6fb..3b48b39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -3,10 +3,8 @@
import android.util.FloatProperty
import android.view.View
import androidx.annotation.FloatRange
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.RefactorFlag
import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import kotlin.math.abs
@@ -42,13 +40,13 @@
/** Current top corner in pixel, based on [topRoundness] and [maxRadius] */
val topCornerRadius: Float
get() =
- if (roundableState.newHeadsUpAnim.isEnabled) roundableState.topCornerRadius
+ if (NotificationsImprovedHunAnimation.isEnabled) roundableState.topCornerRadius
else topRoundness * maxRadius
/** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */
val bottomCornerRadius: Float
get() =
- if (roundableState.newHeadsUpAnim.isEnabled) roundableState.bottomCornerRadius
+ if (NotificationsImprovedHunAnimation.isEnabled) roundableState.bottomCornerRadius
else bottomRoundness * maxRadius
/** Get and update the current radii */
@@ -318,13 +316,10 @@
internal val targetView: View,
private val roundable: Roundable,
maxRadius: Float,
- featureFlags: FeatureFlags? = null
) {
internal var maxRadius = maxRadius
private set
- internal val newHeadsUpAnim = RefactorFlag.forView(Flags.IMPROVED_HUN_ANIMATIONS, featureFlags)
-
/** Animatable for top roundness */
private val topAnimatable = topAnimatable(roundable)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
index cf03d1c..2cc1403 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
@@ -62,8 +62,8 @@
override val isPulseExpanding: Flow<Boolean> = conflatedCallbackFlow {
val listener =
object : NotificationWakeUpCoordinator.WakeUpListener {
- override fun onPulseExpansionChanged(expandingChanged: Boolean) {
- trySend(expandingChanged)
+ override fun onPulseExpandingChanged(isPulseExpanding: Boolean) {
+ trySend(isPulseExpanding)
}
}
trySend(wakeUpCoordinator.isPulseExpanding())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
index afc123f..319b499 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
@@ -20,6 +20,7 @@
import android.content.Context
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.contentDescForNotification
import javax.inject.Inject
/**
@@ -36,6 +37,6 @@
}
fun getIconContentDescription(n: Notification): CharSequence {
- return StatusBarIconView.contentDescForNotification(context, n)
+ return contentDescForNotification(context, n)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 9e8654a..76e5fd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -26,15 +26,15 @@
import android.util.Log
import android.view.View
import android.widget.ImageView
+import com.android.app.tracing.traceSection
import com.android.internal.statusbar.StatusBarIcon
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.app.tracing.traceSection
import javax.inject.Inject
/**
@@ -112,15 +112,6 @@
aodIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
aodIcon.setIncreasedSize(true)
- // Construct the centered icon view.
- val centeredIcon = if (entry.sbn.notification.isMediaNotification) {
- iconBuilder.createIconView(entry).apply {
- scaleType = ImageView.ScaleType.CENTER_INSIDE
- }
- } else {
- null
- }
-
// Set the icon views' icons
val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
@@ -128,10 +119,7 @@
setIcon(entry, normalIconDescriptor, sbIcon)
setIcon(entry, sensitiveIconDescriptor, shelfIcon)
setIcon(entry, sensitiveIconDescriptor, aodIcon)
- if (centeredIcon != null) {
- setIcon(entry, normalIconDescriptor, centeredIcon)
- }
- entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, centeredIcon, entry.icons)
+ entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, entry.icons)
} catch (e: InflationException) {
entry.icons = IconPack.buildEmptyPack(entry.icons)
throw e
@@ -153,24 +141,22 @@
entry.icons.peopleAvatarDescriptor = null
val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
+ val notificationContentDescription = entry.sbn.notification?.let {
+ iconBuilder.getIconContentDescription(it)
+ }
entry.icons.statusBarIcon?.let {
- it.notification = entry.sbn
+ it.setNotification(entry.sbn, notificationContentDescription)
setIcon(entry, normalIconDescriptor, it)
}
entry.icons.shelfIcon?.let {
- it.notification = entry.sbn
+ it.setNotification(entry.sbn, notificationContentDescription)
setIcon(entry, normalIconDescriptor, it)
}
entry.icons.aodIcon?.let {
- it.notification = entry.sbn
- setIcon(entry, sensitiveIconDescriptor, it)
- }
-
- entry.icons.centeredIcon?.let {
- it.notification = entry.sbn
+ it.setNotification(entry.sbn, notificationContentDescription)
setIcon(entry, sensitiveIconDescriptor, it)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
index 054e381..442c097 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
@@ -31,7 +31,6 @@
@Nullable private final StatusBarIconView mStatusBarIcon;
@Nullable private final StatusBarIconView mShelfIcon;
@Nullable private final StatusBarIconView mAodIcon;
- @Nullable private final StatusBarIconView mCenteredIcon;
@Nullable private StatusBarIcon mSmallIconDescriptor;
@Nullable private StatusBarIcon mPeopleAvatarDescriptor;
@@ -43,7 +42,7 @@
* haven't been inflated yet or there was an error while inflating them).
*/
public static IconPack buildEmptyPack(@Nullable IconPack fromSource) {
- return new IconPack(false, null, null, null, null, fromSource);
+ return new IconPack(false, null, null, null, fromSource);
}
/**
@@ -53,9 +52,8 @@
@NonNull StatusBarIconView statusBarIcon,
@NonNull StatusBarIconView shelfIcon,
@NonNull StatusBarIconView aodIcon,
- @Nullable StatusBarIconView centeredIcon,
@Nullable IconPack source) {
- return new IconPack(true, statusBarIcon, shelfIcon, aodIcon, centeredIcon, source);
+ return new IconPack(true, statusBarIcon, shelfIcon, aodIcon, source);
}
private IconPack(
@@ -63,12 +61,10 @@
@Nullable StatusBarIconView statusBarIcon,
@Nullable StatusBarIconView shelfIcon,
@Nullable StatusBarIconView aodIcon,
- @Nullable StatusBarIconView centeredIcon,
@Nullable IconPack source) {
mAreIconsAvailable = areIconsAvailable;
mStatusBarIcon = statusBarIcon;
mShelfIcon = shelfIcon;
- mCenteredIcon = centeredIcon;
mAodIcon = aodIcon;
if (source != null) {
mIsImportantConversation = source.mIsImportantConversation;
@@ -91,11 +87,6 @@
return mShelfIcon;
}
- @Nullable
- public StatusBarIconView getCenteredIcon() {
- return mCenteredIcon;
- }
-
/** The version of the icon that's shown when pulsing (in AOD). */
@Nullable
public StatusBarIconView getAodIcon() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt
new file mode 100644
index 0000000..d7c29f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerAlwaysOnDisplayViewBinder.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.icon.ui.viewbinder
+
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.ui.SystemBarUtilsState
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/** Binds a [NotificationIconContainer] to a [NotificationIconContainerAlwaysOnDisplayViewModel]. */
+class NotificationIconContainerAlwaysOnDisplayViewBinder
+@Inject
+constructor(
+ private val viewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
+ private val keyguardRootViewModel: KeyguardRootViewModel,
+ private val configuration: ConfigurationState,
+ private val failureTracker: StatusBarIconViewBindingFailureTracker,
+ private val screenOffAnimationController: ScreenOffAnimationController,
+ private val systemBarUtilsState: SystemBarUtilsState,
+ private val viewStore: AlwaysOnDisplayNotificationIconViewStore,
+) {
+ fun bindWhileAttached(view: NotificationIconContainer): DisposableHandle {
+ return view.repeatWhenAttached {
+ lifecycleScope.launch {
+ launch {
+ NotificationIconContainerViewBinder.bind(
+ view = view,
+ viewModel = viewModel,
+ configuration = configuration,
+ systemBarUtilsState = systemBarUtilsState,
+ failureTracker = failureTracker,
+ viewStore = viewStore,
+ )
+ }
+ launch {
+ KeyguardRootViewBinder.bindAodNotifIconVisibility(
+ view = view,
+ isVisible = keyguardRootViewModel.isNotifIconContainerVisible,
+ configuration = configuration,
+ screenOffAnimationController = screenOffAnimationController,
+ )
+ }
+ }
+ }
+ }
+}
+
+/** [IconViewStore] for the always-on display. */
+class AlwaysOnDisplayNotificationIconViewStore
+@Inject
+constructor(notifCollection: NotifCollection) :
+ IconViewStore by (notifCollection.iconViewStoreBy { it.aodIcon })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
new file mode 100644
index 0000000..783488af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerShelfViewBinder.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.icon.ui.viewbinder
+
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.bindIcons
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.ui.SystemBarUtilsState
+import javax.inject.Inject
+
+/** Binds a [NotificationIconContainer] to a [NotificationIconContainerShelfViewModel]. */
+class NotificationIconContainerShelfViewBinder
+@Inject
+constructor(
+ private val viewModel: NotificationIconContainerShelfViewModel,
+ private val configuration: ConfigurationState,
+ private val systemBarUtilsState: SystemBarUtilsState,
+ private val failureTracker: StatusBarIconViewBindingFailureTracker,
+ private val viewStore: ShelfNotificationIconViewStore,
+) {
+ suspend fun bind(view: NotificationIconContainer) {
+ viewModel.icons.bindIcons(
+ view,
+ configuration,
+ systemBarUtilsState,
+ notifyBindingFailures = { failureTracker.shelfFailures = it },
+ viewStore,
+ )
+ }
+}
+
+/** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */
+class ShelfNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
+ IconViewStore by (notifCollection.iconViewStoreBy { it.shelfIcon })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
new file mode 100644
index 0000000..8e089b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerStatusBarViewBinder.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.icon.ui.viewbinder
+
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.ui.SystemBarUtilsState
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/** Binds a [NotificationIconContainer] to a [NotificationIconContainerStatusBarViewModel]. */
+class NotificationIconContainerStatusBarViewBinder
+@Inject
+constructor(
+ private val viewModel: NotificationIconContainerStatusBarViewModel,
+ private val configuration: ConfigurationState,
+ private val systemBarUtilsState: SystemBarUtilsState,
+ private val failureTracker: StatusBarIconViewBindingFailureTracker,
+ private val viewStore: StatusBarNotificationIconViewStore,
+) {
+ fun bindWhileAttached(view: NotificationIconContainer): DisposableHandle {
+ return view.repeatWhenAttached {
+ lifecycleScope.launch {
+ NotificationIconContainerViewBinder.bind(
+ view = view,
+ viewModel = viewModel,
+ configuration = configuration,
+ systemBarUtilsState = systemBarUtilsState,
+ failureTracker = failureTracker,
+ viewStore = viewStore,
+ )
+ }
+ }
+ }
+}
+
+/** [IconViewStore] for the status bar. */
+class StatusBarNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
+ IconViewStore by (notifCollection.iconViewStoreBy { it.statusBarIcon })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index e1e30e1..8fe0022 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -35,7 +35,6 @@
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData.LimitType
@@ -45,7 +44,6 @@
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
-import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@@ -56,42 +54,6 @@
/** Binds a view-model to a [NotificationIconContainer]. */
object NotificationIconContainerViewBinder {
- @JvmStatic
- fun bindWhileAttached(
- view: NotificationIconContainer,
- viewModel: NotificationIconContainerShelfViewModel,
- configuration: ConfigurationState,
- systemBarUtilsState: SystemBarUtilsState,
- failureTracker: StatusBarIconViewBindingFailureTracker,
- viewStore: IconViewStore,
- ): DisposableHandle {
- return view.repeatWhenAttached {
- lifecycleScope.launch {
- viewModel.icons.bindIcons(
- view,
- configuration,
- systemBarUtilsState,
- notifyBindingFailures = { failureTracker.shelfFailures = it },
- viewStore,
- )
- }
- }
- }
-
- @JvmStatic
- fun bindWhileAttached(
- view: NotificationIconContainer,
- viewModel: NotificationIconContainerStatusBarViewModel,
- configuration: ConfigurationState,
- systemBarUtilsState: SystemBarUtilsState,
- failureTracker: StatusBarIconViewBindingFailureTracker,
- viewStore: IconViewStore,
- ): DisposableHandle =
- view.repeatWhenAttached {
- lifecycleScope.launch {
- bind(view, viewModel, configuration, systemBarUtilsState, failureTracker, viewStore)
- }
- }
suspend fun bind(
view: NotificationIconContainer,
@@ -215,7 +177,7 @@
* given `iconKey`. The parent [Job] of this coroutine will be cancelled automatically when the
* view is to be unbound.
*/
- private suspend fun Flow<NotificationIconsViewData>.bindIcons(
+ suspend fun Flow<NotificationIconsViewData>.bindIcons(
view: NotificationIconContainer,
configuration: ConfigurationState,
systemBarUtilsState: SystemBarUtilsState,
@@ -377,24 +339,14 @@
}
@ColorInt private const val DEFAULT_AOD_ICON_COLOR = Color.WHITE
- private const val TAG = "NotifIconContainerViewBinder"
+ private const val TAG = "NotifIconContainerViewBinder"
}
-/** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */
-class ShelfNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
- IconViewStore by (notifCollection.iconViewStoreBy { it.shelfIcon })
-
-/** [IconViewStore] for the always-on display. */
-class AlwaysOnDisplayNotificationIconViewStore
-@Inject
-constructor(notifCollection: NotifCollection) :
- IconViewStore by (notifCollection.iconViewStoreBy { it.aodIcon })
-
-/** [IconViewStore] for the status bar. */
-class StatusBarNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) :
- IconViewStore by (notifCollection.iconViewStoreBy { it.statusBarIcon })
-
-private fun NotifCollection.iconViewStoreBy(block: (IconPack) -> StatusBarIconView?) =
+/**
+ * Convenience builder for [IconViewStore] that uses [block] to extract the relevant
+ * [StatusBarIconView] from an [IconPack] stored inside of the [NotifCollection].
+ */
+fun NotifCollection.iconViewStoreBy(block: (IconPack) -> StatusBarIconView?) =
IconViewStore { key ->
getEntry(key)?.icons?.let(block)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
index 2624363..db33f92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
@@ -17,12 +17,17 @@
package com.android.systemui.statusbar.notification.interruption
import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the visual interruptions refactor flag state. */
object VisualInterruptionRefactor {
const val FLAG_NAME = Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
/** Whether the refactor is enabled */
@JvmStatic
inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 4fe05ec..fca527f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -31,7 +31,6 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
import com.android.app.animation.Interpolators;
import com.android.internal.jank.InteractionJankMonitor;
@@ -44,6 +43,7 @@
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.util.DumpUtilsKt;
@@ -67,7 +67,8 @@
* The content of the view should start showing at animation progress value of
* #ALPHA_APPEAR_START_FRACTION.
*/
- private static final float ALPHA_APPEAR_START_FRACTION = .4f;
+
+ private static final float ALPHA_APPEAR_START_FRACTION = .7f;
/**
* The content should show fully with progress at #ALPHA_APPEAR_END_FRACTION
* The start of the animation is at #ALPHA_APPEAR_START_FRACTION
@@ -86,9 +87,7 @@
*/
private boolean mActivated;
- private final Interpolator mSlowOutFastInInterpolator;
private Interpolator mCurrentAppearInterpolator;
-
NotificationBackgroundView mBackgroundNormal;
private float mAnimationTranslationY;
private boolean mDrawingAppearAnimation;
@@ -116,7 +115,6 @@
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
- mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f);
setClipChildren(false);
setClipToPadding(false);
updateColors();
@@ -400,12 +398,16 @@
mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN;
targetValue = 1.0f;
} else {
- mCurrentAppearInterpolator = mSlowOutFastInInterpolator;
+ mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE;
targetValue = 0.0f;
}
mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
targetValue);
- mAppearAnimator.setInterpolator(Interpolators.LINEAR);
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ mAppearAnimator.setInterpolator(mCurrentAppearInterpolator);
+ } else {
+ mAppearAnimator.setInterpolator(Interpolators.LINEAR);
+ }
mAppearAnimator.setDuration(
(long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
mAppearAnimator.addUpdateListener(animation -> {
@@ -502,8 +504,9 @@
}
private void updateAppearRect() {
- float interpolatedFraction = mCurrentAppearInterpolator.getInterpolation(
- mAppearAnimationFraction);
+ float interpolatedFraction =
+ NotificationsImprovedHunAnimation.isEnabled() ? mAppearAnimationFraction
+ : mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY;
final int actualHeight = getActualHeight();
float bottom = actualHeight * interpolatedFraction;
@@ -524,6 +527,7 @@
}
private float getInterpolatedAppearAnimationFraction() {
+
if (mAppearAnimationFraction >= 0) {
return mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
}
@@ -569,7 +573,7 @@
@Override
public float getTopCornerRadius() {
- if (mImprovedHunAnimation.isEnabled()) {
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
return super.getTopCornerRadius();
}
@@ -579,7 +583,7 @@
@Override
public float getBottomCornerRadius() {
- if (mImprovedHunAnimation.isEnabled()) {
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
return super.getBottomCornerRadius();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 2a3e69b..aefd348 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -28,10 +28,9 @@
import android.view.View;
import android.view.ViewOutlineProvider;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.RoundableState;
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
import com.android.systemui.util.DumpUtilsKt;
@@ -49,8 +48,6 @@
private float mOutlineAlpha = -1f;
private boolean mAlwaysRoundBothCorners;
private Path mTmpPath = new Path();
- protected final RefactorFlag mImprovedHunAnimation =
- RefactorFlag.forView(Flags.IMPROVED_HUN_ANIMATIONS);
/**
* {@code false} if the children views of the {@link ExpandableOutlineView} are translated when
@@ -126,7 +123,7 @@
return EMPTY_PATH;
}
float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
- if (!mImprovedHunAnimation.isEnabled() && (topRadius + bottomRadius > height)) {
+ if (!NotificationsImprovedHunAnimation.isEnabled() && (topRadius + bottomRadius > height)) {
float overShoot = topRadius + bottomRadius - height;
float currentTopRoundness = getTopRoundness();
float currentBottomRoundness = getBottomRoundness();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt
index 24e7f05..9556c2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.row.shared
import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the async hybrid view inflation flag state. */
@@ -24,6 +25,10 @@
object AsyncHybridViewInflation {
const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
/** Is async hybrid (single-line) view inflation enabled */
@JvmStatic
inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt
new file mode 100644
index 0000000..16d35fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notifications improved hun animation flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationsImprovedHunAnimation {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_IMPROVED_HUN_ANIMATION
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationsImprovedHunAnimation()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index 699e140..5ab4d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -16,60 +16,38 @@
package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.NotificationShelf
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
import com.android.systemui.statusbar.phone.NotificationIconAreaController
-import com.android.systemui.statusbar.ui.SystemBarUtilsState
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
/** Binds a [NotificationShelf] to its [view model][NotificationShelfViewModel]. */
object NotificationShelfViewBinder {
- fun bind(
+ suspend fun bind(
shelf: NotificationShelf,
viewModel: NotificationShelfViewModel,
- configuration: ConfigurationState,
- systemBarUtilsState: SystemBarUtilsState,
falsingManager: FalsingManager,
- iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
+ nicBinder: NotificationIconContainerShelfViewBinder,
notificationIconAreaController: NotificationIconAreaController,
- shelfIconViewStore: ShelfNotificationIconViewStore,
- ) {
+ ): Unit = coroutineScope {
ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
shelf.apply {
if (NotificationIconContainerRefactor.isEnabled) {
- NotificationIconContainerViewBinder.bindWhileAttached(
- shelfIcons,
- viewModel.icons,
- configuration,
- systemBarUtilsState,
- iconViewBindingFailureTracker,
- shelfIconViewStore,
- )
+ launch { nicBinder.bind(shelfIcons) }
} else {
notificationIconAreaController.setShelfIcons(shelfIcons)
}
- repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.canModifyColorOfNotifications.collect(
- ::setCanModifyColorOfNotifications
- )
- }
- launch { viewModel.isClickable.collect(::setCanInteract) }
- registerViewListenersWhileAttached(shelf, viewModel)
- }
+ launch {
+ viewModel.canModifyColorOfNotifications.collect(::setCanModifyColorOfNotifications)
}
+ launch { viewModel.isClickable.collect(::setCanInteract) }
+ registerViewListenersWhileAttached(shelf, viewModel)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
index 64b5b62c..5ca8b53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -18,7 +18,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.NotificationShelf
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
import javax.inject.Inject
@@ -32,7 +31,6 @@
constructor(
private val interactor: NotificationShelfInteractor,
activatableViewModel: ActivatableNotificationViewModel,
- val icons: NotificationIconContainerShelfViewModel,
) : ActivatableNotificationViewModel by activatableViewModel {
/** Is the shelf allowed to be clickable when it has content? */
val isClickable: Flow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 3bbdfd1..62fdc29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -94,9 +94,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.res.R;
-import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.TouchLogger;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
@@ -109,12 +107,12 @@
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
-import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -146,8 +144,6 @@
private static final String TAG = "StackScroller";
private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
- // Delay in milli-seconds before shade closes for clear all.
- private static final int DELAY_BEFORE_SHADE_CLOSE = 200;
private boolean mShadeNeedsToClose = false;
@VisibleForTesting
@@ -319,7 +315,7 @@
}
};
private NotificationStackScrollLogger mLogger;
- private NotificationsController mNotificationsController;
+ private Runnable mResetUserExpandedStatesRunnable;
private ActivityStarter mActivityStarter;
private final int[] mTempInt2 = new int[2];
private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
@@ -482,7 +478,7 @@
private final Rect mTmpRect = new Rect();
private ClearAllListener mClearAllListener;
private ClearAllAnimationListener mClearAllAnimationListener;
- private ShadeController mShadeController;
+ private Runnable mClearAllFinishedWhilePanelExpandedRunnable;
private Consumer<Boolean> mOnStackYChanged;
private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
@@ -3328,8 +3324,10 @@
logHunAnimationSkipped(row, "row has no viewState");
continue;
}
+ boolean shouldHunAppearFromTheBottom =
+ mStackScrollAlgorithm.shouldHunAppearFromBottom(mAmbientState, viewState);
if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
- if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
+ if (pinnedAndClosed || shouldHunAppearFromTheBottom) {
// Our custom add animation
type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
} else {
@@ -3341,6 +3339,11 @@
}
AnimationEvent event = new AnimationEvent(row, type);
event.headsUpFromBottom = onBottom;
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ // TODO(b/283084712) remove this with the flag and update the HUN filters at
+ // creation
+ event.filter.animateHeight = false;
+ }
mAnimationEvents.add(event);
if (SPEW) {
Log.v(TAG, "Generating HUN animation event: "
@@ -3355,11 +3358,6 @@
mAddedHeadsUpChildren.clear();
}
- private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
- return viewState.getYTranslation() + viewState.height
- >= mAmbientState.getMaxHeadsUpTranslation();
- }
-
private void generateGroupExpansionEvent() {
// Generate a group expansion/collapsing event if there is such a group at all
if (mExpandedGroupView != null) {
@@ -4114,7 +4112,7 @@
mAmbientState.setExpansionChanging(false);
if (!mIsExpanded) {
resetScrollPosition();
- mNotificationsController.resetUserExpandedStates();
+ mResetUserExpandedStatesRunnable.run();
clearTemporaryViews();
clearUserLockedViews();
resetAllSwipeState();
@@ -4316,21 +4314,12 @@
if (mShadeNeedsToClose) {
mShadeNeedsToClose = false;
if (mIsExpanded) {
- collapseShadeDelayed();
+ mClearAllFinishedWhilePanelExpandedRunnable.run();
}
}
}
}
- private void collapseShadeDelayed() {
- postDelayed(
- () -> {
- mShadeController.animateCollapseShade(
- CommandQueue.FLAG_EXCLUDE_NONE);
- },
- DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
- }
-
private void clearHeadsUpDisappearRunning() {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
@@ -4747,8 +4736,8 @@
return max + getStackTranslation();
}
- public void setNotificationsController(NotificationsController notificationsController) {
- this.mNotificationsController = notificationsController;
+ public void setResetUserExpandedStatesRunnable(Runnable runnable) {
+ this.mResetUserExpandedStatesRunnable = runnable;
}
public void setActivityStarter(ActivityStarter activityStarter) {
@@ -4932,7 +4921,9 @@
*/
public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
+ mStackScrollAlgorithm.setHeadsUpAppearHeightBottom(height);
mStateAnimator.setHeadsUpAppearHeightBottom(height);
+ mStateAnimator.setStackTopMargin(mAmbientState.getStackTopMargin());
requestChildrenUpdate();
}
@@ -5681,8 +5672,8 @@
mFooterClearAllListener = listener;
}
- void setShadeController(ShadeController shadeController) {
- mShadeController = shadeController;
+ void setClearAllFinishedWhilePanelExpandedRunnable(Runnable runnable) {
+ mClearAllFinishedWhilePanelExpandedRunnable = runnable;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index e6315fd..7c7d943 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -81,6 +81,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -152,6 +153,8 @@
private static final String TAG = "StackScrollerController";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final String HIGH_PRIORITY = "high_priority";
+ /** Delay in milli-seconds before shade closes for clear all. */
+ private static final int DELAY_BEFORE_SHADE_CLOSE = 200;
private final boolean mAllowLongPress;
private final NotificationGutsManager mNotificationGutsManager;
@@ -754,7 +757,7 @@
mView.setController(this);
mView.setLogger(mLogger);
mView.setTouchHandler(new TouchHandler());
- mView.setNotificationsController(mNotificationsController);
+ mView.setResetUserExpandedStatesRunnable(mNotificationsController::resetUserExpandedStates);
mView.setActivityStarter(mActivityStarter);
mView.setClearAllAnimationListener(this::onAnimationEnd);
mView.setClearAllListener((selection) -> mUiEventLogger.log(
@@ -770,7 +773,11 @@
mView.setIsRemoteInputActive(active);
}
});
- mView.setShadeController(mShadeController);
+ mView.setClearAllFinishedWhilePanelExpandedRunnable(()-> {
+ final Runnable doCollapseRunnable = () ->
+ mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
+ mView.postDelayed(doCollapseRunnable, /* delayMillis = */ DELAY_BEFORE_SHADE_CLOSE);
+ });
mDumpManager.registerDumpable(mView);
mKeyguardBypassController.registerOnBypassStateChangedListener(
@@ -852,7 +859,7 @@
mGroupExpansionManager.registerGroupExpansionChangeListener(
(changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded));
- mViewBinder.bind(mView, this);
+ mViewBinder.bindWhileAttached(mView, this);
if (!FooterViewRefactor.isEnabled()) {
collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 06ca9a5..c4e6b909 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -37,6 +37,7 @@
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import java.util.ArrayList;
import java.util.List;
@@ -66,12 +67,15 @@
private boolean mClipNotificationScrollToTop;
@VisibleForTesting
float mHeadsUpInset;
+ @VisibleForTesting
+ float mHeadsUpAppearStartAboveScreen;
private int mPinnedZTranslationExtra;
private float mNotificationScrimPadding;
private int mMarginBottom;
private float mQuickQsOffsetHeight;
private float mSmallCornerRadius;
private float mLargeCornerRadius;
+ private int mHeadsUpAppearHeightBottom;
public StackScrollAlgorithm(
Context context,
@@ -94,6 +98,8 @@
int statusBarHeight = SystemBarUtils.getStatusBarHeight(context);
mHeadsUpInset = statusBarHeight + res.getDimensionPixelSize(
R.dimen.heads_up_status_bar_padding);
+ mHeadsUpAppearStartAboveScreen = res.getDimensionPixelSize(
+ R.dimen.heads_up_appear_y_above_screen);
mPinnedZTranslationExtra = res.getDimensionPixelSize(
R.dimen.heads_up_pinned_elevation);
mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
@@ -221,6 +227,25 @@
return getExpansionFractionWithoutShelf(mTempAlgorithmState, ambientState);
}
+ public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
+ mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
+ }
+
+ /**
+ * If the QuickSettings is showing full screen, we want to animate the HeadsUp Notifications
+ * from the bottom of the screen.
+ *
+ * @param ambientState Current ambient state.
+ * @param viewState The state of the HUN that is being queried to appear from the bottom.
+ *
+ * @return true if the HeadsUp Notifications should appear from the bottom
+ */
+ public boolean shouldHunAppearFromBottom(AmbientState ambientState,
+ ExpandableViewState viewState) {
+ return viewState.getYTranslation() + viewState.height
+ >= ambientState.getMaxHeadsUpTranslation();
+ }
+
public static void log(String s) {
if (DEBUG) {
android.util.Log.i(TAG, s);
@@ -793,10 +818,16 @@
}
}
if (row.isPinned()) {
- // Make sure row yTranslation is at maximum the HUN yTranslation,
- // which accounts for AmbientState.stackTopMargin in split-shade.
- childState.setYTranslation(
- Math.max(childState.getYTranslation(), headsUpTranslation));
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ // Make sure row yTranslation is at the HUN yTranslation,
+ // which accounts for AmbientState.stackTopMargin in split-shade.
+ childState.setYTranslation(headsUpTranslation);
+ } else {
+ // Make sure row yTranslation is at maximum the HUN yTranslation,
+ // which accounts for AmbientState.stackTopMargin in split-shade.
+ childState.setYTranslation(
+ Math.max(childState.getYTranslation(), headsUpTranslation));
+ }
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
childState.hidden = false;
ExpandableViewState topState =
@@ -819,10 +850,22 @@
}
}
if (row.isHeadsUpAnimatingAway()) {
- // Make sure row yTranslation is at maximum the HUN yTranslation,
- // which accounts for AmbientState.stackTopMargin in split-shade.
- childState.setYTranslation(
- Math.max(childState.getYTranslation(), headsUpTranslation));
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ if (shouldHunAppearFromBottom(ambientState, childState)) {
+ // move to the bottom of the screen
+ childState.setYTranslation(
+ mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen);
+ } else {
+ // move to the top of the screen
+ childState.setYTranslation(-ambientState.getStackTopMargin()
+ - mHeadsUpAppearStartAboveScreen);
+ }
+ } else {
+ // Make sure row yTranslation is at maximum the HUN yTranslation,
+ // which accounts for AmbientState.stackTopMargin in split-shade.
+ childState.setYTranslation(
+ Math.max(childState.getYTranslation(), headsUpTranslation));
+ }
// keep it visible for the animation
childState.hidden = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index e94258f..a3e0941 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK;
@@ -26,6 +27,7 @@
import android.view.View;
import com.android.app.animation.Interpolators;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardSliceView;
import com.android.systemui.res.R;
import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -33,6 +35,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import java.util.ArrayList;
import java.util.HashSet;
@@ -68,6 +71,8 @@
private final int mGoToFullShadeAppearingTranslation;
private final int mPulsingAppearingTranslation;
+ @VisibleForTesting
+ float mHeadsUpAppearStartAboveScreen;
private final ExpandableViewState mTmpState = new ExpandableViewState();
private final AnimationProperties mAnimationProperties;
public NotificationStackScrollLayout mHostLayout;
@@ -85,21 +90,23 @@
private ValueAnimator mTopOverScrollAnimator;
private ValueAnimator mBottomOverScrollAnimator;
private int mHeadsUpAppearHeightBottom;
+ private int mStackTopMargin;
private boolean mShadeExpanded;
private ArrayList<ExpandableView> mTransientViewsToRemove = new ArrayList<>();
private NotificationShelf mShelf;
- private float mStatusBarIconLocation;
- private int[] mTmpLocation = new int[2];
private StackStateLogger mLogger;
public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
mHostLayout = hostLayout;
+ // TODO(b/317061579) reload on configuration changes
mGoToFullShadeAppearingTranslation =
hostLayout.getContext().getResources().getDimensionPixelSize(
R.dimen.go_to_full_shade_appearing_translation);
mPulsingAppearingTranslation =
hostLayout.getContext().getResources().getDimensionPixelSize(
R.dimen.pulsing_notification_appear_translation);
+ mHeadsUpAppearStartAboveScreen = hostLayout.getContext().getResources()
+ .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
mAnimationProperties = new AnimationProperties() {
@Override
public AnimationFilter getAnimationFilter() {
@@ -455,8 +462,37 @@
.AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
ExpandableNotificationRow row = (ExpandableNotificationRow) event.mChangingView;
row.prepareExpansionChanged();
- } else if (event.animationType == NotificationStackScrollLayout
- .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
+ } else if (NotificationsImprovedHunAnimation.isEnabled()
+ && (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR)) {
+ mHeadsUpAppearChildren.add(changingView);
+
+ mTmpState.copyFrom(changingView.getViewState());
+ if (event.headsUpFromBottom) {
+ // start from the bottom of the screen
+ mTmpState.setYTranslation(
+ mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen);
+ } else {
+ // start from the top of the screen
+ mTmpState.setYTranslation(
+ -mStackTopMargin - mHeadsUpAppearStartAboveScreen);
+ }
+ // set the height and the initial position
+ mTmpState.applyToView(changingView);
+ mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+ Interpolators.FAST_OUT_SLOW_IN);
+
+ Runnable onAnimationEnd = null;
+ if (loggable) {
+ // This only captures HEADS_UP_APPEAR animations, but HUNs can appear with
+ // normal ADD animations, which would not be logged here.
+ String finalKey = key;
+ mLogger.logHUNViewAppearing(key);
+ onAnimationEnd = () -> mLogger.appearAnimationEnded(finalKey);
+ }
+ changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
+ /* isHeadsUpAppear= */ true, onAnimationEnd);
+ } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) {
+ NotificationsImprovedHunAnimation.assertInLegacyMode();
// This item is added, initialize its properties.
ExpandableViewState viewState = changingView.getViewState();
mTmpState.copyFrom(viewState);
@@ -536,6 +572,10 @@
changingView.setInRemovalAnimation(true);
};
}
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+ Interpolators.FAST_OUT_SLOW_IN_REVERSE);
+ }
long removeAnimationDelay = changingView.performRemoveAnimation(
ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
0, 0.0f, true /* isHeadsUpAppear */,
@@ -601,6 +641,10 @@
mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
}
+ public void setStackTopMargin(int stackTopMargin) {
+ mStackTopMargin = stackTopMargin;
+ }
+
public void setShadeExpanded(boolean shadeExpanded) {
mShadeExpanded = shadeExpanded;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
index 4de3a7f..3a650aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
@@ -18,7 +18,7 @@
import android.graphics.Rect
import android.util.Log
import com.android.app.tracing.FlowTracing.traceEach
-import com.android.systemui.common.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON
@@ -28,6 +28,7 @@
import com.android.systemui.util.kotlin.area
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.race
+import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.flow.Flow
@@ -38,7 +39,6 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withTimeout
-import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt
index 98c1734..3cd9a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack.shared
import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the DisplaySwitchNotificationsHider flag state. */
@@ -24,6 +25,10 @@
object DisplaySwitchNotificationsHiderFlag {
const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
/** Is the hiding enabled? */
@JvmStatic
inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt
index 274bf94..910b40f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/HideNotificationsBinder.kt
@@ -16,29 +16,22 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import androidx.core.view.doOnDetach
-import androidx.lifecycle.lifecycleScope
-import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
-import kotlinx.coroutines.launch
/**
* Binds a [NotificationStackScrollLayoutController] to its [view model][NotificationListViewModel].
*/
object HideNotificationsBinder {
- fun bindHideList(
+ suspend fun bindHideList(
viewController: NotificationStackScrollLayoutController,
viewModel: NotificationListViewModel
) {
- viewController.view.repeatWhenAttached {
- lifecycleScope.launch {
- viewModel.hideListViewModel.shouldHideListForPerformance.collect { shouldHide ->
- viewController.bindHideState(shouldHide)
- }
- }
- }
-
viewController.view.doOnDetach { viewController.bindHideState(shouldHide = false) }
+
+ viewModel.hideListViewModel.shouldHideListForPerformance.collect { shouldHide ->
+ viewController.bindHideState(shouldHide)
+ }
}
private fun NotificationStackScrollLayoutController.bindHideState(shouldHide: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 9373d49..1b36660 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -32,15 +32,14 @@
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.statusbar.phone.NotificationIconAreaController
-import com.android.systemui.statusbar.ui.SystemBarUtilsState
+import com.android.systemui.util.kotlin.getOrNull
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.combine
@@ -55,25 +54,27 @@
private val configuration: ConfigurationState,
private val falsingManager: FalsingManager,
private val iconAreaController: NotificationIconAreaController,
- private val iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
private val metricsLogger: MetricsLogger,
- private val shelfIconViewStore: ShelfNotificationIconViewStore,
- private val systemBarUtilsState: SystemBarUtilsState,
+ private val nicBinder: NotificationIconContainerShelfViewBinder,
) {
- fun bind(
+ fun bindWhileAttached(
view: NotificationStackScrollLayout,
viewController: NotificationStackScrollLayoutController
) {
- bindShelf(view)
- bindHideList(viewController, viewModel)
+ val shelf =
+ LayoutInflater.from(view.context)
+ .inflate(R.layout.status_bar_notification_shelf, view, false) as NotificationShelf
+ view.setShelf(shelf)
- if (FooterViewRefactor.isEnabled) {
- bindFooter(view)
- bindEmptyShade(view)
+ view.repeatWhenAttached {
+ lifecycleScope.launch {
+ launch { bindShelf(shelf) }
+ launch { bindHideList(viewController, viewModel) }
- view.repeatWhenAttached {
- lifecycleScope.launch {
+ if (FooterViewRefactor.isEnabled) {
+ launch { bindFooter(view) }
+ launch { bindEmptyShade(view) }
viewModel.isImportantForAccessibility.collect { isImportantForAccessibility ->
view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
}
@@ -82,73 +83,57 @@
}
}
- private fun bindShelf(parentView: NotificationStackScrollLayout) {
- val shelf =
- LayoutInflater.from(parentView.context)
- .inflate(R.layout.status_bar_notification_shelf, parentView, false)
- as NotificationShelf
+ private suspend fun bindShelf(shelf: NotificationShelf) {
NotificationShelfViewBinder.bind(
shelf,
viewModel.shelf,
- configuration,
- systemBarUtilsState,
falsingManager,
- iconViewBindingFailureTracker,
+ nicBinder,
iconAreaController,
- shelfIconViewStore,
)
- parentView.setShelf(shelf)
}
- private fun bindFooter(parentView: NotificationStackScrollLayout) {
- viewModel.footer.ifPresent { footerViewModel ->
+ private suspend fun bindFooter(parentView: NotificationStackScrollLayout) {
+ viewModel.footer.getOrNull()?.let { footerViewModel ->
// The footer needs to be re-inflated every time the theme or the font size changes.
- parentView.repeatWhenAttached {
- configuration.reinflateAndBindLatest(
- R.layout.status_bar_notification_footer,
- parentView,
- attachToRoot = false,
- backgroundDispatcher,
- ) { footerView: FooterView ->
- traceSection("bind FooterView") {
- val disposableHandle =
- FooterViewBinder.bind(
- footerView,
- footerViewModel,
- clearAllNotifications = {
- metricsLogger.action(
- MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES
- )
- parentView.clearAllNotifications()
- },
- )
- parentView.setFooterView(footerView)
- return@reinflateAndBindLatest disposableHandle
- }
+ configuration.reinflateAndBindLatest(
+ R.layout.status_bar_notification_footer,
+ parentView,
+ attachToRoot = false,
+ backgroundDispatcher,
+ ) { footerView: FooterView ->
+ traceSection("bind FooterView") {
+ val disposableHandle =
+ FooterViewBinder.bind(
+ footerView,
+ footerViewModel,
+ clearAllNotifications = {
+ metricsLogger.action(
+ MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES
+ )
+ parentView.clearAllNotifications()
+ },
+ )
+ parentView.setFooterView(footerView)
+ return@reinflateAndBindLatest disposableHandle
}
}
}
}
- private fun bindEmptyShade(
- parentView: NotificationStackScrollLayout,
- ) {
- parentView.repeatWhenAttached {
- lifecycleScope.launch {
- combine(
- viewModel.shouldShowEmptyShadeView,
- viewModel.areNotificationsHiddenInShade,
- viewModel.hasFilteredOutSeenNotifications,
- ::Triple
- )
- .collect { (shouldShow, areNotifsHidden, hasFilteredNotifs) ->
- parentView.updateEmptyShadeView(
- shouldShow,
- areNotifsHidden,
- hasFilteredNotifs,
- )
- }
+ private suspend fun bindEmptyShade(parentView: NotificationStackScrollLayout) {
+ combine(
+ viewModel.shouldShowEmptyShadeView,
+ viewModel.areNotificationsHiddenInShade,
+ viewModel.hasFilteredOutSeenNotifications,
+ ::Triple
+ )
+ .collect { (shouldShow, areNotifsHidden, hasFilteredNotifs) ->
+ parentView.updateEmptyShadeView(
+ shouldShow,
+ areNotifsHidden,
+ hasFilteredNotifs,
+ )
}
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 145dbff..7cc0888 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -196,7 +196,7 @@
}
private void updateKeyguardStatusBarHeight() {
- MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
+ ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) getLayoutParams();
lp.height = getStatusBarHeaderHeightKeyguard(mContext);
setLayoutParams(lp);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index a62a1ed..e79f3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -608,7 +608,7 @@
}
@Override
- public void onPulseExpansionChanged(boolean expandingChanged) {
+ public void onPulseExpansionAmountChanged(boolean expandingChanged) {
if (expandingChanged) {
updateAodIconsVisibility(true /* animate */, false /* force */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 0dabafb..f34a44a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -284,11 +284,22 @@
@Override
public String toString() {
- return "NotificationIconContainer("
- + "dozing=" + mDozing + " onLockScreen=" + mOnLockScreen
- + " overrideIconColor=" + mOverrideIconColor
- + " speedBumpIndex=" + mSpeedBumpIndex
- + " themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary) + ')';
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ return super.toString()
+ + " {"
+ + " overrideIconColor=" + mOverrideIconColor
+ + ", maxIcons=" + mMaxIcons
+ + ", isStaticLayout=" + mIsStaticLayout
+ + ", themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary)
+ + " }";
+ } else {
+ return "NotificationIconContainer("
+ + "dozing=" + mDozing + " onLockScreen=" + mOnLockScreen
+ + " overrideIconColor=" + mOverrideIconColor
+ + " speedBumpIndex=" + mSpeedBumpIndex
+ + " themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary)
+ + ')';
+ }
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index cd99934..2740cc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -38,7 +38,6 @@
import com.android.app.animation.InterpolatorsAndroidX;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
-import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
@@ -54,10 +53,7 @@
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
@@ -75,7 +71,6 @@
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.ui.SystemBarUtilsState;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
import com.android.systemui.util.CarrierConfigTracker;
@@ -95,6 +90,8 @@
import kotlin.Unit;
+import kotlinx.coroutines.DisposableHandle;
+
/**
* Contains the collapsed status bar and handles hiding/showing based on disable flags
* and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -151,10 +148,7 @@
private final DumpManager mDumpManager;
private final StatusBarWindowStateController mStatusBarWindowStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final NotificationIconContainerStatusBarViewModel mStatusBarIconsViewModel;
- private final ConfigurationState mConfigurationState;
- private final SystemBarUtilsState mSystemBarUtilsState;
- private final StatusBarNotificationIconViewStore mStatusBarIconViewStore;
+ private final NotificationIconContainerStatusBarViewBinder mNicViewBinder;
private final DemoModeController mDemoModeController;
private List<String> mBlockedIcons = new ArrayList<>();
@@ -216,7 +210,7 @@
mWaitingForWindowStateChangeAfterCameraLaunch = false;
mTransitionFromLockscreenToDreamStarted = false;
};
- private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker;
+ private DisposableHandle mNicBindingDisposable;
@Inject
public CollapsedStatusBarFragment(
@@ -234,7 +228,7 @@
KeyguardStateController keyguardStateController,
ShadeViewController shadeViewController,
StatusBarStateController statusBarStateController,
- StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker,
+ NotificationIconContainerStatusBarViewBinder nicViewBinder,
CommandQueue commandQueue,
CarrierConfigTracker carrierConfigTracker,
CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
@@ -244,10 +238,6 @@
DumpManager dumpManager,
StatusBarWindowStateController statusBarWindowStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- NotificationIconContainerStatusBarViewModel statusBarIconsViewModel,
- ConfigurationState configurationState,
- SystemBarUtilsState systemBarUtilsState,
- StatusBarNotificationIconViewStore statusBarIconViewStore,
DemoModeController demoModeController) {
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
mOngoingCallController = ongoingCallController;
@@ -263,7 +253,7 @@
mKeyguardStateController = keyguardStateController;
mShadeViewController = shadeViewController;
mStatusBarStateController = statusBarStateController;
- mIconViewBindingFailureTracker = iconViewBindingFailureTracker;
+ mNicViewBinder = nicViewBinder;
mCommandQueue = commandQueue;
mCarrierConfigTracker = carrierConfigTracker;
mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger;
@@ -273,10 +263,6 @@
mDumpManager = dumpManager;
mStatusBarWindowStateController = statusBarWindowStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mStatusBarIconsViewModel = statusBarIconsViewModel;
- mConfigurationState = configurationState;
- mSystemBarUtilsState = systemBarUtilsState;
- mStatusBarIconViewStore = statusBarIconViewStore;
mDemoModeController = demoModeController;
}
@@ -455,6 +441,12 @@
mStartableStates.put(startable, Startable.State.STOPPED);
}
mDumpManager.unregisterDumpable(getClass().getSimpleName());
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ if (mNicBindingDisposable != null) {
+ mNicBindingDisposable.dispose();
+ mNicBindingDisposable = null;
+ }
+ }
}
/** Initializes views related to the notification icon area. */
@@ -466,13 +458,7 @@
.inflate(R.layout.notification_icon_area, notificationIconArea, true);
NotificationIconContainer notificationIcons =
notificationIconArea.requireViewById(R.id.notificationIcons);
- NotificationIconContainerViewBinder.bindWhileAttached(
- notificationIcons,
- mStatusBarIconsViewModel,
- mConfigurationState,
- mSystemBarUtilsState,
- mIconViewBindingFailureTracker,
- mStatusBarIconViewStore);
+ mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
} else {
mNotificationIconAreaInner =
mNotificationIconAreaController.getNotificationInnerAreaView();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index a14e87c..5475528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -30,6 +30,7 @@
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.Flags.statusBarStaticInoutIndicators
import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -41,6 +42,8 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBinderConstants.ALPHA_ACTIVE
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBinderConstants.ALPHA_INACTIVE
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -190,10 +193,29 @@
}
}
- // Set the activity indicators
- launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } }
+ if (statusBarStaticInoutIndicators()) {
+ // Set the opacity of the activity indicators
+ launch {
+ viewModel.activityInVisible.collect { visible ->
+ activityIn.imageAlpha =
+ (if (visible) ALPHA_ACTIVE else ALPHA_INACTIVE)
+ }
+ }
- launch { viewModel.activityOutVisible.collect { activityOut.isVisible = it } }
+ launch {
+ viewModel.activityOutVisible.collect { visible ->
+ activityOut.imageAlpha =
+ (if (visible) ALPHA_ACTIVE else ALPHA_INACTIVE)
+ }
+ }
+ } else {
+ // Set the activity indicators
+ launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } }
+
+ launch {
+ viewModel.activityOutVisible.collect { activityOut.isVisible = it }
+ }
+ }
launch {
viewModel.activityContainerVisible.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index d88c9ef..60c662d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
+import com.android.systemui.Flags.statusBarStaticInoutIndicators
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.flags.FeatureFlagsClassic
@@ -199,7 +200,10 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val activityContainerVisible: Flow<Boolean> =
- activity
- .map { it != null && (it.hasActivityIn || it.hasActivityOut) }
+ if (statusBarStaticInoutIndicators()) {
+ flowOf(constants.shouldShowActivityConfig)
+ } else {
+ activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
+ }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarViewBinderConstants.kt
similarity index 66%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarViewBinderConstants.kt
index dca151d..7348d5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/StatusBarViewBinderConstants.kt
@@ -13,14 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.statusbar.pipeline.shared.ui.binder
-package com.android.systemui.keyguard.ui.viewmodel
-
-import android.graphics.Rect
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-@ExperimentalCoroutinesApi
-class UdfpsKeyguardViewModel @Inject constructor() {
- var sensorBounds: Rect = Rect()
+object StatusBarViewBinderConstants {
+ /** Opacity for the traffic indicator icons */
+ const val ALPHA_ACTIVE: Int = (1.0 * 255).toInt() // Fully opaque
+ const val ALPHA_INACTIVE: Int = (0.3 * 255).toInt() // 30% opaque -> 70% transparency
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 6005527..9512490 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -23,6 +23,7 @@
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.Flags.statusBarStaticInoutIndicators
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
@@ -30,6 +31,8 @@
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBinderConstants.ALPHA_ACTIVE
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarViewBinderConstants.ALPHA_INACTIVE
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
@@ -118,15 +121,36 @@
launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
- launch {
- viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible ->
- activityInView.isVisible = visible
+ if (statusBarStaticInoutIndicators()) {
+ // Set the opacity of the activity indicators
+ launch {
+ viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible
+ ->
+ activityInView.imageAlpha =
+ (if (visible) ALPHA_ACTIVE else ALPHA_INACTIVE)
+ }
}
- }
- launch {
- viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible ->
- activityOutView.isVisible = visible
+ launch {
+ viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible
+ ->
+ activityOutView.imageAlpha =
+ (if (visible) ALPHA_ACTIVE else ALPHA_INACTIVE)
+ }
+ }
+ } else {
+ launch {
+ viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible
+ ->
+ activityInView.isVisible = visible
+ }
+ }
+
+ launch {
+ viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible
+ ->
+ activityOutView.isVisible = visible
+ }
}
}
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 d099c8e..fa243da 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
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.content.Context
+import com.android.systemui.Flags.statusBarStaticInoutIndicators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
@@ -38,7 +39,6 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -101,42 +101,36 @@
)
/** The wifi activity status. Null if we shouldn't display the activity status. */
- private val activity: Flow<DataActivityModel> = run {
- val default = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ private val activity: Flow<DataActivityModel?> =
if (!connectivityConstants.shouldShowActivityConfig) {
- flowOf(default)
+ flowOf(null)
} else {
combine(interactor.activity, interactor.ssid) { activity, ssid ->
when (ssid) {
- null -> default
+ null -> null
else -> activity
}
}
}
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTableLogBuffer,
- columnPrefix = "VM.activity",
- initialValue = default,
- )
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = default)
- }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
override val isActivityInViewVisible: Flow<Boolean> =
activity
- .map { it.hasActivityIn }
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+ .map { it?.hasActivityIn ?: false }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isActivityOutViewVisible: Flow<Boolean> =
activity
- .map { it.hasActivityOut }
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+ .map { it?.hasActivityOut ?: false }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isActivityContainerVisible: Flow<Boolean> =
- combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut ->
- activityIn || activityOut
+ if (statusBarStaticInoutIndicators()) {
+ flowOf(connectivityConstants.shouldShowActivityConfig)
+ } else {
+ activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
}
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
// TODO(b/238425913): It isn't ideal for the wifi icon to need to know about whether the
// airplane icon is visible. Instead, we should have a parent StatusBarSystemIconsViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 4864fb8..5bced93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -303,8 +303,7 @@
mEntry.mRemoteEditImeVisible = editTextRootWindowInsets != null
&& editTextRootWindowInsets.isVisible(WindowInsets.Type.ime());
if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {
- // Pass null to ensure all inputs are cleared for this entry b/227115380
- mController.removeRemoteInput(mEntry, null,
+ mController.removeRemoteInput(mEntry, mToken,
/* reason= */"RemoteInputView$WindowInsetAnimation#onEnd");
}
}
@@ -536,6 +535,11 @@
if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) {
return;
}
+ // RemoteInputView can be detached from window before IME close event in some cases like
+ // remote input view removal with notification update. As a result of this, RemoteInputView
+ // will stop ime animation updates, which results in never removing remote input. That's why
+ // we have to set mRemoteEditImeAnimatingAway false on detach to remove remote input.
+ mEntry.mRemoteEditImeAnimatingAway = false;
mController.removeRemoteInput(mEntry, mToken,
/* reason= */"RemoteInputView#onDetachedFromWindow");
mController.removeSpinning(mEntry.getKey(), mToken);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index fa0cb5c..66bf527 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy;
import android.app.AlarmManager;
+import android.app.Flags;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -191,7 +192,11 @@
@Override
public void setZen(int zen, Uri conditionId, String reason) {
- mNoMan.setZenMode(zen, conditionId, reason);
+ if (Flags.modesApi()) {
+ mNoMan.setZenMode(zen, conditionId, reason, /* fromUser= */ true);
+ } else {
+ mNoMan.setZenMode(zen, conditionId, reason);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index e0d205f..c170eb5 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -37,6 +37,7 @@
import com.android.internal.util.UserIcons
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Flags.switchUserOnBg
import com.android.systemui.SystemUISecondaryUserService
import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -44,6 +45,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -100,6 +102,7 @@
broadcastDispatcher: BroadcastDispatcher,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Main private val mainDispatcher: CoroutineDispatcher,
private val activityManager: ActivityManager,
private val refreshUsersScheduler: RefreshUsersScheduler,
private val guestUserInteractor: GuestUserInteractor,
@@ -339,7 +342,11 @@
}
.launchIn(applicationScope)
restartSecondaryService(repository.getSelectedUserInfo().id)
- keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ applicationScope.launch {
+ withContext(mainDispatcher) {
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ }
+ }
}
fun addCallback(callback: UserCallback) {
@@ -593,10 +600,18 @@
private fun switchUser(userId: Int) {
// TODO(b/246631653): track jank and latency like in the old impl.
refreshUsersScheduler.pause()
- try {
- activityManager.switchUser(userId)
- } catch (e: RemoteException) {
- Log.e(TAG, "Couldn't switch user.", e)
+ val runnable = Runnable {
+ try {
+ activityManager.switchUser(userId)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Couldn't switch user.", e)
+ }
+ }
+
+ if (switchUserOnBg()) {
+ applicationScope.launch { withContext(backgroundDispatcher) { runnable.run() } }
+ } else {
+ runnable.run()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java b/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java
deleted file mode 100644
index 8215360..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/leak/DumpTruck.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.util.leak;
-
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Build;
-import android.util.Log;
-
-import androidx.core.content.FileProvider;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-
-/**
- * Utility class for dumping, compressing, sending, and serving heap dump files.
- *
- * <p>Unlike the Internet, this IS a big truck you can dump something on.
- */
-public class DumpTruck {
- private static final String FILEPROVIDER_AUTHORITY = "com.android.systemui.fileprovider";
- private static final String FILEPROVIDER_PATH = "leak";
-
- private static final String TAG = "DumpTruck";
- private static final int BUFSIZ = 1024 * 1024; // 1MB
-
- private final Context context;
- private final GarbageMonitor mGarbageMonitor;
- private Uri hprofUri;
- private long rss;
- final StringBuilder body = new StringBuilder();
-
- public DumpTruck(Context context, GarbageMonitor garbageMonitor) {
- this.context = context;
- mGarbageMonitor = garbageMonitor;
- }
-
- /**
- * Capture memory for the given processes and zip them up for sharing.
- *
- * @param pids
- * @return this, for chaining
- */
- public DumpTruck captureHeaps(List<Long> pids) {
- final File dumpDir = new File(context.getCacheDir(), FILEPROVIDER_PATH);
- dumpDir.mkdirs();
- hprofUri = null;
-
- body.setLength(0);
- body.append("Build: ").append(Build.DISPLAY).append("\n\nProcesses:\n");
-
- final ArrayList<String> paths = new ArrayList<String>();
- final int myPid = android.os.Process.myPid();
-
- for (Long pidL : pids) {
- final int pid = pidL.intValue();
- body.append(" pid ").append(pid);
- GarbageMonitor.ProcessMemInfo info = mGarbageMonitor.getMemInfo(pid);
- if (info != null) {
- body.append(":")
- .append(" up=")
- .append(info.getUptime())
- .append(" rss=")
- .append(info.currentRss);
- rss = info.currentRss;
- }
- if (pid == myPid) {
- final String path =
- new File(dumpDir, String.format("heap-%d.ahprof", pid)).getPath();
- Log.v(TAG, "Dumping memory info for process " + pid + " to " + path);
- try {
- android.os.Debug.dumpHprofData(path); // will block
- paths.add(path);
- body.append(" (hprof attached)");
- } catch (IOException e) {
- Log.e(TAG, "error dumping memory:", e);
- body.append("\n** Could not dump heap: \n").append(e.toString()).append("\n");
- }
- }
- body.append("\n");
- }
-
- try {
- final String zipfile =
- new File(dumpDir, String.format("hprof-%d.zip", System.currentTimeMillis()))
- .getCanonicalPath();
- if (DumpTruck.zipUp(zipfile, paths)) {
- final File pathFile = new File(zipfile);
- hprofUri = FileProvider.getUriForFile(context, FILEPROVIDER_AUTHORITY, pathFile);
- Log.v(TAG, "Heap dump accessible at URI: " + hprofUri);
- }
- } catch (IOException e) {
- Log.e(TAG, "unable to zip up heapdumps", e);
- body.append("\n** Could not zip up files: \n").append(e.toString()).append("\n");
- }
-
- return this;
- }
-
- /**
- * Get the Uri of the current heap dump. Be sure to call captureHeaps first.
- *
- * @return Uri to the dump served by the SystemUI file provider
- */
- public Uri getDumpUri() {
- return hprofUri;
- }
-
- /**
- * Get an ACTION_SEND intent suitable for startActivity() or attaching to a Notification.
- *
- * @return share intent
- */
- public Intent createShareIntent() {
- Intent shareIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
- shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- shareIntent.putExtra(Intent.EXTRA_SUBJECT,
- String.format("SystemUI memory dump (rss=%dM)", rss / 1024));
-
- shareIntent.putExtra(Intent.EXTRA_TEXT, body.toString());
-
- if (hprofUri != null) {
- final ArrayList<Uri> uriList = new ArrayList<>();
- uriList.add(hprofUri);
- shareIntent.setType("application/zip");
- shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList);
-
- // Include URI in ClipData also, so that grantPermission picks it up.
- // We don't use setData here because some apps interpret this as "to:".
- ClipData clipdata = new ClipData(new ClipDescription("content",
- new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
- new ClipData.Item(hprofUri));
- shareIntent.setClipData(clipdata);
- shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
- return shareIntent;
- }
-
- private static boolean zipUp(String zipfilePath, ArrayList<String> paths) {
- try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipfilePath))) {
- final byte[] buf = new byte[BUFSIZ];
-
- for (String filename : paths) {
- try (InputStream is = new BufferedInputStream(new FileInputStream(filename))) {
- ZipEntry entry = new ZipEntry(filename);
- zos.putNextEntry(entry);
- int len;
- while (0 < (len = is.read(buf, 0, BUFSIZ))) {
- zos.write(buf, 0, len);
- }
- zos.closeEntry();
- }
- }
- return true;
- } catch (IOException e) {
- Log.e(TAG, "error zipping up profile data", e);
- }
- return false;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
deleted file mode 100644
index de392d3..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ /dev/null
@@ -1,617 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.util.leak;
-
-import static android.service.quicksettings.Tile.STATE_ACTIVE;
-import static android.telephony.ims.feature.ImsFeature.STATE_UNAVAILABLE;
-
-import static com.android.internal.logging.MetricsLogger.VIEW_UNKNOWN;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.ColorStateList;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Process;
-import android.os.SystemProperties;
-import android.provider.Settings;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.LongSparseArray;
-import android.view.View;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.QsEventLogger;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.concurrency.MessageRouter;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Suite of tools to periodically inspect the System UI heap and possibly prompt the user to
- * capture heap dumps and report them. Includes the implementation of the "Dump SysUI Heap"
- * quick settings tile.
- */
-@SysUISingleton
-public class GarbageMonitor implements Dumpable {
- // Feature switches
- // ================
-
- // Whether to use TrackedGarbage to trigger LeakReporter. Off by default unless you set the
- // appropriate sysprop on a userdebug device.
- public static final boolean LEAK_REPORTING_ENABLED = Build.IS_DEBUGGABLE
- && SystemProperties.getBoolean("debug.enable_leak_reporting", false);
- public static final String FORCE_ENABLE_LEAK_REPORTING = "sysui_force_enable_leak_reporting";
-
- // Heap tracking: watch the current memory levels and update the MemoryTile if available.
- // On for all userdebug devices.
- public static final boolean HEAP_TRACKING_ENABLED = Build.IS_DEBUGGABLE;
-
- // Tell QSTileHost.java to toss this into the default tileset?
- public static final boolean ADD_MEMORY_TILE_TO_DEFAULT_ON_DEBUGGABLE_BUILDS = true;
-
- // whether to use ActivityManager.setHeapLimit (and post a notification to the user asking
- // to dump the heap). Off by default unless you set the appropriate sysprop on userdebug
- private static final boolean ENABLE_AM_HEAP_LIMIT = Build.IS_DEBUGGABLE
- && SystemProperties.getBoolean("debug.enable_sysui_heap_limit", false);
-
- // Tuning params
- // =============
-
- // threshold for setHeapLimit(), in KB (overrides R.integer.watch_heap_limit)
- private static final String SETTINGS_KEY_AM_HEAP_LIMIT = "systemui_am_heap_limit";
-
- private static final long GARBAGE_INSPECTION_INTERVAL =
- 15 * DateUtils.MINUTE_IN_MILLIS; // 15 min
- private static final long HEAP_TRACK_INTERVAL = 1 * DateUtils.MINUTE_IN_MILLIS; // 1 min
- private static final int HEAP_TRACK_HISTORY_LEN = 720; // 12 hours
-
- private static final int DO_GARBAGE_INSPECTION = 1000;
- private static final int DO_HEAP_TRACK = 3000;
-
- static final int GARBAGE_ALLOWANCE = 5;
-
- private static final String TAG = "GarbageMonitor";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private final MessageRouter mMessageRouter;
- private final TrackedGarbage mTrackedGarbage;
- private final LeakReporter mLeakReporter;
- private final Context mContext;
- private final DelayableExecutor mDelayableExecutor;
- private MemoryTile mQSTile;
- private final DumpTruck mDumpTruck;
-
- private final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<>();
- private final ArrayList<Long> mPids = new ArrayList<>();
-
- private long mHeapLimit;
-
- /**
- */
- @Inject
- public GarbageMonitor(
- Context context,
- @Background DelayableExecutor delayableExecutor,
- @Background MessageRouter messageRouter,
- LeakDetector leakDetector,
- LeakReporter leakReporter,
- DumpManager dumpManager) {
- mContext = context.getApplicationContext();
-
- mDelayableExecutor = delayableExecutor;
- mMessageRouter = messageRouter;
- mMessageRouter.subscribeTo(DO_GARBAGE_INSPECTION, this::doGarbageInspection);
- mMessageRouter.subscribeTo(DO_HEAP_TRACK, this::doHeapTrack);
-
- mTrackedGarbage = leakDetector.getTrackedGarbage();
- mLeakReporter = leakReporter;
-
- mDumpTruck = new DumpTruck(mContext, this);
-
- dumpManager.registerDumpable(getClass().getSimpleName(), this);
-
- if (ENABLE_AM_HEAP_LIMIT) {
- mHeapLimit = Settings.Global.getInt(context.getContentResolver(),
- SETTINGS_KEY_AM_HEAP_LIMIT,
- mContext.getResources().getInteger(R.integer.watch_heap_limit));
- }
- }
-
- public void startLeakMonitor() {
- if (mTrackedGarbage == null) {
- return;
- }
-
- mMessageRouter.sendMessage(DO_GARBAGE_INSPECTION);
- }
-
- public void startHeapTracking() {
- startTrackingProcess(
- android.os.Process.myPid(), mContext.getPackageName(), System.currentTimeMillis());
- mMessageRouter.sendMessage(DO_HEAP_TRACK);
- }
-
- private boolean gcAndCheckGarbage() {
- if (mTrackedGarbage.countOldGarbage() > GARBAGE_ALLOWANCE) {
- Runtime.getRuntime().gc();
- return true;
- }
- return false;
- }
-
- void reinspectGarbageAfterGc() {
- int count = mTrackedGarbage.countOldGarbage();
- if (count > GARBAGE_ALLOWANCE) {
- mLeakReporter.dumpLeak(count);
- }
- }
-
- public ProcessMemInfo getMemInfo(int pid) {
- return mData.get(pid);
- }
-
- public List<Long> getTrackedProcesses() {
- return mPids;
- }
-
- public void startTrackingProcess(long pid, String name, long start) {
- synchronized (mPids) {
- if (mPids.contains(pid)) return;
-
- mPids.add(pid);
- logPids();
-
- mData.put(pid, new ProcessMemInfo(pid, name, start));
- }
- }
-
- private void logPids() {
- if (DEBUG) {
- StringBuffer sb = new StringBuffer("Now tracking processes: ");
- for (int i = 0; i < mPids.size(); i++) {
- final int p = mPids.get(i).intValue();
- sb.append(" ");
- }
- Log.v(TAG, sb.toString());
- }
- }
-
- private void update() {
- synchronized (mPids) {
- for (int i = 0; i < mPids.size(); i++) {
- final int pid = mPids.get(i).intValue();
- // rssValues contains [VmRSS, RssFile, RssAnon, VmSwap].
- long[] rssValues = Process.getRss(pid);
- if (rssValues == null && rssValues.length == 0) {
- if (DEBUG) Log.e(TAG, "update: Process.getRss() didn't provide any values.");
- break;
- }
- long rss = rssValues[0];
- final ProcessMemInfo info = mData.get(pid);
- info.rss[info.head] = info.currentRss = rss;
- info.head = (info.head + 1) % info.rss.length;
- if (info.currentRss > info.max) info.max = info.currentRss;
- if (info.currentRss == 0) {
- if (DEBUG) Log.v(TAG, "update: pid " + pid + " has rss=0, it probably died");
- mData.remove(pid);
- }
- }
- for (int i = mPids.size() - 1; i >= 0; i--) {
- final long pid = mPids.get(i).intValue();
- if (mData.get(pid) == null) {
- mPids.remove(i);
- logPids();
- }
- }
- }
- if (mQSTile != null) mQSTile.update();
- }
-
- private void setTile(MemoryTile tile) {
- mQSTile = tile;
- if (tile != null) tile.update();
- }
-
- private static String formatBytes(long b) {
- String[] SUFFIXES = {"B", "K", "M", "G", "T"};
- int i;
- for (i = 0; i < SUFFIXES.length; i++) {
- if (b < 1024) break;
- b /= 1024;
- }
- return b + SUFFIXES[i];
- }
-
- private Intent dumpHprofAndGetShareIntent() {
- return mDumpTruck.captureHeaps(getTrackedProcesses()).createShareIntent();
- }
-
- @Override
- public void dump(PrintWriter pw, @Nullable String[] args) {
- pw.println("GarbageMonitor params:");
- pw.println(String.format(" mHeapLimit=%d KB", mHeapLimit));
- pw.println(String.format(" GARBAGE_INSPECTION_INTERVAL=%d (%.1f mins)",
- GARBAGE_INSPECTION_INTERVAL,
- (float) GARBAGE_INSPECTION_INTERVAL / DateUtils.MINUTE_IN_MILLIS));
- final float htiMins = HEAP_TRACK_INTERVAL / DateUtils.MINUTE_IN_MILLIS;
- pw.println(String.format(" HEAP_TRACK_INTERVAL=%d (%.1f mins)",
- HEAP_TRACK_INTERVAL,
- htiMins));
- pw.println(String.format(" HEAP_TRACK_HISTORY_LEN=%d (%.1f hr total)",
- HEAP_TRACK_HISTORY_LEN,
- (float) HEAP_TRACK_HISTORY_LEN * htiMins / 60f));
-
- pw.println("GarbageMonitor tracked processes:");
-
- for (long pid : mPids) {
- final ProcessMemInfo pmi = mData.get(pid);
- if (pmi != null) {
- pmi.dump(pw, args);
- }
- }
- }
-
-
- private static class MemoryIconDrawable extends Drawable {
- long rss, limit;
- final Drawable baseIcon;
- final Paint paint = new Paint();
- final float dp;
-
- MemoryIconDrawable(Context context) {
- baseIcon = context.getDrawable(R.drawable.ic_memory).mutate();
- dp = context.getResources().getDisplayMetrics().density;
- paint.setColor(Color.WHITE);
- }
-
- public void setRss(long rss) {
- if (rss != this.rss) {
- this.rss = rss;
- invalidateSelf();
- }
- }
-
- public void setLimit(long limit) {
- if (limit != this.limit) {
- this.limit = limit;
- invalidateSelf();
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- baseIcon.draw(canvas);
-
- if (limit > 0 && rss > 0) {
- float frac = Math.min(1f, (float) rss / limit);
-
- final Rect bounds = getBounds();
- canvas.translate(bounds.left + 8 * dp, bounds.top + 5 * dp);
- //android:pathData="M16.0,5.0l-8.0,0.0l0.0,14.0l8.0,0.0z"
- canvas.drawRect(0, 14 * dp * (1 - frac), 8 * dp + 1, 14 * dp + 1, paint);
- }
- }
-
- @Override
- public void setBounds(int left, int top, int right, int bottom) {
- super.setBounds(left, top, right, bottom);
- baseIcon.setBounds(left, top, right, bottom);
- }
-
- @Override
- public int getIntrinsicHeight() {
- return baseIcon.getIntrinsicHeight();
- }
-
- @Override
- public int getIntrinsicWidth() {
- return baseIcon.getIntrinsicWidth();
- }
-
- @Override
- public void setAlpha(int i) {
- baseIcon.setAlpha(i);
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- baseIcon.setColorFilter(colorFilter);
- paint.setColorFilter(colorFilter);
- }
-
- @Override
- public void setTint(int tint) {
- super.setTint(tint);
- baseIcon.setTint(tint);
- }
-
- @Override
- public void setTintList(ColorStateList tint) {
- super.setTintList(tint);
- baseIcon.setTintList(tint);
- }
-
- @Override
- public void setTintMode(PorterDuff.Mode tintMode) {
- super.setTintMode(tintMode);
- baseIcon.setTintMode(tintMode);
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
- }
-
- private static class MemoryGraphIcon extends QSTile.Icon {
- long rss, limit;
-
- public void setRss(long rss) {
- this.rss = rss;
- }
-
- public void setHeapLimit(long limit) {
- this.limit = limit;
- }
-
- @Override
- public Drawable getDrawable(Context context) {
- final MemoryIconDrawable drawable = new MemoryIconDrawable(context);
- drawable.setRss(rss);
- drawable.setLimit(limit);
- return drawable;
- }
- }
-
- public static class MemoryTile extends QSTileImpl<QSTile.State> {
- public static final String TILE_SPEC = "dbg:mem";
-
- private final GarbageMonitor gm;
- private ProcessMemInfo pmi;
- private boolean dumpInProgress;
- private final PanelInteractor mPanelInteractor;
-
- @Inject
- public MemoryTile(
- QSHost host,
- QsEventLogger uiEventLogger,
- @Background Looper backgroundLooper,
- @Main Handler mainHandler,
- FalsingManager falsingManager,
- MetricsLogger metricsLogger,
- StatusBarStateController statusBarStateController,
- ActivityStarter activityStarter,
- QSLogger qsLogger,
- GarbageMonitor monitor,
- PanelInteractor panelInteractor
- ) {
- super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
- statusBarStateController, activityStarter, qsLogger);
- gm = monitor;
- mPanelInteractor = panelInteractor;
- }
-
- @Override
- public State newTileState() {
- return new QSTile.State();
- }
-
- @Override
- public Intent getLongClickIntent() {
- return new Intent();
- }
-
- @Override
- protected void handleClick(@Nullable View view) {
- if (dumpInProgress) return;
-
- dumpInProgress = true;
- refreshState();
- new Thread("HeapDumpThread") {
- @Override
- public void run() {
- try {
- // wait for animations & state changes
- Thread.sleep(500);
- } catch (InterruptedException ignored) { }
- final Intent shareIntent = gm.dumpHprofAndGetShareIntent();
- mHandler.post(() -> {
- dumpInProgress = false;
- refreshState();
- mPanelInteractor.collapsePanels();
- mActivityStarter.postStartActivityDismissingKeyguard(shareIntent, 0);
- });
- }
- }.start();
- }
-
- @Override
- public int getMetricsCategory() {
- return VIEW_UNKNOWN;
- }
-
- @Override
- public void handleSetListening(boolean listening) {
- super.handleSetListening(listening);
- if (gm != null) gm.setTile(listening ? this : null);
-
- final ActivityManager am = mContext.getSystemService(ActivityManager.class);
- if (listening && gm.mHeapLimit > 0) {
- am.setWatchHeapLimit(1024 * gm.mHeapLimit); // why is this in bytes?
- } else {
- am.clearWatchHeapLimit();
- }
- }
-
- @Override
- public CharSequence getTileLabel() {
- return getState().label;
- }
-
- @Override
- protected void handleUpdateState(State state, Object arg) {
- pmi = gm.getMemInfo(Process.myPid());
- final MemoryGraphIcon icon = new MemoryGraphIcon();
- icon.setHeapLimit(gm.mHeapLimit);
- state.state = dumpInProgress ? STATE_UNAVAILABLE : STATE_ACTIVE;
- state.label = dumpInProgress
- ? "Dumping..."
- : mContext.getString(R.string.heap_dump_tile_name);
- if (pmi != null) {
- icon.setRss(pmi.currentRss);
- state.secondaryLabel =
- String.format(
- "rss: %s / %s",
- formatBytes(pmi.currentRss * 1024),
- formatBytes(gm.mHeapLimit * 1024));
- } else {
- icon.setRss(0);
- state.secondaryLabel = null;
- }
- state.icon = icon;
- }
-
- public void update() {
- refreshState();
- }
-
- public long getRss() {
- return pmi != null ? pmi.currentRss : 0;
- }
-
- public long getHeapLimit() {
- return gm != null ? gm.mHeapLimit : 0;
- }
- }
-
- /** */
- public static class ProcessMemInfo implements Dumpable {
- public long pid;
- public String name;
- public long startTime;
- public long currentRss;
- public long[] rss = new long[HEAP_TRACK_HISTORY_LEN];
- public long max = 1;
- public int head = 0;
-
- public ProcessMemInfo(long pid, String name, long start) {
- this.pid = pid;
- this.name = name;
- this.startTime = start;
- }
-
- public long getUptime() {
- return System.currentTimeMillis() - startTime;
- }
-
- @Override
- public void dump(PrintWriter pw, @Nullable String[] args) {
- pw.print("{ \"pid\": ");
- pw.print(pid);
- pw.print(", \"name\": \"");
- pw.print(name.replace('"', '-'));
- pw.print("\", \"start\": ");
- pw.print(startTime);
- pw.print(", \"rss\": [");
- // write rss values starting from the oldest, which is rss[head], wrapping around to
- // rss[(head-1) % rss.length]
- for (int i = 0; i < rss.length; i++) {
- if (i > 0) pw.print(",");
- pw.print(rss[(head + i) % rss.length]);
- }
- pw.println("] }");
- }
- }
-
- /** */
- @SysUISingleton
- public static class Service implements CoreStartable, Dumpable {
- private final Context mContext;
- private final GarbageMonitor mGarbageMonitor;
-
- @Inject
- public Service(Context context, GarbageMonitor garbageMonitor) {
- mContext = context;
- mGarbageMonitor = garbageMonitor;
- }
-
- @Override
- public void start() {
- boolean forceEnable =
- Settings.Secure.getInt(
- mContext.getContentResolver(), FORCE_ENABLE_LEAK_REPORTING, 0)
- != 0;
- if (LEAK_REPORTING_ENABLED || forceEnable) {
- mGarbageMonitor.startLeakMonitor();
- }
- if (HEAP_TRACKING_ENABLED || forceEnable) {
- mGarbageMonitor.startHeapTracking();
- }
- }
-
- @Override
- public void dump(PrintWriter pw, @Nullable String[] args) {
- if (mGarbageMonitor != null) mGarbageMonitor.dump(pw, args);
- }
- }
-
- private void doGarbageInspection(int id) {
- if (gcAndCheckGarbage()) {
- mDelayableExecutor.executeDelayed(this::reinspectGarbageAfterGc, 100);
- }
-
- mMessageRouter.cancelMessages(DO_GARBAGE_INSPECTION);
- mMessageRouter.sendMessageDelayed(DO_GARBAGE_INSPECTION, GARBAGE_INSPECTION_INTERVAL);
- }
-
- private void doHeapTrack(int id) {
- update();
- mMessageRouter.cancelMessages(DO_HEAP_TRACK);
- mMessageRouter.sendMessageDelayed(DO_HEAP_TRACK, HEAP_TRACK_INTERVAL);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt
deleted file mode 100644
index e975200..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt
+++ /dev/null
@@ -1,39 +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 com.android.systemui.util.leak
-
-import com.android.systemui.CoreStartable
-import com.android.systemui.qs.tileimpl.QSTileImpl
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-import dagger.multibindings.StringKey
-
-@Module
-interface GarbageMonitorModule {
- /** Inject into GarbageMonitor.Service. */
- @Binds
- @IntoMap
- @ClassKey(GarbageMonitor::class)
- fun bindGarbageMonitorService(sysui: GarbageMonitor.Service): CoreStartable
-
- @Binds
- @IntoMap
- @StringKey(GarbageMonitor.MemoryTile.TILE_SPEC)
- fun bindMemoryTile(memoryTile: GarbageMonitor.MemoryTile): QSTileImpl<*>
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 88f63ad..a249961 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -34,14 +34,12 @@
import android.widget.RelativeLayout;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.plugins.clocks.ClockAnimations;
import com.android.systemui.plugins.clocks.ClockController;
@@ -56,14 +54,9 @@
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel;
-import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerAlwaysOnDisplayViewBinder;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.ui.SystemBarUtilsState;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -185,9 +178,7 @@
mKeyguardSliceViewController,
mNotificationIconAreaController,
mSmartspaceController,
- mock(SystemBarUtilsState.class),
- mock(ScreenOffAnimationController.class),
- mock(StatusBarIconViewBindingFailureTracker.class),
+ mock(NotificationIconContainerAlwaysOnDisplayViewBinder.class),
mKeyguardUnlockAnimationController,
mSecureSettings,
mExecutor,
@@ -195,11 +186,6 @@
mDumpManager,
mClockEventController,
mLogBuffer,
- mock(NotificationIconContainerAlwaysOnDisplayViewModel.class),
- mock(KeyguardRootViewModel.class),
- mock(ConfigurationState.class),
- mock(DozeParameters.class),
- mock(AlwaysOnDisplayNotificationIconViewStore.class),
KeyguardInteractorFactory.create(mFakeFeatureFlags).getKeyguardInteractor(),
mKeyguardClockInteractor,
mFakeFeatureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 91219f0..0ee0939 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -151,6 +151,7 @@
)
udfpsOverlayInteractor =
UdfpsOverlayInteractor(
+ context,
authController,
selectedUserInteractor,
testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index bbf471c..6a68672 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -108,6 +108,7 @@
private fun createUdpfsOverlayInteractor() {
underTest =
UdfpsOverlayInteractor(
+ context,
authController,
selectedUserInteractor,
testScope.backgroundScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
index 863d9eb..1b6aaab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
@@ -18,20 +18,17 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
-import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModelTransitionsMock
+import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.systemUIDialogManager
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -47,24 +44,14 @@
@SmallTest
@RunWith(JUnit4::class)
class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() {
- val kosmos =
+ private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
}
- val testScope = kosmos.testScope
-
- private val testDeviceEntryIconTransitionAlpha = MutableStateFlow(0f)
- private val testDeviceEntryIconTransition: DeviceEntryIconTransition
- get() =
- object : DeviceEntryIconTransition {
- override val deviceEntryParentViewAlpha: Flow<Float> =
- testDeviceEntryIconTransitionAlpha.asStateFlow()
- }
-
- init {
- kosmos.deviceEntryIconViewModelTransitionsMock.add(testDeviceEntryIconTransition)
- }
- val systemUIDialogManager = kosmos.systemUIDialogManager
+ private val systemUIDialogManager = kosmos.systemUIDialogManager
+ private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ private val testScope = kosmos.testScope
+ private val deviceEntryIconViewModelTransition = kosmos.fakeDeviceEntryIconViewModelTransition
private val underTest = kosmos.deviceEntryUdfpsTouchOverlayViewModel
@Captor
@@ -80,7 +67,7 @@
testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
- testDeviceEntryIconTransitionAlpha.value = 1f
+ deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(1f)
runCurrent()
verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture())
@@ -94,7 +81,7 @@
testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
- testDeviceEntryIconTransitionAlpha.value = .3f
+ deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(.3f)
runCurrent()
verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture())
@@ -108,7 +95,7 @@
testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
- testDeviceEntryIconTransitionAlpha.value = 1f
+ deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(1f)
runCurrent()
verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture())
@@ -116,4 +103,16 @@
assertThat(shouldHandleTouches).isTrue()
}
+
+ @Test
+ fun deviceEntryViewAlphaZero_alternateBouncerVisible_shouldHandleTouchesTrue() =
+ testScope.runTest {
+ val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
+
+ deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(0f)
+ runCurrent()
+
+ bouncerRepository.setAlternateVisible(true)
+ assertThat(shouldHandleTouches).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 7475235..6170e0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -128,6 +128,7 @@
)
udfpsOverlayInteractor =
UdfpsOverlayInteractor(
+ context,
authController,
selectedUserInteractor,
testScope.backgroundScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorTest.kt
deleted file mode 100644
index bfa3641..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorTest.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2023 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.common.domain.interactor
-
-import android.content.res.Configuration
-import android.graphics.Rect
-import android.testing.AndroidTestingRunner
-import android.view.Surface.ROTATION_0
-import android.view.Surface.ROTATION_90
-import android.view.Surface.Rotation
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
-import com.android.systemui.coroutines.collectValues
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-open class ConfigurationInteractorTest : SysuiTestCase() {
-
- private val testScope = TestScope()
-
- private val configurationController = FakeConfigurationController()
- private val configurationRepository =
- ConfigurationRepositoryImpl(
- configurationController,
- context,
- testScope.backgroundScope,
- mock()
- )
-
- private lateinit var configuration: Configuration
- private lateinit var underTest: ConfigurationInteractor
-
- @Before
- fun setUp() {
- configuration = context.resources.configuration
-
- val testableResources = context.getOrCreateTestableResources()
- testableResources.overrideConfiguration(configuration)
-
- underTest = ConfigurationInteractorImpl(configurationRepository)
- }
-
- @Test
- fun maxBoundsChange_emitsMaxBoundsChange() =
- testScope.runTest {
- val values by collectValues(underTest.naturalMaxBounds)
-
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
- updateDisplay(width = DISPLAY_WIDTH * 2, height = DISPLAY_HEIGHT * 3)
- runCurrent()
-
- assertThat(values)
- .containsExactly(
- Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT),
- Rect(0, 0, DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 3),
- )
- .inOrder()
- }
-
- @Test
- fun maxBoundsSameOnConfigChange_doesNotEmitMaxBoundsChange() =
- testScope.runTest {
- val values by collectValues(underTest.naturalMaxBounds)
-
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
-
- assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
- }
-
- @Test
- fun firstMaxBoundsChange_emitsMaxBoundsChange() =
- testScope.runTest {
- val values by collectValues(underTest.naturalMaxBounds)
-
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
-
- assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
- }
-
- @Test
- fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewMaxBoundsChange() =
- testScope.runTest {
- val values by collectValues(underTest.naturalMaxBounds)
-
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
- updateDisplay(width = DISPLAY_HEIGHT, height = DISPLAY_WIDTH, rotation = ROTATION_90)
- runCurrent()
-
- assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
- }
-
- private fun updateDisplay(
- width: Int = DISPLAY_WIDTH,
- height: Int = DISPLAY_HEIGHT,
- @Rotation rotation: Int = ROTATION_0
- ) {
- configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
- configuration.windowConfiguration.displayRotation = rotation
-
- configurationController.onConfigurationChanged(configuration)
- }
-
- private companion object {
- private const val DISPLAY_WIDTH = 100
- private const val DISPLAY_HEIGHT = 200
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
index c5c0208..9e007e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
@@ -16,14 +16,19 @@
package com.android.systemui.common.ui.domain.interactor
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.view.Surface
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -35,13 +40,16 @@
@RunWith(AndroidJUnit4::class)
class ConfigurationInteractorTest : SysuiTestCase() {
private lateinit var testScope: TestScope
+ private lateinit var configuration: Configuration
private lateinit var underTest: ConfigurationInteractor
private lateinit var configurationRepository: FakeConfigurationRepository
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
+ configuration = context.resources.configuration
+ val testableResources = context.getOrCreateTestableResources()
+ testableResources.overrideConfiguration(configuration)
configurationRepository = FakeConfigurationRepository()
testScope = TestScope()
underTest = ConfigurationInteractor(configurationRepository)
@@ -79,4 +87,79 @@
assertThat(dimensionPixelSizes!![resourceId1]).isEqualTo(pixelSize1)
assertThat(dimensionPixelSizes!![resourceId2]).isEqualTo(pixelSize2)
}
+
+ @Test
+ fun maxBoundsChange_emitsMaxBoundsChange() =
+ testScope.runTest {
+ val values by collectValues(underTest.naturalMaxBounds)
+
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+ updateDisplay(width = DISPLAY_WIDTH * 2, height = DISPLAY_HEIGHT * 3)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT),
+ Rect(0, 0, DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 3),
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun maxBoundsSameOnConfigChange_doesNotEmitMaxBoundsChange() =
+ testScope.runTest {
+ val values by collectValues(underTest.naturalMaxBounds)
+
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+
+ assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
+ }
+
+ @Test
+ fun firstMaxBoundsChange_emitsMaxBoundsChange() =
+ testScope.runTest {
+ val values by collectValues(underTest.naturalMaxBounds)
+
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+
+ assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
+ }
+
+ @Test
+ fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewMaxBoundsChange() =
+ testScope.runTest {
+ val values by collectValues(underTest.naturalMaxBounds)
+
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+ updateDisplay(
+ width = DISPLAY_HEIGHT,
+ height = DISPLAY_WIDTH,
+ rotation = Surface.ROTATION_90
+ )
+ runCurrent()
+
+ assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
+ }
+
+ private fun updateDisplay(
+ width: Int = DISPLAY_WIDTH,
+ height: Int = DISPLAY_HEIGHT,
+ @Surface.Rotation rotation: Int = Surface.ROTATION_0
+ ) {
+ configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
+ configuration.windowConfiguration.displayRotation = rotation
+
+ configurationRepository.onConfigurationChange(configuration)
+ }
+
+ private companion object {
+ private const val DISPLAY_WIDTH = 100
+ private const val DISPLAY_HEIGHT = 200
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
new file mode 100644
index 0000000..93ce86a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2023 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.deviceentry.domain.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.data.ui.viewmodel.udfpsAccessibilityOverlayViewModel
+import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) }
+ }
+ private val deviceEntryIconTransition = kosmos.fakeDeviceEntryIconViewModelTransition
+ private val testScope = kosmos.testScope
+ private val accessibilityRepository = kosmos.fakeAccessibilityRepository
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ private val deviceEntryFingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
+ private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+ private val shadeRepository = kosmos.fakeShadeRepository
+ private val underTest = kosmos.udfpsAccessibilityOverlayViewModel
+
+ @Test
+ fun visible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+ assertThat(visible).isTrue()
+ }
+
+ @Test
+ fun touchExplorationNotEnabled_overlayNotVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+ accessibilityRepository.isTouchExplorationEnabled.value = false
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun deviceEntryFgIconViewModelAod_overlayNotVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+
+ // AOD
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun deviceUnlocked_overlayNotVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+ deviceEntryRepository.setUnlocked(true)
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun deviceEntryViewAlpha0_overlayNotVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+ deviceEntryIconTransition.setDeviceEntryParentViewAlpha(0f)
+ assertThat(visible).isFalse()
+ }
+
+ private suspend fun setupVisibleStateOnLockscreen() {
+ // A11y enabled
+ accessibilityRepository.isTouchExplorationEnabled.value = true
+
+ // Transition alpha is 1f
+ deviceEntryIconTransition.setDeviceEntryParentViewAlpha(1f)
+
+ // Listening for UDFPS
+ fingerprintPropertyRepository.supportsUdfps()
+ deviceEntryFingerprintAuthRepository.setIsRunning(true)
+ deviceEntryRepository.setUnlocked(false)
+
+ // Lockscreen
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+
+ // Shade not expanded
+ shadeRepository.qsExpansion.value = 0f
+ shadeRepository.lockscreenShadeExpansion.value = 0f
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 3109e76..ad86ee9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection
import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import com.android.systemui.util.mockito.whenever
@@ -70,7 +71,8 @@
@Mock private lateinit var communalTutorialIndicatorSection: CommunalTutorialIndicatorSection
@Mock private lateinit var clockSection: ClockSection
@Mock private lateinit var smartspaceSection: SmartspaceSection
-
+ @Mock
+ private lateinit var udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -90,6 +92,7 @@
communalTutorialIndicatorSection,
clockSection,
smartspaceSection,
+ udfpsAccessibilityOverlaySection,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 22569e2..c864704 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -30,20 +30,15 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
-import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.gesture.TapGestureDetector
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -87,11 +82,6 @@
{ mock(DeviceEntryForegroundViewModel::class.java) },
{ mock(DeviceEntryBackgroundViewModel::class.java) },
{ falsingManager },
- { mock(AlternateBouncerViewModel::class.java) },
- { mock(NotificationShadeWindowController::class.java) },
- TestScope().backgroundScope,
- { mock(SwipeUpAnywhereGestureHandler::class.java) },
- { mock(TapGestureDetector::class.java) },
{ mock(VibratorHelper::class.java) },
)
}
@@ -177,21 +167,4 @@
assertThat(constraint.layout.topMargin).isEqualTo(5)
assertThat(constraint.layout.startMargin).isEqualTo(4)
}
-
- @Test
- fun deviceEntryIconViewIsAboveAlternateBouncerView() {
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
-
- val constraintLayout = ConstraintLayout(context, null)
- underTest.addViews(constraintLayout)
- assertThat(constraintLayout.childCount).isGreaterThan(0)
- val deviceEntryIconView = constraintLayout.getViewById(R.id.device_entry_icon_view)
- val alternateBouncerView = constraintLayout.getViewById(R.id.alternate_bouncer)
- assertThat(deviceEntryIconView).isNotNull()
- assertThat(alternateBouncerView).isNotNull()
-
- // device entry icon is above the alternate bouncer
- assertThat(constraintLayout.indexOfChild(deviceEntryIconView))
- .isGreaterThan(constraintLayout.indexOfChild(alternateBouncerView))
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
deleted file mode 100644
index 6512290..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2023 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.keyguard.ui.viewmodel
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@ExperimentalCoroutinesApi
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsAodViewModelTest : SysuiTestCase() {
- private val defaultPadding = 12
- private lateinit var underTest: UdfpsAodViewModel
-
- private lateinit var testScope: TestScope
- private lateinit var configRepository: FakeConfigurationRepository
- private lateinit var bouncerRepository: KeyguardBouncerRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var shadeRepository: FakeShadeRepository
- private lateinit var keyguardInteractor: KeyguardInteractor
-
- @Mock private lateinit var dialogManager: SystemUIDialogManager
- @Mock private lateinit var burnInHelper: BurnInHelperWrapper
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- overrideResource(com.android.systemui.res.R.dimen.lock_icon_padding, defaultPadding)
- testScope = TestScope()
- shadeRepository = FakeShadeRepository()
- KeyguardInteractorFactory.create().also {
- keyguardInteractor = it.keyguardInteractor
- keyguardRepository = it.repository
- configRepository = it.configurationRepository
- bouncerRepository = it.bouncerRepository
- }
- val udfpsKeyguardInteractor =
- UdfpsKeyguardInteractor(
- configRepository,
- BurnInInteractor(
- context,
- burnInHelper,
- testScope.backgroundScope,
- configRepository,
- keyguardInteractor,
- ),
- keyguardInteractor,
- shadeRepository,
- dialogManager,
- )
-
- underTest =
- UdfpsAodViewModel(
- udfpsKeyguardInteractor,
- context,
- )
- }
-
- @Test
- fun alphaAndVisibleUpdates_onDozeAmountChanges() =
- testScope.runTest {
- val alpha by collectLastValue(underTest.alpha)
- val visible by collectLastValue(underTest.isVisible)
-
- keyguardRepository.setDozeAmount(0f)
- runCurrent()
- assertThat(alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- keyguardRepository.setDozeAmount(.65f)
- runCurrent()
- assertThat(alpha).isEqualTo(.65f)
- assertThat(visible).isTrue()
-
- keyguardRepository.setDozeAmount(.23f)
- runCurrent()
- assertThat(alpha).isEqualTo(.23f)
- assertThat(visible).isTrue()
-
- keyguardRepository.setDozeAmount(1f)
- runCurrent()
- assertThat(alpha).isEqualTo(1f)
- assertThat(visible).isTrue()
- }
-
- @Test
- fun paddingUpdates_onScaleForResolutionChanges() =
- testScope.runTest {
- val padding by collectLastValue(underTest.padding)
-
- configRepository.setScaleForResolution(1f)
- runCurrent()
- assertThat(padding).isEqualTo(defaultPadding)
-
- configRepository.setScaleForResolution(2f)
- runCurrent()
- assertThat(padding).isEqualTo(defaultPadding * 2)
-
- configRepository.setScaleForResolution(.5f)
- runCurrent()
- assertThat(padding).isEqualTo((defaultPadding * .5f).toInt())
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
deleted file mode 100644
index 95b2fe5..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2023 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.keyguard.ui.viewmodel
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-/** Tests UdfpsFingerprintViewModel specific flows. */
-@ExperimentalCoroutinesApi
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsFingerprintViewModelTest : SysuiTestCase() {
- private val defaultPadding = 12
- private lateinit var underTest: FingerprintViewModel
-
- private lateinit var testScope: TestScope
- private lateinit var configRepository: FakeConfigurationRepository
- private lateinit var bouncerRepository: KeyguardBouncerRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var fakeCommandQueue: FakeCommandQueue
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
- private lateinit var shadeRepository: FakeShadeRepository
-
- @Mock private lateinit var burnInHelper: BurnInHelperWrapper
- @Mock private lateinit var dialogManager: SystemUIDialogManager
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- overrideResource(com.android.systemui.res.R.dimen.lock_icon_padding, defaultPadding)
- testScope = TestScope()
- configRepository = FakeConfigurationRepository()
- keyguardRepository = FakeKeyguardRepository()
- bouncerRepository = FakeKeyguardBouncerRepository()
- fakeCommandQueue = FakeCommandQueue()
- bouncerRepository = FakeKeyguardBouncerRepository()
- transitionRepository = FakeKeyguardTransitionRepository()
- shadeRepository = FakeShadeRepository()
- val keyguardInteractor =
- KeyguardInteractorFactory.create(
- repository = keyguardRepository,
- )
- .keyguardInteractor
-
- val transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = transitionRepository,
- keyguardInteractor = keyguardInteractor,
- )
- .keyguardTransitionInteractor
-
- underTest =
- FingerprintViewModel(
- context,
- transitionInteractor,
- UdfpsKeyguardInteractor(
- configRepository,
- BurnInInteractor(
- context,
- burnInHelper,
- testScope.backgroundScope,
- configRepository,
- keyguardInteractor,
- ),
- keyguardInteractor,
- shadeRepository,
- dialogManager,
- ),
- keyguardInteractor,
- )
- }
-
- @Test
- fun paddingUpdates_onScaleForResolutionChanges() =
- testScope.runTest {
- val padding by collectLastValue(underTest.padding)
-
- configRepository.setScaleForResolution(1f)
- runCurrent()
- assertThat(padding).isEqualTo(defaultPadding)
-
- configRepository.setScaleForResolution(2f)
- runCurrent()
- assertThat(padding).isEqualTo(defaultPadding * 2)
-
- configRepository.setScaleForResolution(.5f)
- runCurrent()
- assertThat(padding).isEqualTo((defaultPadding * .5).toInt())
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
deleted file mode 100644
index 848a94b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
+++ /dev/null
@@ -1,749 +0,0 @@
-/*
- * Copyright (C) 2023 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.keyguard.ui.viewmodel
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.settingslib.Utils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.android.wm.shell.animation.Interpolators
-import com.google.common.collect.Range
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.MockitoAnnotations
-
-/** Tests UDFPS lockscreen view model transitions. */
-@ExperimentalCoroutinesApi
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsLockscreenViewModelTest : SysuiTestCase() {
- private val lockscreenColorResId = android.R.attr.textColorPrimary
- private val alternateBouncerResId = com.android.internal.R.attr.materialColorOnPrimaryFixed
- private val lockscreenColor = Utils.getColorAttrDefaultColor(context, lockscreenColorResId)
- private val alternateBouncerColor =
- Utils.getColorAttrDefaultColor(context, alternateBouncerResId)
-
- @Mock private lateinit var dialogManager: SystemUIDialogManager
-
- private lateinit var underTest: UdfpsLockscreenViewModel
- private lateinit var testScope: TestScope
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
- private lateinit var configRepository: FakeConfigurationRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var keyguardInteractor: KeyguardInteractor
- private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
- private lateinit var shadeRepository: FakeShadeRepository
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- testScope = TestScope()
- transitionRepository = FakeKeyguardTransitionRepository()
- shadeRepository = FakeShadeRepository()
- KeyguardInteractorFactory.create().also {
- keyguardInteractor = it.keyguardInteractor
- keyguardRepository = it.repository
- configRepository = it.configurationRepository
- bouncerRepository = it.bouncerRepository
- }
-
- val transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = transitionRepository,
- keyguardInteractor = keyguardInteractor,
- )
- .keyguardTransitionInteractor
-
- underTest =
- UdfpsLockscreenViewModel(
- context,
- lockscreenColorResId,
- alternateBouncerResId,
- transitionInteractor,
- UdfpsKeyguardInteractor(
- configRepository,
- BurnInInteractor(
- context,
- burnInHelperWrapper = mock(),
- testScope.backgroundScope,
- configRepository,
- keyguardInteractor,
- ),
- keyguardInteractor,
- shadeRepository,
- dialogManager,
- ),
- keyguardInteractor,
- )
- }
-
- @Test
- fun goneToAodTransition() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: gone -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "goneToAodTransition",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- // TransitionState.RUNNING: gone -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "goneToAodTransition",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- // TransitionState.FINISHED: gone -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "goneToAodTransition",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun lockscreenToAod() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-
- // TransitionState.STARTED: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun lockscreenShadeLockedToAod() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
-
- // TransitionState.STARTED: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- // TransitionState.RUNNING: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- // TransitionState.FINISHED: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun aodToLockscreen() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: AOD -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "aodToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isFalse()
-
- // TransitionState.RUNNING: AOD -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "aodToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: AOD -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "aodToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
- }
-
- @Test
- fun lockscreenToAlternateBouncer() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-
- // TransitionState.STARTED: lockscreen -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "lockscreenToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: lockscreen -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "lockscreenToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: lockscreen -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "lockscreenToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
- }
-
- fun alternateBouncerToPrimaryBouncer() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: alternate bouncer -> primary bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.PRIMARY_BOUNCER,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "alternateBouncerToPrimaryBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: alternate bouncer -> primary bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.PRIMARY_BOUNCER,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "alternateBouncerToPrimaryBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: alternate bouncer -> primary bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.PRIMARY_BOUNCER,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "alternateBouncerToPrimaryBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isFalse()
- }
-
- fun alternateBouncerToAod() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: alternate bouncer -> aod
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "alternateBouncerToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: alternate bouncer -> aod
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.AOD,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "alternateBouncerToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: alternate bouncer -> aod
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "alternateBouncerToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun lockscreenToOccluded() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-
- // TransitionState.STARTED: lockscreen -> occluded
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.OCCLUDED,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "lockscreenToOccluded",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: lockscreen -> occluded
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.OCCLUDED,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "lockscreenToOccluded",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: lockscreen -> occluded
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.OCCLUDED,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "lockscreenToOccluded",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun occludedToLockscreen() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: occluded -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.LOCKSCREEN,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "occludedToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: occluded -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.LOCKSCREEN,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "occludedToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: occluded -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.LOCKSCREEN,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "occludedToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
- }
-
- @Test
- fun qsProgressChange() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- givenTransitionToLockscreenFinished()
-
- // qsExpansion = 0f
- shadeRepository.setQsExpansion(0f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(visible).isEqualTo(true)
-
- // qsExpansion = .25
- shadeRepository.setQsExpansion(.2f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(.6f)
- assertThat(visible).isEqualTo(true)
-
- // qsExpansion = .5
- shadeRepository.setQsExpansion(.5f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isEqualTo(false)
-
- // qsExpansion = 1
- shadeRepository.setQsExpansion(1f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isEqualTo(false)
- }
-
- @Test
- fun shadeExpansionChanged() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- givenTransitionToLockscreenFinished()
-
- // shadeExpansion = 0f
- shadeRepository.setUdfpsTransitionToFullShadeProgress(0f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(visible).isEqualTo(true)
-
- // shadeExpansion = .2
- shadeRepository.setUdfpsTransitionToFullShadeProgress(.2f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(.8f)
- assertThat(visible).isEqualTo(true)
-
- // shadeExpansion = .5
- shadeRepository.setUdfpsTransitionToFullShadeProgress(.5f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(.5f)
- assertThat(visible).isEqualTo(true)
-
- // shadeExpansion = 1
- shadeRepository.setUdfpsTransitionToFullShadeProgress(1f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isEqualTo(false)
- }
-
- @Test
- fun dialogHideAffordancesRequestChanged() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- givenTransitionToLockscreenFinished()
- runCurrent()
- val captor = argumentCaptor<SystemUIDialogManager.Listener>()
- Mockito.verify(dialogManager).registerListener(captor.capture())
-
- captor.value.shouldHideAffordances(true)
- assertThat(transition?.alpha).isEqualTo(0f)
-
- captor.value.shouldHideAffordances(false)
- assertThat(transition?.alpha).isEqualTo(1f)
- }
-
- @Test
- fun occludedToAlternateBouncer() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: occluded -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "occludedToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(0f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: occluded -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "occludedToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale)
- .isEqualTo(Interpolators.FAST_OUT_SLOW_IN.getInterpolation(.6f))
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: occluded -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "occludedToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
- }
-
- private suspend fun givenTransitionToLockscreenFinished() {
- transitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- testScope
- )
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 1f99303..0a464e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -22,11 +22,11 @@
import android.testing.TestableLooper
import android.view.View
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.res.R
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionViewState
@@ -37,6 +37,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.floatThat
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -183,10 +184,12 @@
// detail widgets occupy [90, 100]
whenever(detailWidgetState.y).thenReturn(90F)
whenever(detailWidgetState.height).thenReturn(10)
+ whenever(detailWidgetState.alpha).thenReturn(1F)
// control widgets occupy [150, 170]
whenever(controlWidgetState.y).thenReturn(150F)
whenever(controlWidgetState.height).thenReturn(20)
- // in current beizer, when the progress reach 0.38, the result will be 0.5
+ whenever(controlWidgetState.alpha).thenReturn(1F)
+ // in current bezier, when the progress reach 0.38, the result will be 0.5
mediaViewController.squishViewState(mockViewState, 181.4F / 200F)
verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
@@ -196,6 +199,34 @@
}
@Test
+ fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_invisibleElements() {
+ whenever(mockViewState.copy()).thenReturn(mockCopiedState)
+ whenever(mockCopiedState.widgetStates)
+ .thenReturn(
+ mutableMapOf(
+ R.id.media_progress_bar to controlWidgetState,
+ R.id.header_artist to detailWidgetState
+ )
+ )
+ whenever(mockCopiedState.measureHeight).thenReturn(200)
+ // detail widgets occupy [90, 100]
+ whenever(detailWidgetState.y).thenReturn(90F)
+ whenever(detailWidgetState.height).thenReturn(10)
+ whenever(detailWidgetState.alpha).thenReturn(0F)
+ // control widgets occupy [150, 170]
+ whenever(controlWidgetState.y).thenReturn(150F)
+ whenever(controlWidgetState.height).thenReturn(20)
+ whenever(controlWidgetState.alpha).thenReturn(0F)
+ // Verify that alpha remains 0 throughout squishing
+ mediaViewController.squishViewState(mockViewState, 181.4F / 200F)
+ verify(controlWidgetState, never()).alpha = floatThat { it > 0 }
+ verify(detailWidgetState, never()).alpha = floatThat { it > 0 }
+ mediaViewController.squishViewState(mockViewState, 200F / 200F)
+ verify(controlWidgetState, never()).alpha = floatThat { it > 0 }
+ verify(detailWidgetState, never()).alpha = floatThat { it > 0 }
+ }
+
+ @Test
fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forRecommendation() {
whenever(mockViewState.copy()).thenReturn(mockCopiedState)
whenever(mockCopiedState.widgetStates)
@@ -210,12 +241,15 @@
// media container widgets occupy [20, 300]
whenever(mediaContainerWidgetState.y).thenReturn(20F)
whenever(mediaContainerWidgetState.height).thenReturn(280)
+ whenever(mediaContainerWidgetState.alpha).thenReturn(1F)
// media title widgets occupy [320, 330]
whenever(mediaTitleWidgetState.y).thenReturn(320F)
whenever(mediaTitleWidgetState.height).thenReturn(10)
+ whenever(mediaTitleWidgetState.alpha).thenReturn(1F)
// media subtitle widgets occupy [340, 350]
whenever(mediaSubTitleWidgetState.y).thenReturn(340F)
whenever(mediaSubTitleWidgetState.height).thenReturn(10)
+ whenever(mediaSubTitleWidgetState.alpha).thenReturn(1F)
// in current beizer, when the progress reach 0.38, the result will be 0.5
mediaViewController.squishViewState(mockViewState, 307.6F / 360F)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 4d42324..b7618d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -75,7 +75,6 @@
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
@@ -463,7 +462,6 @@
// region setNoteTaskShortcutEnabled
@Test
- @Ignore("b/316332684")
fun setNoteTaskShortcutEnabled_setTrue() {
createNoteTaskController().setNoteTaskShortcutEnabled(value = true, userTracker.userHandle)
@@ -480,7 +478,6 @@
}
@Test
- @Ignore("b/316332684")
fun setNoteTaskShortcutEnabled_setFalse() {
createNoteTaskController().setNoteTaskShortcutEnabled(value = false, userTracker.userHandle)
@@ -497,7 +494,6 @@
}
@Test
- @Ignore("b/316332684")
fun setNoteTaskShortcutEnabled_workProfileUser_setTrue() {
whenever(context.createContextAsUser(eq(workUserInfo.userHandle), any()))
.thenReturn(workProfileContext)
@@ -519,7 +515,6 @@
}
@Test
- @Ignore("b/316332684")
fun setNoteTaskShortcutEnabled_workProfileUser_setFalse() {
whenever(context.createContextAsUser(eq(workUserInfo.userHandle), any()))
.thenReturn(workProfileContext)
@@ -738,7 +733,6 @@
// region internalUpdateNoteTaskAsUser
@Test
- @Ignore("b/316332684")
fun updateNoteTaskAsUserInternal_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
createNoteTaskController(isEnabled = true)
.launchUpdateNoteTaskAsUser(userTracker.userHandle)
@@ -772,7 +766,6 @@
}
@Test
- @Ignore("b/316332684")
fun updateNoteTaskAsUserInternal_noNotesRole_shouldDisableShortcuts() {
whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle))
.thenReturn(emptyList())
@@ -796,7 +789,6 @@
}
@Test
- @Ignore("b/316332684")
fun updateNoteTaskAsUserInternal_flagDisabled_shouldDisableShortcuts() {
createNoteTaskController(isEnabled = false)
.launchUpdateNoteTaskAsUser(userTracker.userHandle)
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 5e2423a..ef7798e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -447,10 +447,6 @@
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles_default, "spec1,spec1");
List<String> specs = QSTileHost.loadTileSpecs(mContext, "default");
-
- // Remove spurious tiles, like dbg:mem
- specs.removeIf(spec -> !"spec1".equals(spec));
- assertEquals(1, specs.size());
}
@Test
@@ -458,10 +454,6 @@
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles_default, "spec1");
List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1");
-
- // Remove spurious tiles, like dbg:mem
- specs.removeIf(spec -> !"spec1".equals(spec));
- assertEquals(1, specs.size());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
index 067218a..5201e5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -50,7 +50,6 @@
import com.android.systemui.qs.tiles.ScreenRecordTile
import com.android.systemui.qs.tiles.UiModeNightTile
import com.android.systemui.qs.tiles.WorkModeTile
-import com.android.systemui.util.leak.GarbageMonitor
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -117,7 +116,6 @@
@Mock private lateinit var dataSaverTile: DataSaverTile
@Mock private lateinit var nightDisplayTile: NightDisplayTile
@Mock private lateinit var nfcTile: NfcTile
- @Mock private lateinit var memoryTile: GarbageMonitor.MemoryTile
@Mock private lateinit var darkModeTile: UiModeNightTile
@Mock private lateinit var screenRecordTile: ScreenRecordTile
@Mock private lateinit var reduceBrightColorsTile: ReduceBrightColorsTile
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index b90ccc0..9941661 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,35 +16,23 @@
package com.android.systemui.scene.shared.flag
-import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.FakeFeatureFlagsImpl
-import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.compose.ComposeFacade
import com.android.systemui.flags.Flags
-import com.android.systemui.flags.ReleasedFlag
-import com.android.systemui.flags.ResourceBooleanFlag
-import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.flags.setFlagValue
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
-import com.android.systemui.res.R
-import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth
+import org.junit.Assume
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
@SmallTest
-@RunWith(Parameterized::class)
-internal class SceneContainerFlagsTest(
- private val testCase: TestCase,
-) : SysuiTestCase() {
-
- @Rule @JvmField val setFlagsRule: SetFlagsRule = SetFlagsRule()
-
- private lateinit var underTest: SceneContainerFlags
+@RunWith(AndroidJUnit4::class)
+internal class SceneContainerFlagsTest : SysuiTestCase() {
@Before
fun setUp() {
@@ -52,83 +40,39 @@
// Flags.SCENE_CONTAINER_ENABLED is no longer needed.
val field = Flags::class.java.getField("SCENE_CONTAINER_ENABLED")
field.isAccessible = true
- field.set(null, true)
+ field.set(null, true) // note: this does not work with multivalent tests
+ }
- val featureFlags =
- FakeFeatureFlagsClassic().apply {
- SceneContainerFlagsImpl.classicFlagTokens.forEach { flagToken ->
- when (flagToken) {
- is ResourceBooleanFlag -> set(flagToken, testCase.areAllFlagsSet)
- is ReleasedFlag -> set(flagToken, testCase.areAllFlagsSet)
- is UnreleasedFlag -> set(flagToken, testCase.areAllFlagsSet)
- else -> error("Unsupported flag type ${flagToken.javaClass}")
- }
- }
- }
- // TODO(b/306421592): get the aconfig FeatureFlags from the SetFlagsRule.
- val aconfigFlags = FakeFeatureFlagsImpl()
-
+ private fun setAconfigFlagsEnabled(enabled: Boolean) {
listOf(
- AconfigFlags.FLAG_SCENE_CONTAINER,
- AconfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
+ com.android.systemui.Flags.FLAG_SCENE_CONTAINER,
+ com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
KeyguardShadeMigrationNssl.FLAG_NAME,
MediaInSceneContainerFlag.FLAG_NAME,
)
- .forEach { flagToken ->
- setFlagsRule.enableFlags(flagToken)
- aconfigFlags.setFlag(flagToken, testCase.areAllFlagsSet)
- overrideResource(
- R.bool.config_sceneContainerFrameworkEnabled,
- testCase.isResourceConfigEnabled
- )
- }
-
- underTest =
- SceneContainerFlagsImpl(
- context = context,
- featureFlagsClassic = featureFlags,
- isComposeAvailable = testCase.isComposeAvailable,
- )
+ .forEach { flagName -> mSetFlagsRule.setFlagValue(flagName, enabled) }
}
@Test
- fun isEnabled() {
- assertThat(underTest.isEnabled()).isEqualTo(testCase.expectedEnabled)
+ fun isNotEnabled_withoutAconfigFlags() {
+ setAconfigFlagsEnabled(false)
+ Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
+ Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
}
- internal data class TestCase(
- val isComposeAvailable: Boolean,
- val areAllFlagsSet: Boolean,
- val isResourceConfigEnabled: Boolean,
- val expectedEnabled: Boolean,
- ) {
- override fun toString(): String {
- return "(compose=$isComposeAvailable + flags=$areAllFlagsSet) + XML" +
- " config=$isResourceConfigEnabled -> expected=$expectedEnabled"
- }
+ @Test
+ fun isEnabled_withAconfigFlags_withCompose() {
+ Assume.assumeTrue(ComposeFacade.isComposeAvailable())
+ setAconfigFlagsEnabled(true)
+ Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(true)
+ Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(true)
}
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun testCases() = buildList {
- repeat(8) { combination ->
- val isComposeAvailable = combination and 0b100 != 0
- val areAllFlagsSet = combination and 0b010 != 0
- val isResourceConfigEnabled = combination and 0b001 != 0
-
- val expectedEnabled =
- isComposeAvailable && areAllFlagsSet && isResourceConfigEnabled
-
- add(
- TestCase(
- isComposeAvailable = isComposeAvailable,
- areAllFlagsSet = areAllFlagsSet,
- expectedEnabled = expectedEnabled,
- isResourceConfigEnabled = isResourceConfigEnabled,
- )
- )
- }
- }
+ @Test
+ fun isNotEnabled_withAconfigFlags_withoutCompose() {
+ Assume.assumeFalse(ComposeFacade.isComposeAvailable())
+ setAconfigFlagsEnabled(true)
+ Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
+ Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
index d4e8d37..72fc65b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
@@ -10,8 +10,6 @@
import androidx.constraintlayout.widget.Guideline
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
@@ -39,7 +37,6 @@
lateinit var detectionNoticeView: ViewGroup
lateinit var container: FrameLayout
- var featureFlags = FakeFeatureFlags()
lateinit var screenshotView: ViewGroup
val userHandle = UserHandle.of(5)
@@ -55,7 +52,6 @@
MessageContainerController(
workProfileMessageController,
screenshotDetectionController,
- featureFlags
)
screenshotView = ConstraintLayout(mContext)
workProfileData = WorkProfileMessageController.WorkProfileFirstRunData(appName, icon)
@@ -105,8 +101,6 @@
@Test
fun testOnScreenshotTakenScreenshotData_nothingToShow() {
- featureFlags.set(Flags.SCREENSHOT_DETECTION, true)
-
messageContainer.onScreenshotTaken(screenshotData)
verify(workProfileMessageController, never()).populateView(any(), any(), any())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 9d997da..86d8d54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -33,7 +33,6 @@
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
@@ -68,9 +67,12 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.log.BouncerLogger
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.DragDownHelper
@@ -79,6 +81,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.gesture.TapGestureDetector
import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.stack.AmbientState
@@ -93,6 +96,7 @@
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
@@ -126,8 +130,6 @@
@Mock private lateinit var centralSurfaces: CentralSurfaces
@Mock private lateinit var dozeServiceHost: DozeServiceHost
@Mock private lateinit var dozeScrimController: DozeScrimController
- @Mock private lateinit var backActionInteractor: BackActionInteractor
- @Mock private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var dockManager: DockManager
@Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
@Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
@@ -275,6 +277,12 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
mSelectedUserInteractor,
+ { mock (JavaAdapter::class.java )},
+ { mock(AlternateBouncerViewModel::class.java) },
+ { mock(FalsingManager::class.java) },
+ { mock(SwipeUpAnywhereGestureHandler::class.java) },
+ { mock(TapGestureDetector::class.java) },
+ { mock(AlternateBouncerUdfpsIconViewModel::class.java) },
)
underTest.setupExpandedStatusBar()
underTest.setDragDownHelper(dragDownHelper)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 9750f60..d9ff892 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -55,8 +55,12 @@
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.log.BouncerLogger
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.DragDownHelper
@@ -65,6 +69,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.gesture.TapGestureDetector
import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.stack.AmbientState
@@ -79,12 +84,14 @@
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -99,6 +106,7 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@ExperimentalCoroutinesApi
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -259,6 +267,12 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
mSelectedUserInteractor,
+ { Mockito.mock(JavaAdapter::class.java) },
+ { Mockito.mock(AlternateBouncerViewModel::class.java) },
+ { Mockito.mock(FalsingManager::class.java) },
+ { Mockito.mock(SwipeUpAnywhereGestureHandler::class.java) },
+ { Mockito.mock(TapGestureDetector::class.java) },
+ { Mockito.mock(AlternateBouncerUdfpsIconViewModel::class.java) },
)
controller.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
new file mode 100644
index 0000000..80f8cf1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.notifications.data.repository
+
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.shared.settings.data.repository.FakeSecureSettingsRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class NotificationSettingsRepositoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: NotificationSettingsRepository
+
+ private lateinit var testScope: TestScope
+ private lateinit var secureSettingsRepository: FakeSecureSettingsRepository
+
+ @Before
+ fun setUp() {
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ secureSettingsRepository = FakeSecureSettingsRepository()
+
+ underTest =
+ NotificationSettingsRepository(
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
+ secureSettingsRepository = secureSettingsRepository,
+ )
+ }
+
+ @Test
+ fun testGetIsShowNotificationsOnLockscreenEnabled() =
+ testScope.runTest {
+ val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled)
+
+ secureSettingsRepository.set(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ value = 1,
+ )
+ assertThat(showNotifs).isEqualTo(true)
+
+ secureSettingsRepository.set(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ value = 0,
+ )
+ assertThat(showNotifs).isEqualTo(false)
+ }
+
+ @Test
+ fun testSetIsShowNotificationsOnLockscreenEnabled() =
+ testScope.runTest {
+ val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled)
+
+ underTest.setShowNotificationsOnLockscreenEnabled(true)
+ assertThat(showNotifs).isEqualTo(true)
+
+ underTest.setShowNotificationsOnLockscreenEnabled(false)
+ assertThat(showNotifs).isEqualTo(false)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index a6180ec..f178046 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -54,6 +54,7 @@
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationContentDescription;
import org.junit.Before;
import org.junit.Rule;
@@ -183,7 +184,7 @@
.build();
// should be ApplicationInfo
n.extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, new Bundle());
- StatusBarIconView.contentDescForNotification(mContext, n);
+ NotificationContentDescription.contentDescForNotification(mContext, n);
// no crash, good
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
index a56fb2c..7d8cf36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
@@ -1,11 +1,10 @@
package com.android.systemui.statusbar.notification
+import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import org.junit.Assert.assertEquals
@@ -20,8 +19,7 @@
@RunWith(JUnit4::class)
class RoundableTest : SysuiTestCase() {
private val targetView: View = mock()
- private val featureFlags = FakeFeatureFlags()
- private val roundable = FakeRoundable(targetView = targetView, featureFlags = featureFlags)
+ private val roundable = FakeRoundable(targetView = targetView)
@Test
fun defaultConfig_shouldNotHaveRoundedCorner() {
@@ -150,36 +148,36 @@
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_radius_maxed_to_height() {
whenever(targetView.height).thenReturn(10)
- featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
roundable.requestRoundness(1f, 1f, SOURCE1)
assertCornerRadiiEquals(5f, 5f)
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_topRadius_maxed_to_height() {
whenever(targetView.height).thenReturn(5)
- featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
roundable.requestRoundness(1f, 0f, SOURCE1)
assertCornerRadiiEquals(5f, 0f)
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_bottomRadius_maxed_to_height() {
whenever(targetView.height).thenReturn(5)
- featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
roundable.requestRoundness(0f, 1f, SOURCE1)
assertCornerRadiiEquals(0f, 5f)
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_radii_kept() {
whenever(targetView.height).thenReturn(100)
- featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
roundable.requestRoundness(1f, 1f, SOURCE1)
assertCornerRadiiEquals(MAX_RADIUS, MAX_RADIUS)
@@ -193,14 +191,12 @@
class FakeRoundable(
targetView: View,
radius: Float = MAX_RADIUS,
- featureFlags: FeatureFlags
) : Roundable {
override val roundableState =
RoundableState(
targetView = targetView,
roundable = this,
maxRadius = radius,
- featureFlags = featureFlags
)
override val clipHeight: Int
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
index f3094cd..170f651 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
@@ -80,7 +80,7 @@
assertThat(isPulseExpanding).isFalse()
withArgCaptor { verify(mockWakeUpCoordinator).addListener(capture()) }
- .onPulseExpansionChanged(true)
+ .onPulseExpandingChanged(true)
runCurrent()
assertThat(isPulseExpanding).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index cb73108..0cd834d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -57,7 +57,6 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -568,9 +567,6 @@
NotificationEntry entry,
@InflationFlag int extraInflationFlags)
throws Exception {
- // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be
- // set, but we do not want to override an existing value that is needed by a specific test.
- mFeatureFlags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS);
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
mContext.LAYOUT_INFLATER_SERVICE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index c8dbdc5..2df6e46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -28,8 +28,8 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
/** Tests for {@link NotificationShelf}. */
@SmallTest
@@ -53,7 +53,6 @@
MockitoAnnotations.initMocks(this)
mDependency.injectTestDependency(FeatureFlags::class.java, flags)
flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal)
- flags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS)
val root = FrameLayout(context)
shelf =
LayoutInflater.from(root.context)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index ba5ba2c..ad7dee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -82,7 +82,6 @@
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
-import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -116,7 +115,6 @@
private AmbientState mAmbientState;
private TestableResources mTestableResources;
@Rule public MockitoRule mockito = MockitoJUnit.rule();
- @Mock private NotificationsController mNotificationsController;
@Mock private SysuiStatusBarStateController mBarState;
@Mock private GroupMembershipManager mGroupMembershipManger;
@Mock private GroupExpansionManager mGroupExpansionManager;
@@ -193,7 +191,7 @@
mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper,
mNotificationStackSizeCalculator);
mStackScroller = spy(mStackScrollerInternal);
- mStackScroller.setNotificationsController(mNotificationsController);
+ mStackScroller.setResetUserExpandedStatesRunnable(()->{});
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
when(mStackScrollLayoutController.getNotificationRoundnessManager())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 08ef477..f266f03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -2,21 +2,28 @@
import android.annotation.DimenRes
import android.content.pm.PackageManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.res.R
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.EmptyShadeView
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.RoundableState
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView.FooterViewState
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Expect
@@ -24,6 +31,7 @@
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
@@ -37,22 +45,26 @@
@SmallTest
class StackScrollAlgorithmTest : SysuiTestCase() {
- @JvmField @Rule
- var expect: Expect = Expect.create()
+ @JvmField @Rule var expect: Expect = Expect.create()
private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
private val hostView = FrameLayout(context)
private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
private val notificationRow = mock<ExpandableNotificationRow>()
+ private val notificationEntry = mock<NotificationEntry>()
private val dumpManager = mock<DumpManager>()
+ @OptIn(ExperimentalCoroutinesApi::class)
private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
private val notificationShelf = mock<NotificationShelf>()
- private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
- layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
- }
- private val footerView = FooterView(context, /*attrs=*/null)
- private val ambientState = AmbientState(
+ private val emptyShadeView =
+ EmptyShadeView(context, /* attrs= */ null).apply {
+ layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
+ }
+ private val footerView = FooterView(context, /*attrs=*/ null)
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private val ambientState =
+ AmbientState(
context,
dumpManager,
/* sectionProvider */ { _, _ -> false },
@@ -62,13 +74,14 @@
)
private val testableResources = mContext.getOrCreateTestableResources()
+ private val featureFlags = mock<FeatureFlagsClassic>()
private val maxPanelHeight =
mContext.resources.displayMetrics.heightPixels -
- px(R.dimen.notification_panel_margin_top) -
- px(R.dimen.notification_panel_margin_bottom)
+ px(R.dimen.notification_panel_margin_top) -
+ px(R.dimen.notification_panel_margin_bottom)
private fun px(@DimenRes id: Int): Float =
- testableResources.resources.getDimensionPixelSize(id).toFloat()
+ testableResources.resources.getDimensionPixelSize(id).toFloat()
private val bigGap = px(R.dimen.notification_section_divider_height)
private val smallGap = px(R.dimen.notification_section_divider_height_lockscreen)
@@ -76,9 +89,12 @@
@Before
fun setUp() {
Assume.assumeFalse(isTv())
-
+ mDependency.injectTestDependency(FeatureFlags::class.java, featureFlags)
whenever(notificationShelf.viewState).thenReturn(ExpandableViewState())
whenever(notificationRow.viewState).thenReturn(ExpandableViewState())
+ whenever(notificationRow.entry).thenReturn(notificationEntry)
+ whenever(notificationRow.roundableState)
+ .thenReturn(RoundableState(notificationRow, notificationRow, 0f))
ambientState.isSmallScreen = true
hostView.addView(notificationRow)
@@ -92,7 +108,7 @@
fun resetViewStates_defaultHun_yTranslationIsInset() {
whenever(notificationRow.isPinned).thenReturn(true)
whenever(notificationRow.isHeadsUp).thenReturn(true)
- resetViewStates_hunYTranslationIsInset()
+ resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
}
@Test
@@ -103,18 +119,87 @@
}
@Test
+ @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun resetViewStates_hunAnimatingAway_yTranslationIsInset() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
- resetViewStates_hunYTranslationIsInset()
+ resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
}
@Test
+ @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun resetViewStates_hunAnimatingAway_StackMarginChangesHunYTranslation() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
resetViewStates_stackMargin_changesHunYTranslation()
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_defaultHun_newHeadsUpAnim_yTranslationIsInset() {
+ whenever(notificationRow.isPinned).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+ resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_defaultHunWithStackMargin_newHeadsUpAnim_changesHunYTranslation() {
+ whenever(notificationRow.isPinned).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+ resetViewStates_stackMargin_changesHunYTranslation()
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_defaultHun_showingQS_newHeadsUpAnim_hunTranslatedToMax() {
+ // Given: the shade is open and scrolled to the bottom to show the QuickSettings
+ val maxHunTranslation = 2000f
+ ambientState.maxHeadsUpTranslation = maxHunTranslation
+ ambientState.setLayoutMinHeight(2500) // Mock the height of shade
+ ambientState.stackY = 2500f // Scroll over the max translation
+ stackScrollAlgorithm.setIsExpanded(true) // Mark the shade open
+ whenever(notificationRow.mustStayOnScreen()).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+ whenever(notificationRow.isAboveShelf).thenReturn(true)
+
+ resetViewStates_hunYTranslationIs(maxHunTranslation)
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAway_showingQS_newHeadsUpAnim_hunTranslatedToBottomOfScreen() {
+ // Given: the shade is open and scrolled to the bottom to show the QuickSettings
+ val bottomOfScreen = 2600f
+ val maxHunTranslation = 2000f
+ ambientState.maxHeadsUpTranslation = maxHunTranslation
+ ambientState.setLayoutMinHeight(2500) // Mock the height of shade
+ ambientState.stackY = 2500f // Scroll over the max translation
+ stackScrollAlgorithm.setIsExpanded(true) // Mark the shade open
+ stackScrollAlgorithm.setHeadsUpAppearHeightBottom(bottomOfScreen.toInt())
+ whenever(notificationRow.mustStayOnScreen()).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+ whenever(notificationRow.isAboveShelf).thenReturn(true)
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+
+ resetViewStates_hunYTranslationIs(
+ expected = bottomOfScreen + stackScrollAlgorithm.mHeadsUpAppearStartAboveScreen
+ )
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAway_newHeadsUpAnim_hunTranslatedToTopOfScreen() {
+ val topMargin = 100f
+ ambientState.maxHeadsUpTranslation = 2000f
+ ambientState.stackTopMargin = topMargin.toInt()
+ whenever(notificationRow.intrinsicHeight).thenReturn(100)
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+
+ resetViewStates_hunYTranslationIs(
+ expected = -topMargin - stackScrollAlgorithm.mHeadsUpAppearStartAboveScreen
+ )
+ }
+
+ @Test
fun resetViewStates_hunAnimatingAway_bottomNotClipped() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
@@ -136,6 +221,7 @@
}
@Test
+ @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun resetViewStates_hunsOverlappingAndBottomHunAnimatingAway_bottomHunClipped() {
val topHun = mockExpandableNotificationRow()
val bottomHun = mockExpandableNotificationRow()
@@ -156,7 +242,7 @@
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
val marginBottom =
- context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
+ context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY
val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f
assertThat(emptyShadeView.viewState.yTranslation).isEqualTo(centeredY)
@@ -174,33 +260,37 @@
assertThat(notificationRow.viewState.alpha).isEqualTo(1f)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun resetViewStates_expansionChanging_notificationBecomesTransparent() {
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction = 0.25f,
- expectedAlpha = 0.0f
+ expansionFraction = 0.25f,
+ expectedAlpha = 0.0f
)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun resetViewStates_expansionChangingWhileBouncerInTransit_viewBecomesTransparent() {
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction = 0.85f,
- expectedAlpha = 0.0f
+ expansionFraction = 0.85f,
+ expectedAlpha = 0.0f
)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun resetViewStates_expansionChanging_notificationAlphaUpdated() {
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction = 0.6f,
- expectedAlpha = getContentAlpha(0.6f)
+ expansionFraction = 0.6f,
+ expectedAlpha = getContentAlpha(0.6f)
)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun resetViewStates_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
val expansionFraction = 0.6f
@@ -216,13 +306,14 @@
)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun expansionChanging_largeScreen_bouncerInTransit_alphaUpdated_bouncerValues() {
ambientState.isSmallScreen = false
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction = 0.95f,
- expectedAlpha = aboutToShowBouncerProgress(0.95f),
+ expansionFraction = 0.95f,
+ expectedAlpha = aboutToShowBouncerProgress(0.95f),
)
}
@@ -235,10 +326,8 @@
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
- verify(notificationShelf).updateState(
- /* algorithmState= */any(),
- /* ambientState= */eq(ambientState)
- )
+ verify(notificationShelf)
+ .updateState(/* algorithmState= */ any(), /* ambientState= */ eq(ambientState))
}
@Test
@@ -397,22 +486,31 @@
@Test
fun getGapForLocation_onLockscreen_returnsSmallGap() {
- val gap = stackScrollAlgorithm.getGapForLocation(
- /* fractionToShade= */ 0f, /* onKeyguard= */ true)
+ val gap =
+ stackScrollAlgorithm.getGapForLocation(
+ /* fractionToShade= */ 0f,
+ /* onKeyguard= */ true
+ )
assertThat(gap).isEqualTo(smallGap)
}
@Test
fun getGapForLocation_goingToShade_interpolatesGap() {
- val gap = stackScrollAlgorithm.getGapForLocation(
- /* fractionToShade= */ 0.5f, /* onKeyguard= */ true)
+ val gap =
+ stackScrollAlgorithm.getGapForLocation(
+ /* fractionToShade= */ 0.5f,
+ /* onKeyguard= */ true
+ )
assertThat(gap).isEqualTo(smallGap * 0.5f + bigGap * 0.5f)
}
@Test
fun getGapForLocation_notOnLockscreen_returnsBigGap() {
- val gap = stackScrollAlgorithm.getGapForLocation(
- /* fractionToShade= */ 0f, /* onKeyguard= */ false)
+ val gap =
+ stackScrollAlgorithm.getGapForLocation(
+ /* fractionToShade= */ 0f,
+ /* onKeyguard= */ false
+ )
assertThat(gap).isEqualTo(bigGap)
}
@@ -469,12 +567,14 @@
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = false
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ true,
- /* mustStayOnScreen= */ true,
- /* isViewEndVisible= */ true,
- /* viewEnd= */ 0f,
- /* maxHunY= */ 10f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 0f,
+ /* maxHunY= */ 10f
+ )
assertTrue(expandableViewState.headsUpIsVisible)
}
@@ -484,12 +584,14 @@
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = true
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ true,
- /* mustStayOnScreen= */ true,
- /* isViewEndVisible= */ true,
- /* viewEnd= */ 10f,
- /* maxHunY= */ 0f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 0f
+ )
assertFalse(expandableViewState.headsUpIsVisible)
}
@@ -499,12 +601,14 @@
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = true
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ false,
- /* mustStayOnScreen= */ true,
- /* isViewEndVisible= */ true,
- /* viewEnd= */ 10f,
- /* maxHunY= */ 1f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ false,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f
+ )
assertTrue(expandableViewState.headsUpIsVisible)
}
@@ -514,12 +618,14 @@
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = true
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ true,
- /* mustStayOnScreen= */ false,
- /* isViewEndVisible= */ true,
- /* viewEnd= */ 10f,
- /* maxHunY= */ 1f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ false,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f
+ )
assertTrue(expandableViewState.headsUpIsVisible)
}
@@ -529,12 +635,14 @@
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = true
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ true,
- /* mustStayOnScreen= */ true,
- /* isViewEndVisible= */ false,
- /* viewEnd= */ 10f,
- /* maxHunY= */ 1f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ false,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f
+ )
assertTrue(expandableViewState.headsUpIsVisible)
}
@@ -544,9 +652,12 @@
val expandableViewState = ExpandableViewState()
expandableViewState.yTranslation = 50f
- stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
- /* stackTranslation= */ 0f,
- /* collapsedHeight= */ 1f, expandableViewState)
+ stackScrollAlgorithm.clampHunToTop(
+ /* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 1f,
+ expandableViewState
+ )
// qqs (10 + 0) < viewY (50)
assertEquals(50f, expandableViewState.yTranslation)
@@ -557,9 +668,12 @@
val expandableViewState = ExpandableViewState()
expandableViewState.yTranslation = -10f
- stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
- /* stackTranslation= */ 0f,
- /* collapsedHeight= */ 1f, expandableViewState)
+ stackScrollAlgorithm.clampHunToTop(
+ /* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 1f,
+ expandableViewState
+ )
// qqs (10 + 0) > viewY (-10)
assertEquals(10f, expandableViewState.yTranslation)
@@ -571,9 +685,12 @@
expandableViewState.height = 20
expandableViewState.yTranslation = -100f
- stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
- /* stackTranslation= */ 0f,
- /* collapsedHeight= */ 10f, expandableViewState)
+ stackScrollAlgorithm.clampHunToTop(
+ /* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 10f,
+ expandableViewState
+ )
// newTranslation = max(10, -100) = 10
// distToRealY = 10 - (-100f) = 110
@@ -587,9 +704,12 @@
expandableViewState.height = 20
expandableViewState.yTranslation = 5f
- stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
- /* stackTranslation= */ 0f,
- /* collapsedHeight= */ 10f, expandableViewState)
+ stackScrollAlgorithm.clampHunToTop(
+ /* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 10f,
+ expandableViewState
+ )
// newTranslation = max(10, 5) = 10
// distToRealY = 10 - 5 = 5
@@ -599,41 +719,49 @@
@Test
fun computeCornerRoundnessForPinnedHun_stackBelowScreen_round() {
- val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ val currentRoundness =
+ stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
/* hostViewHeight= */ 100f,
/* stackY= */ 110f,
/* viewMaxHeight= */ 20f,
- /* originalCornerRoundness= */ 0f)
+ /* originalCornerRoundness= */ 0f
+ )
assertEquals(1f, currentRoundness)
}
@Test
fun computeCornerRoundnessForPinnedHun_stackAboveScreenBelowPinPoint_halfRound() {
- val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ val currentRoundness =
+ stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
/* hostViewHeight= */ 100f,
/* stackY= */ 90f,
/* viewMaxHeight= */ 20f,
- /* originalCornerRoundness= */ 0f)
+ /* originalCornerRoundness= */ 0f
+ )
assertEquals(0.5f, currentRoundness)
}
@Test
fun computeCornerRoundnessForPinnedHun_stackAbovePinPoint_notRound() {
- val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ val currentRoundness =
+ stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
/* hostViewHeight= */ 100f,
/* stackY= */ 0f,
/* viewMaxHeight= */ 20f,
- /* originalCornerRoundness= */ 0f)
+ /* originalCornerRoundness= */ 0f
+ )
assertEquals(0f, currentRoundness)
}
@Test
fun computeCornerRoundnessForPinnedHun_originallyRoundAndStackAbovePinPoint_round() {
- val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ val currentRoundness =
+ stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
/* hostViewHeight= */ 100f,
/* stackY= */ 0f,
/* viewMaxHeight= */ 20f,
- /* originalCornerRoundness= */ 1f)
+ /* originalCornerRoundness= */ 1f
+ )
assertEquals(1f, currentRoundness)
}
@@ -642,23 +770,20 @@
// Given: shade is opened, yTranslation of HUN is 0,
// the height of HUN equals to the height of QQS Panel,
// and HUN fully overlaps with QQS Panel
- ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
- px(R.dimen.qqs_layout_padding_bottom)
- val childHunView = createHunViewMock(
- isShadeOpen = true,
- fullyVisible = false,
- headerVisibleAmount = 1f
- )
+ ambientState.stackTranslation =
+ px(R.dimen.qqs_layout_margin_top) + px(R.dimen.qqs_layout_padding_bottom)
+ val childHunView =
+ createHunViewMock(isShadeOpen = true, fullyVisible = false, headerVisibleAmount = 1f)
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
algorithmState.visibleChildren.add(childHunView)
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: full shadow would be applied
@@ -670,13 +795,10 @@
// Given: shade is opened, yTranslation of HUN is greater than 0,
// the height of HUN is equal to the height of QQS Panel,
// and HUN partially overlaps with QQS Panel
- ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
- px(R.dimen.qqs_layout_padding_bottom)
- val childHunView = createHunViewMock(
- isShadeOpen = true,
- fullyVisible = false,
- headerVisibleAmount = 1f
- )
+ ambientState.stackTranslation =
+ px(R.dimen.qqs_layout_margin_top) + px(R.dimen.qqs_layout_padding_bottom)
+ val childHunView =
+ createHunViewMock(isShadeOpen = true, fullyVisible = false, headerVisibleAmount = 1f)
// Use half of the HUN's height as overlap
childHunView.viewState.yTranslation = (childHunView.viewState.height + 1 shr 1).toFloat()
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
@@ -684,17 +806,17 @@
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: HUN should have shadow, but not as full size
assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
assertThat(childHunView.viewState.zTranslation)
- .isLessThan(px(R.dimen.heads_up_pinned_elevation))
+ .isLessThan(px(R.dimen.heads_up_pinned_elevation))
}
@Test
@@ -702,28 +824,25 @@
// Given: shade is opened, yTranslation of HUN is equal to QQS Panel's height,
// the height of HUN is equal to the height of QQS Panel,
// and HUN doesn't overlap with QQS Panel
- ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
- px(R.dimen.qqs_layout_padding_bottom)
+ ambientState.stackTranslation =
+ px(R.dimen.qqs_layout_margin_top) + px(R.dimen.qqs_layout_padding_bottom)
// Mock the height of shade
ambientState.setLayoutMinHeight(1000)
- val childHunView = createHunViewMock(
- isShadeOpen = true,
- fullyVisible = true,
- headerVisibleAmount = 1f
- )
+ val childHunView =
+ createHunViewMock(isShadeOpen = true, fullyVisible = true, headerVisibleAmount = 1f)
// HUN doesn't overlap with QQS Panel
- childHunView.viewState.yTranslation = ambientState.topPadding +
- ambientState.stackTranslation
+ childHunView.viewState.yTranslation =
+ ambientState.topPadding + ambientState.stackTranslation
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
algorithmState.visibleChildren.add(childHunView)
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: HUN should not have shadow
@@ -737,11 +856,8 @@
ambientState.stackTranslation = -ambientState.topPadding
// Mock the height of shade
ambientState.setLayoutMinHeight(1000)
- val childHunView = createHunViewMock(
- isShadeOpen = false,
- fullyVisible = false,
- headerVisibleAmount = 0f
- )
+ val childHunView =
+ createHunViewMock(isShadeOpen = false, fullyVisible = false, headerVisibleAmount = 0f)
childHunView.viewState.yTranslation = 0f
// Shade is closed, thus childHunView's headerVisibleAmount is 0
childHunView.headerVisibleAmount = 0f
@@ -750,11 +866,11 @@
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: HUN should have full shadow
@@ -768,11 +884,8 @@
ambientState.stackTranslation = -ambientState.topPadding
// Mock the height of shade
ambientState.setLayoutMinHeight(1000)
- val childHunView = createHunViewMock(
- isShadeOpen = false,
- fullyVisible = false,
- headerVisibleAmount = 0.5f
- )
+ val childHunView =
+ createHunViewMock(isShadeOpen = false, fullyVisible = false, headerVisibleAmount = 0.5f)
childHunView.viewState.yTranslation = 0f
// Shade is being opened, thus childHunView's headerVisibleAmount is between 0 and 1
// use 0.5 as headerVisibleAmount here
@@ -782,17 +895,17 @@
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: HUN should have shadow, but not as full size
assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
assertThat(childHunView.viewState.zTranslation)
- .isLessThan(px(R.dimen.heads_up_pinned_elevation))
+ .isLessThan(px(R.dimen.heads_up_pinned_elevation))
}
@Test
@@ -862,134 +975,174 @@
// stackScrollAlgorithm.resetViewStates is called.
ambientState.dozeAmount = 0.5f
setExpansionFractionWithoutShelfDuringAodToLockScreen(
- ambientState,
- algorithmState,
- fraction = 0.5f
+ ambientState,
+ algorithmState,
+ fraction = 0.5f
)
stackScrollAlgorithm.resetViewStates(ambientState, 0)
// Then: pulsingNotificationView should show at full height
assertEquals(
- stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView),
- pulsingNotificationView.viewState.height
+ stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView),
+ pulsingNotificationView.viewState.height
)
// After: reset dozeAmount and expansionFraction
ambientState.dozeAmount = 0f
setExpansionFractionWithoutShelfDuringAodToLockScreen(
- ambientState,
- algorithmState,
- fraction = 1f
+ ambientState,
+ algorithmState,
+ fraction = 1f
)
}
// region shouldPinHunToBottomOfExpandedQs
@Test
fun shouldHunBeVisibleWhenScrolled_mustStayOnScreenFalse_false() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */false,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/false,
- /*headsUpOnKeyguard=*/false
- )).isFalse()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ false,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ false,
+ /*headsUpOnKeyguard=*/ false
+ )
+ )
+ .isFalse()
}
@Test
fun shouldPinHunToBottomOfExpandedQs_headsUpIsVisible_false() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */true,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/false,
- /*headsUpOnKeyguard=*/false
- )).isFalse()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ true,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ false,
+ /*headsUpOnKeyguard=*/ false
+ )
+ )
+ .isFalse()
}
@Test
fun shouldHunBeVisibleWhenScrolled_showingPulsing_false() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */true,
- /* isOnKeyguard=*/false,
- /* headsUpOnKeyguard= */false
- )).isFalse()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ true,
+ /* isOnKeyguard=*/ false,
+ /* headsUpOnKeyguard= */ false
+ )
+ )
+ .isFalse()
}
@Test
fun shouldHunBeVisibleWhenScrolled_isOnKeyguard_false() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/true,
- /* headsUpOnKeyguard= */false
- )).isFalse()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ true,
+ /* headsUpOnKeyguard= */ false
+ )
+ )
+ .isFalse()
}
@Test
fun shouldHunBeVisibleWhenScrolled_isNotOnKeyguard_true() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/false,
- /* headsUpOnKeyguard= */false
- )).isTrue()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ false,
+ /* headsUpOnKeyguard= */ false
+ )
+ )
+ .isTrue()
}
@Test
fun shouldHunBeVisibleWhenScrolled_headsUpOnKeyguard_true() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/true,
- /* headsUpOnKeyguard= */true
- )).isTrue()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ true,
+ /* headsUpOnKeyguard= */ true
+ )
+ )
+ .isTrue()
+ }
+
+ @Test
+ fun shouldHunAppearFromBottom_hunAtMaxHunTranslation() {
+ ambientState.maxHeadsUpTranslation = 400f
+ val viewState =
+ ExpandableViewState().apply {
+ height = 100
+ yTranslation = ambientState.maxHeadsUpTranslation - height // move it to the max
+ }
+
+ assertTrue(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState))
+ }
+
+ @Test
+ fun shouldHunAppearFromBottom_hunBelowMaxHunTranslation() {
+ ambientState.maxHeadsUpTranslation = 400f
+ val viewState =
+ ExpandableViewState().apply {
+ height = 100
+ yTranslation =
+ ambientState.maxHeadsUpTranslation - height - 1 // move it below the max
+ }
+
+ assertFalse(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState))
}
// endregion
private fun createHunViewMock(
- isShadeOpen: Boolean,
- fullyVisible: Boolean,
- headerVisibleAmount: Float
+ isShadeOpen: Boolean,
+ fullyVisible: Boolean,
+ headerVisibleAmount: Float
) =
- mock<ExpandableNotificationRow>().apply {
- val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible)
- whenever(this.viewState).thenReturn(childViewStateMock)
+ mock<ExpandableNotificationRow>().apply {
+ val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible)
+ whenever(this.viewState).thenReturn(childViewStateMock)
- whenever(this.mustStayOnScreen()).thenReturn(true)
- whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount)
- }
-
+ whenever(this.mustStayOnScreen()).thenReturn(true)
+ whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount)
+ }
private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) =
- ExpandableViewState().apply {
- // Mock the HUN's height with ambientState.topPadding +
- // ambientState.stackTranslation
- height = (ambientState.topPadding + ambientState.stackTranslation).toInt()
- if (isShadeOpen && fullyVisible) {
- yTranslation =
- ambientState.topPadding + ambientState.stackTranslation
- } else {
- yTranslation = 0f
- }
- headsUpIsVisible = fullyVisible
+ ExpandableViewState().apply {
+ // Mock the HUN's height with ambientState.topPadding +
+ // ambientState.stackTranslation
+ height = (ambientState.topPadding + ambientState.stackTranslation).toInt()
+ if (isShadeOpen && fullyVisible) {
+ yTranslation = ambientState.topPadding + ambientState.stackTranslation
+ } else {
+ yTranslation = 0f
}
+ headsUpIsVisible = fullyVisible
+ }
- private fun createPulsingViewMock(
- ) =
- mock<ExpandableNotificationRow>().apply {
- whenever(this.viewState).thenReturn(ExpandableViewState())
- whenever(this.showingPulsing()).thenReturn(true)
- }
+ private fun createPulsingViewMock() =
+ mock<ExpandableNotificationRow>().apply {
+ whenever(this.viewState).thenReturn(ExpandableViewState())
+ whenever(this.showingPulsing()).thenReturn(true)
+ }
private fun setExpansionFractionWithoutShelfDuringAodToLockScreen(
- ambientState: AmbientState,
- algorithmState: StackScrollAlgorithm.StackScrollAlgorithmState,
- fraction: Float
+ ambientState: AmbientState,
+ algorithmState: StackScrollAlgorithm.StackScrollAlgorithmState,
+ fraction: Float
) {
// showingShelf: false
algorithmState.firstViewInShelf = null
@@ -1002,11 +1155,10 @@
ambientState.stackHeight = ambientState.stackEndHeight * fraction
}
- private fun resetViewStates_hunYTranslationIsInset() {
+ private fun resetViewStates_hunYTranslationIs(expected: Float) {
stackScrollAlgorithm.resetViewStates(ambientState, 0)
- assertThat(notificationRow.viewState.yTranslation)
- .isEqualTo(stackScrollAlgorithm.mHeadsUpInset)
+ assertThat(notificationRow.viewState.yTranslation).isEqualTo(expected)
}
private fun resetViewStates_stackMargin_changesHunYTranslation() {
@@ -1025,13 +1177,13 @@
}
private fun resetViewStates_hunsOverlapping_bottomHunClipped(
- topHun: ExpandableNotificationRow,
- bottomHun: ExpandableNotificationRow
+ topHun: ExpandableNotificationRow,
+ bottomHun: ExpandableNotificationRow
) {
- val topHunHeight = mContext.resources.getDimensionPixelSize(
- R.dimen.notification_content_min_height)
- val bottomHunHeight = mContext.resources.getDimensionPixelSize(
- R.dimen.notification_max_heads_up_height)
+ val topHunHeight =
+ mContext.resources.getDimensionPixelSize(R.dimen.notification_content_min_height)
+ val bottomHunHeight =
+ mContext.resources.getDimensionPixelSize(R.dimen.notification_max_heads_up_height)
whenever(topHun.intrinsicHeight).thenReturn(topHunHeight)
whenever(bottomHun.intrinsicHeight).thenReturn(bottomHunHeight)
@@ -1054,8 +1206,8 @@
}
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction: Float,
- expectedAlpha: Float,
+ expansionFraction: Float,
+ expectedAlpha: Float,
) {
ambientState.isExpansionChanging = true
ambientState.expansionFraction = expansionFraction
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
new file mode 100644
index 0000000..5a57035
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack
+
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.description
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+
+private const val VIEW_HEIGHT = 100
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class StackStateAnimatorTest : SysuiTestCase() {
+
+ private lateinit var stackStateAnimator: StackStateAnimator
+ private val stackScroller: NotificationStackScrollLayout = mock()
+ private val view: ExpandableView = mock()
+ private val viewState: ExpandableViewState =
+ ExpandableViewState().apply { height = VIEW_HEIGHT }
+ private val runnableCaptor: ArgumentCaptor<Runnable> = argumentCaptor()
+ @Before
+ fun setUp() {
+ whenever(stackScroller.context).thenReturn(context)
+ whenever(view.viewState).thenReturn(viewState)
+ stackStateAnimator = StackStateAnimator(stackScroller)
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromTop_startsHeadsUpAppearAnim() {
+ val topMargin = 50f
+ val expectedStartY = -topMargin - stackStateAnimator.mHeadsUpAppearStartAboveScreen
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR)
+ stackStateAnimator.setStackTopMargin(topMargin.toInt())
+
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view).setActualHeight(VIEW_HEIGHT, false)
+ verify(view, description("should animate from the top")).translationY = expectedStartY
+ verify(view)
+ .performAddAnimation(
+ /* delay= */ 0L,
+ /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
+ /* isHeadsUpAppear= */ true,
+ /* onEndRunnable= */ null
+ )
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromBottom_startsHeadsUpAppearAnim() {
+ val screenHeight = 2000f
+ val expectedStartY = screenHeight + stackStateAnimator.mHeadsUpAppearStartAboveScreen
+ val event =
+ AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR).apply {
+ headsUpFromBottom = true
+ }
+ stackStateAnimator.setHeadsUpAppearHeightBottom(screenHeight.toInt())
+
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view).setActualHeight(VIEW_HEIGHT, false)
+ verify(view, description("should animate from the bottom")).translationY = expectedStartY
+ verify(view)
+ .performAddAnimation(
+ /* delay= */ 0L,
+ /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
+ /* isHeadsUpAppear= */ true,
+ /* onEndRunnable= */ null
+ )
+ }
+
+ @Test
+ fun startAnimationForEvents_startsHeadsUpDisappearAnim() {
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR)
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view)
+ .performRemoveAnimation(
+ /* duration= */ eq(ANIMATION_DURATION_HEADS_UP_DISAPPEAR.toLong()),
+ /* delay= */ eq(0L),
+ /* translationDirection= */ eq(0f),
+ /* isHeadsUpAnimation= */ eq(true),
+ /* onStartedRunnable= */ any(),
+ /* onFinishedRunnable= */ runnableCaptor.capture(),
+ /* animationListener= */ any()
+ )
+
+ runnableCaptor.value.run() // execute the end runnable
+
+ verify(view, description("should be called at the end of the animation"))
+ .removeFromTransientContainer()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
index da543d4..cd6bb5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
@@ -17,10 +17,10 @@
package com.android.systemui.statusbar.notification.stack
import android.testing.AndroidTestingRunner
-import android.util.Log
-import android.util.Log.TerribleFailureHandler
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.assertDoesNotLogWtf
+import com.android.systemui.log.assertLogsWtf
import kotlin.math.log2
import kotlin.math.sqrt
import org.junit.Assert
@@ -32,61 +32,36 @@
class ViewStateTest : SysuiTestCase() {
private val viewState = ViewState()
- private var wtfHandler: TerribleFailureHandler? = null
- private var wtfCount = 0
-
@Suppress("DIVISION_BY_ZERO")
@Test
fun testWtfs() {
- interceptWtfs()
-
// Setting valid values doesn't cause any wtfs.
- viewState.alpha = 0.1f
- viewState.xTranslation = 0f
- viewState.yTranslation = 10f
- viewState.zTranslation = 20f
- viewState.scaleX = 0.5f
- viewState.scaleY = 0.25f
-
- expectWtfs(0)
+ assertDoesNotLogWtf {
+ viewState.alpha = 0.1f
+ viewState.xTranslation = 0f
+ viewState.yTranslation = 10f
+ viewState.zTranslation = 20f
+ viewState.scaleX = 0.5f
+ viewState.scaleY = 0.25f
+ }
// Setting NaN values leads to wtfs being logged, and the value not being changed.
- viewState.alpha = 0.0f / 0.0f
- expectWtfs(1)
+ assertLogsWtf { viewState.alpha = 0.0f / 0.0f }
Assert.assertEquals(viewState.alpha, 0.1f)
- viewState.xTranslation = Float.NaN
- expectWtfs(2)
+ assertLogsWtf { viewState.xTranslation = Float.NaN }
Assert.assertEquals(viewState.xTranslation, 0f)
- viewState.yTranslation = log2(-10.0).toFloat()
- expectWtfs(3)
+ assertLogsWtf { viewState.yTranslation = log2(-10.0).toFloat() }
Assert.assertEquals(viewState.yTranslation, 10f)
- viewState.zTranslation = sqrt(-1.0).toFloat()
- expectWtfs(4)
+ assertLogsWtf { viewState.zTranslation = sqrt(-1.0).toFloat() }
Assert.assertEquals(viewState.zTranslation, 20f)
- viewState.scaleX = Float.POSITIVE_INFINITY + Float.NEGATIVE_INFINITY
- expectWtfs(5)
+ assertLogsWtf { viewState.scaleX = Float.POSITIVE_INFINITY + Float.NEGATIVE_INFINITY }
Assert.assertEquals(viewState.scaleX, 0.5f)
- viewState.scaleY = Float.POSITIVE_INFINITY * 0
- expectWtfs(6)
+ assertLogsWtf { viewState.scaleY = Float.POSITIVE_INFINITY * 0 }
Assert.assertEquals(viewState.scaleY, 0.25f)
}
-
- private fun interceptWtfs() {
- wtfCount = 0
- wtfHandler =
- Log.setWtfHandler { _: String?, e: Log.TerribleFailure, _: Boolean ->
- Log.e("ViewStateTest", "Observed WTF: $e")
- wtfCount++
- }
- }
-
- private fun expectWtfs(expectedWtfCount: Int) {
- Assert.assertNotNull(wtfHandler)
- Assert.assertEquals(expectedWtfCount, wtfCount)
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index ac20683..4bfd7e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -23,8 +23,8 @@
import android.view.Surface.ROTATION_90
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectValues
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -81,7 +81,7 @@
testScope.backgroundScope,
mock()
)
- private val configurationInteractor = ConfigurationInteractorImpl(configurationRepository)
+ private val configurationInteractor = ConfigurationInteractor(configurationRepository)
private lateinit var configuration: Configuration
private lateinit var underTest: HideNotificationsInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 21774aa..c17a8ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -28,7 +28,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.TestMocksModule
import com.android.systemui.collectLastValue
-import com.android.systemui.common.domain.CommonDomainLayerModule
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -69,7 +68,6 @@
[
SysUITestModule::class,
ActivatableNotificationViewModelModule::class,
- CommonDomainLayerModule::class,
FooterViewModelModule::class,
HeadlessSystemUserModeModule::class,
UnfoldTransitionModule.Bindings::class,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 1cc611c..14751c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -43,7 +43,6 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.AnimatorTestRule;
-import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.LogBuffer;
@@ -57,9 +56,7 @@
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
@@ -70,7 +67,6 @@
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewBinder;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewModel;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.ui.SystemBarUtilsState;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
import com.android.systemui.util.CarrierConfigTracker;
@@ -702,7 +698,7 @@
mKeyguardStateController,
mShadeViewController,
mStatusBarStateController,
- mock(StatusBarIconViewBindingFailureTracker.class),
+ mock(NotificationIconContainerStatusBarViewBinder.class),
mCommandQueue,
mCarrierConfigTracker,
new CollapsedStatusBarFragmentLogger(
@@ -715,10 +711,6 @@
mDumpManager,
mStatusBarWindowStateController,
mKeyguardUpdateMonitor,
- mock(NotificationIconContainerStatusBarViewModel.class),
- mock(ConfigurationState.class),
- mock(SystemBarUtilsState.class),
- mock(StatusBarNotificationIconViewStore.class),
mock(DemoModeController.class));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index b0305a1..44fa132 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
@@ -24,6 +26,7 @@
import com.android.settingslib.mobile.TelephonyIcons.G
import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.settingslib.mobile.TelephonyIcons.UNKNOWN
+import com.android.systemui.Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -557,7 +560,8 @@
}
@Test
- fun dataActivity_configOn_testIndicators() =
+ @DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
+ fun dataActivity_configOn_testIndicators_staticFlagOff() =
testScope.runTest {
// Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(true)
@@ -611,6 +615,61 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
+ fun dataActivity_configOn_testIndicators_staticFlagOn() =
+ testScope.runTest {
+ // Create a new view model here so the constants are properly read
+ whenever(constants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+
+ var inVisible: Boolean? = null
+ val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
+
+ var outVisible: Boolean? = null
+ val outJob = underTest.activityOutVisible.onEach { outVisible = it }.launchIn(this)
+
+ var containerVisible: Boolean? = null
+ val containerJob =
+ underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this)
+
+ repository.dataActivityDirection.value =
+ DataActivityModel(
+ hasActivityIn = true,
+ hasActivityOut = false,
+ )
+
+ yield()
+
+ assertThat(inVisible).isTrue()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isTrue()
+
+ repository.dataActivityDirection.value =
+ DataActivityModel(
+ hasActivityIn = false,
+ hasActivityOut = true,
+ )
+
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isTrue()
+ assertThat(containerVisible).isTrue()
+
+ repository.dataActivityDirection.value =
+ DataActivityModel(
+ hasActivityIn = false,
+ hasActivityOut = false,
+ )
+
+ assertThat(inVisible).isFalse()
+ assertThat(outVisible).isFalse()
+ assertThat(containerVisible).isTrue()
+
+ inJob.cancel()
+ outJob.cancel()
+ containerJob.cancel()
+ }
+
+ @Test
fun netTypeBackground_flagOff_isNull() =
testScope.runTest {
flags.set(NEW_NETWORK_SLICE_UI, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index bf851eb..6714c94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -1115,6 +1115,7 @@
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = utils.testDispatcher,
+ mainDispatcher = utils.testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index d1870b1..21d4549 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -258,6 +258,7 @@
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestUserInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index b7b24f6..d0804be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -170,6 +170,7 @@
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestUserInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java
deleted file mode 100644
index a2b016f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/GarbageMonitorTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.util.leak;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.concurrency.MessageRouterImpl;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class GarbageMonitorTest extends SysuiTestCase {
-
- @Mock private LeakReporter mLeakReporter;
- @Mock private TrackedGarbage mTrackedGarbage;
- @Mock private DumpManager mDumpManager;
- private GarbageMonitor mGarbageMonitor;
- private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mGarbageMonitor =
- new GarbageMonitor(
- mContext,
- mFakeExecutor,
- new MessageRouterImpl(mFakeExecutor),
- new LeakDetector(null, mTrackedGarbage, null, mDumpManager),
- mLeakReporter,
- mDumpManager);
- }
-
- @Test
- public void testALittleGarbage_doesntDump() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE);
-
- mGarbageMonitor.reinspectGarbageAfterGc();
-
- verify(mLeakReporter, never()).dumpLeak(anyInt());
- }
-
- @Test
- public void testTransientGarbage_doesntDump() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
-
- // Start the leak monitor. Nothing gets reported immediately.
- mGarbageMonitor.startLeakMonitor();
- mFakeExecutor.runAllReady();
- verify(mLeakReporter, never()).dumpLeak(anyInt());
-
- // Garbage gets reset to 0 before the leak reporte actually gets called.
- when(mTrackedGarbage.countOldGarbage()).thenReturn(0);
- mFakeExecutor.advanceClockToLast();
- mFakeExecutor.runAllReady();
-
- // Therefore nothing gets dumped.
- verify(mLeakReporter, never()).dumpLeak(anyInt());
- }
-
- @Test
- public void testLotsOfPersistentGarbage_dumps() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
-
- mGarbageMonitor.reinspectGarbageAfterGc();
-
- verify(mLeakReporter).dumpLeak(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
- }
-
- @Test
- public void testLotsOfPersistentGarbage_dumpsAfterAtime() {
- when(mTrackedGarbage.countOldGarbage()).thenReturn(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
-
- // Start the leak monitor. Nothing gets reported immediately.
- mGarbageMonitor.startLeakMonitor();
- mFakeExecutor.runAllReady();
- verify(mLeakReporter, never()).dumpLeak(anyInt());
-
- mFakeExecutor.advanceClockToLast();
- mFakeExecutor.runAllReady();
-
- verify(mLeakReporter).dumpLeak(GarbageMonitor.GARBAGE_ALLOWANCE + 1);
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
index e59e475..7801684 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
@@ -28,8 +28,8 @@
import com.android.systemui.model.SysUiStateTest
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.BubbleEducationController
-import com.android.wm.shell.bubbles.PREF_MANAGED_EDUCATION
-import com.android.wm.shell.bubbles.PREF_STACK_EDUCATION
+import com.android.wm.shell.bubbles.ManageEducationView.Companion.PREF_MANAGED_EDUCATION
+import com.android.wm.shell.bubbles.StackEducationView.Companion.PREF_STACK_EDUCATION
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import org.junit.Assert.assertEquals
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 b217195..814ea19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -29,8 +29,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -50,6 +48,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
@@ -186,7 +186,7 @@
import com.android.wm.shell.bubbles.BubbleViewInfoTask;
import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.bubbles.StackEducationViewKt;
+import com.android.wm.shell.bubbles.StackEducationView;
import com.android.wm.shell.bubbles.properties.BubbleProperties;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -1930,7 +1930,7 @@
@Test
public void testShowStackEdu_isNotConversationBubble() {
// Setup
- setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, false);
+ setPrefBoolean(StackEducationView.PREF_STACK_EDUCATION, false);
BubbleEntry bubbleEntry = createBubbleEntry(false /* isConversation */);
mBubbleController.updateBubble(bubbleEntry);
assertTrue(mBubbleController.hasBubbles());
@@ -1948,7 +1948,7 @@
@Test
public void testShowStackEdu_isConversationBubble() {
// Setup
- setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, false);
+ setPrefBoolean(StackEducationView.PREF_STACK_EDUCATION, false);
BubbleEntry bubbleEntry = createBubbleEntry(true /* isConversation */);
mBubbleController.updateBubble(bubbleEntry);
assertTrue(mBubbleController.hasBubbles());
@@ -1966,7 +1966,7 @@
@Test
public void testShowStackEdu_isSeenConversationBubble() {
// Setup
- setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, true);
+ setPrefBoolean(StackEducationView.PREF_STACK_EDUCATION, true);
BubbleEntry bubbleEntry = createBubbleEntry(true /* isConversation */);
mBubbleController.updateBubble(bubbleEntry);
assertTrue(mBubbleController.hasBubbles());
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt
index a464fa8..fa79580 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt
@@ -16,10 +16,8 @@
package com.android.systemui.accessibility.data.repository
-import android.view.accessibility.accessibilityManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.accessibilityRepository by Fixture {
- AccessibilityRepository.invoke(a11yManager = accessibilityManager)
-}
+val Kosmos.fakeAccessibilityRepository by Fixture { FakeAccessibilityRepository() }
+val Kosmos.accessibilityRepository by Fixture { fakeAccessibilityRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/AuthControllerKosmos.kt
similarity index 73%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/AuthControllerKosmos.kt
index 94d9a72..7f70785 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/AuthControllerKosmos.kt
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.common.domain.interactor
+package com.android.systemui.biometrics
-import com.android.systemui.common.ui.data.repository.configurationRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
-val Kosmos.configurationInteractor by Fixture {
- ConfigurationInteractorImpl(repository = configurationRepository)
-}
+var Kosmos.authController by Fixture { mock<AuthController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
new file mode 100644
index 0000000..cbfc768
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.biometrics.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.biometrics.authController
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+
+val Kosmos.udfpsOverlayInteractor by Fixture {
+ UdfpsOverlayInteractor(
+ context = applicationContext,
+ authController = authController,
+ selectedUserInteractor = selectedUserInteractor,
+ scope = applicationCoroutineScope,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
index 5475659..8b1a1d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
@@ -16,14 +16,18 @@
package com.android.systemui.biometrics.ui.viewmodel
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.statusbar.phone.systemUIDialogManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+@ExperimentalCoroutinesApi
val Kosmos.deviceEntryUdfpsTouchOverlayViewModel by Fixture {
DeviceEntryUdfpsTouchOverlayViewModel(
deviceEntryIconViewModel = deviceEntryIconViewModel,
+ alternateBouncerInteractor = alternateBouncerInteractor,
systemUIDialogManager = systemUIDialogManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
new file mode 100644
index 0000000..86a4509
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.bouncer.domain.interactor
+
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.fakeSystemClock
+
+var Kosmos.alternateBouncerInteractor by
+ Kosmos.Fixture {
+ AlternateBouncerInteractor(
+ statusBarStateController = statusBarStateController,
+ keyguardStateController = mock<KeyguardStateControllerImpl>(),
+ bouncerRepository = keyguardBouncerRepository,
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
+ biometricSettingsRepository = biometricSettingsRepository,
+ systemClock = fakeSystemClock,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ scope = testScope.backgroundScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 6b38d6e..050c2c9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -37,7 +37,8 @@
MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val onConfigurationChange: Flow<Unit> = _onConfigurationChange.asSharedFlow()
- private val _configurationChangeValues = MutableSharedFlow<Configuration>()
+ private val _configurationChangeValues =
+ MutableSharedFlow<Configuration>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val configurationValues: Flow<Configuration> =
_configurationChangeValues.asSharedFlow()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt
new file mode 100644
index 0000000..9175f09
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.deviceentry.data.ui.viewmodel
+
+import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor
+import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
+import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel
+import com.android.systemui.keyguard.ui.viewmodel.deviceEntryForegroundIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModel
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.udfpsAccessibilityOverlayViewModel by
+ Kosmos.Fixture {
+ UdfpsAccessibilityOverlayViewModel(
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
+ accessibilityInteractor = accessibilityInteractor,
+ deviceEntryIconViewModel = deviceEntryIconViewModel,
+ deviceEntryFgIconViewModel = deviceEntryForegroundIconViewModel,
+ )
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..d9c6e4f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.alternateBouncerToAodTransitionViewModel by Fixture {
+ AlternateBouncerToAodTransitionViewModel(
+ interactor = keyguardTransitionInteractor,
+ deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..e4821b0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.alternateBouncerToGoneTransitionViewModel by Fixture {
+ AlternateBouncerToGoneTransitionViewModel(
+ interactor = keyguardTransitionInteractor,
+ bouncerToGoneFlows = bouncerToGoneFlows,
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt
new file mode 100644
index 0000000..4bfe4f5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.deviceEntryForegroundIconViewModel by Fixture {
+ DeviceEntryForegroundViewModel(
+ context = applicationContext,
+ configurationRepository = configurationRepository,
+ deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ transitionInteractor = keyguardTransitionInteractor,
+ deviceEntryIconViewModel = deviceEntryIconViewModel,
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 6557bcf..5ceefde 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -28,8 +28,10 @@
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+val Kosmos.fakeDeviceEntryIconViewModelTransition by Fixture { FakeDeviceEntryIconTransition() }
+
val Kosmos.deviceEntryIconViewModelTransitionsMock by Fixture {
- mutableSetOf<DeviceEntryIconTransition>()
+ setOf<DeviceEntryIconTransition>(fakeDeviceEntryIconViewModelTransition)
}
val Kosmos.deviceEntryIconViewModel by Fixture {
@@ -41,7 +43,6 @@
transitionInteractor = keyguardTransitionInteractor,
keyguardInteractor = keyguardInteractor,
viewModel = aodToLockscreenTransitionViewModel,
- shadeDependentFlows = shadeDependentFlows,
sceneContainerFlags = sceneContainerFlags,
keyguardViewController = { statusBarKeyguardViewManager },
deviceEntryInteractor = deviceEntryInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/FakeDeviceEntryIconTransition.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/FakeDeviceEntryIconTransition.kt
new file mode 100644
index 0000000..6d872a3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/FakeDeviceEntryIconTransition.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeDeviceEntryIconTransition : DeviceEntryIconTransition {
+ private val _deviceEntryParentViewAlpha: MutableStateFlow<Float> = MutableStateFlow(0f)
+ override val deviceEntryParentViewAlpha: Flow<Float> = _deviceEntryParentViewAlpha.asStateFlow()
+
+ fun setDeviceEntryParentViewAlpha(alpha: Float) {
+ _deviceEntryParentViewAlpha.value = alpha
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
index 6ccb3bc..5e67182 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
@@ -20,6 +20,29 @@
import android.util.Log.TerribleFailureHandler
import junit.framework.Assert
+/** Asserts that the given block does not make a call to Log.wtf */
+fun assertDoesNotLogWtf(
+ message: String = "Expected Log.wtf not to be called",
+ notLoggingBlock: () -> Unit,
+) {
+ var caught: TerribleFailureLog? = null
+ val newHandler = TerribleFailureHandler { tag, failure, system ->
+ caught = TerribleFailureLog(tag, failure, system)
+ }
+ val oldHandler = Log.setWtfHandler(newHandler)
+ try {
+ notLoggingBlock()
+ } finally {
+ Log.setWtfHandler(oldHandler)
+ }
+ caught?.let { throw AssertionError("$message: $it", it.failure) }
+}
+
+fun assertDoesNotLogWtf(
+ message: String = "Expected Log.wtf not to be called",
+ notLoggingRunnable: Runnable,
+) = assertDoesNotLogWtf(message = message) { notLoggingRunnable.run() }
+
/**
* Assert that the given block makes a call to Log.wtf
*
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
new file mode 100644
index 0000000..d705248
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 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.tiles.impl.custom
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTilePackageUpdatesRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakePackageManagerAdapterFacade
+
+var Kosmos.tileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
+
+val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by
+ Kosmos.Fixture { FakeCustomTileStatePersister() }
+
+val Kosmos.customTileRepository: FakeCustomTileRepository by
+ Kosmos.Fixture {
+ FakeCustomTileRepository(
+ customTileStatePersister,
+ packageManagerAdapterFacade,
+ testScope.testScheduler,
+ )
+ }
+
+val Kosmos.customTileDefaultsRepository: FakeCustomTileDefaultsRepository by
+ Kosmos.Fixture { FakeCustomTileDefaultsRepository() }
+
+val Kosmos.customTilePackagesUpdatesRepository: FakeCustomTilePackageUpdatesRepository by
+ Kosmos.Fixture { FakeCustomTilePackageUpdatesRepository() }
+
+val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by
+ Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
index ccf0391..ba803d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
@@ -19,21 +19,21 @@
import android.os.UserHandle
import android.service.quicksettings.Tile
import com.android.systemui.qs.external.FakeCustomTileStatePersister
-import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.Flow
class FakeCustomTileRepository(
- tileSpec: TileSpec.CustomTileSpec,
customTileStatePersister: FakeCustomTileStatePersister,
+ private val packageManagerAdapterFacade: FakePackageManagerAdapterFacade,
testBackgroundContext: CoroutineContext,
) : CustomTileRepository {
private val realDelegate: CustomTileRepository =
CustomTileRepositoryImpl(
- tileSpec,
+ packageManagerAdapterFacade.tileSpec,
customTileStatePersister,
+ packageManagerAdapterFacade.packageManagerAdapter,
testBackgroundContext,
)
@@ -44,6 +44,10 @@
override fun getTile(user: UserHandle): Tile? = realDelegate.getTile(user)
+ override suspend fun isTileActive(): Boolean = realDelegate.isTileActive()
+
+ override suspend fun isTileToggleable(): Boolean = realDelegate.isTileToggleable()
+
override suspend fun updateWithTile(
user: UserHandle,
newTile: Tile,
@@ -55,4 +59,8 @@
defaults: CustomTileDefaults,
isPersistable: Boolean,
) = realDelegate.updateWithDefaults(user, defaults, isPersistable)
+
+ fun setTileActive(isActive: Boolean) = packageManagerAdapterFacade.setIsActive(isActive)
+
+ fun setTileToggleable(isActive: Boolean) = packageManagerAdapterFacade.setIsToggleable(isActive)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
new file mode 100644
index 0000000..c9a7655
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 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.tiles.impl.custom.data.repository
+
+import android.content.pm.ServiceInfo
+import android.os.Bundle
+import com.android.systemui.qs.external.PackageManagerAdapter
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakePackageManagerAdapterFacade(
+ val tileSpec: TileSpec.CustomTileSpec,
+ val packageManagerAdapter: PackageManagerAdapter = mock {},
+) {
+
+ private var isToggleable: Boolean = false
+ private var isActive: Boolean = false
+
+ init {
+ whenever(packageManagerAdapter.getServiceInfo(eq(tileSpec.componentName), any()))
+ .thenAnswer {
+ ServiceInfo().apply {
+ metaData =
+ Bundle().apply {
+ putBoolean(
+ android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
+ isToggleable
+ )
+ putBoolean(
+ android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
+ isActive
+ )
+ }
+ }
+ }
+ }
+
+ fun setIsActive(isActive: Boolean) {
+ this.isActive = isActive
+ }
+
+ fun setIsToggleable(isToggleable: Boolean) {
+ this.isToggleable = isToggleable
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ShelfNotificationIconViewStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
similarity index 64%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ShelfNotificationIconViewStoreKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
index f7f16a4..67fecb4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ShelfNotificationIconViewStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinderKosmos.kt
@@ -16,9 +16,22 @@
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
+import com.android.systemui.common.ui.configurationState
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerShelfViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.notifCollection
+import com.android.systemui.statusbar.ui.systemBarUtilsState
+
+val Kosmos.notificationIconContainerShelfViewBinder by Fixture {
+ NotificationIconContainerShelfViewBinder(
+ notificationIconContainerShelfViewModel,
+ configurationState,
+ systemBarUtilsState,
+ statusBarIconViewBindingFailureTracker,
+ shelfNotificationIconViewStore,
+ )
+}
val Kosmos.shelfNotificationIconViewStore by Fixture {
ShelfNotificationIconViewStore(notifCollection = notifCollection)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt
index 988172c..b906b60 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelKosmos.kt
@@ -18,7 +18,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerShelfViewModel
import com.android.systemui.statusbar.notification.row.ui.viewmodel.activatableNotificationViewModel
import com.android.systemui.statusbar.notification.shelf.domain.interactor.notificationShelfInteractor
@@ -26,6 +25,5 @@
NotificationShelfViewModel(
interactor = notificationShelfInteractor,
activatableViewModel = activatableNotificationViewModel,
- icons = notificationIconContainerShelfViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt
index baca8b2..4232b27 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.stack.domain.interactor
-import com.android.systemui.common.domain.interactor.configurationInteractor
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.power.domain.interactor.powerInteractor
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index ca5b401..04716b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -22,11 +22,9 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.shelfNotificationIconViewStore
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.statusBarIconViewBindingFailureTracker
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
import com.android.systemui.statusbar.phone.notificationIconAreaController
-import com.android.systemui.statusbar.ui.systemBarUtilsState
val Kosmos.notificationListViewBinder by Fixture {
NotificationListViewBinder(
@@ -35,9 +33,7 @@
configuration = configurationState,
falsingManager = falsingManager,
iconAreaController = notificationIconAreaController,
- iconViewBindingFailureTracker = statusBarIconViewBindingFailureTracker,
metricsLogger = metricsLogger,
- shelfIconViewStore = shelfNotificationIconViewStore,
- systemBarUtilsState = systemBarUtilsState,
+ nicBinder = notificationIconContainerShelfViewBinder,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
index 42c77aa..4e2dc7a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
@@ -47,6 +47,7 @@
broadcastDispatcher = broadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestUserInteractor,
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 13908f1..7744fca 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -90,6 +90,7 @@
android.database.Cursor
android.database.CursorIndexOutOfBoundsException
android.database.CursorJoiner
+android.database.CursorWindow
android.database.CursorWrapper
android.database.DataSetObservable
android.database.DataSetObserver
@@ -97,6 +98,9 @@
android.database.MatrixCursor$RowBuilder
android.database.MergeCursor
android.database.Observable
+android.database.SQLException
+android.database.sqlite.SQLiteClosable
+android.database.sqlite.SQLiteException
android.text.TextUtils
android.text.TextUtils$SimpleStringSplitter
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 77a5e3d..a4b2896 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -72,6 +72,7 @@
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -559,10 +560,11 @@
onClickIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(appUserId);
} else if (provider.maskedBySuspendedPackage) {
showBadge = mUserManager.hasBadge(appUserId);
- final String suspendingPackage = mPackageManagerInternal.getSuspendingPackage(
+ final UserPackage suspendingPackage = mPackageManagerInternal.getSuspendingPackage(
appInfo.packageName, appUserId);
// TODO(b/281839596): don't rely on platform always meaning suspended by admin.
- if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
+ if (suspendingPackage != null
+ && PLATFORM_PACKAGE_NAME.equals(suspendingPackage.packageName)) {
onClickIntent = mDevicePolicyManagerInternal.createShowAdminSupportIntent(
appUserId, true);
} else {
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index a159a5e..5a548fd 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -54,9 +54,7 @@
"exclude-annotation": "android.support.test.filters.FlakyTest"
}
]
- }
- ],
- "postsubmit": [
+ },
{
"name": "CtsVirtualDevicesCameraTestCases",
"options": [
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
index 6940ffe..f24c4cc 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 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.
@@ -70,7 +70,7 @@
@Override
public void onProcessCaptureRequest(int streamId, int frameId) throws RemoteException {
- camera.onProcessCaptureRequest(streamId, frameId, /*metadata=*/ null);
+ camera.onProcessCaptureRequest(streamId, frameId);
}
@Override
diff --git a/services/core/Android.bp b/services/core/Android.bp
index f5a80d8..5111b08 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -100,10 +100,9 @@
name: "services.core.unboosted",
defaults: [
"platform_service_defaults",
- "android.hardware.power-java_static",
+ "android.hardware.power-java_shared",
],
srcs: [
- ":android.hardware.biometrics.face-V4-java-source",
":android.hardware.tv.hdmi.connection-V1-java-source",
":android.hardware.tv.hdmi.earc-V1-java-source",
":statslog-art-java-gen",
@@ -153,7 +152,7 @@
],
static_libs: [
- "android.frameworks.location.altitude-V1-java", // AIDL
+ "android.frameworks.location.altitude-V2-java", // AIDL
"android.frameworks.vibrator-V1-java", // AIDL
"android.hardware.authsecret-V1.0-java",
"android.hardware.authsecret-V1-java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 136692e..cac2efb 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -289,11 +289,11 @@
*
* @param suspendedPackage The package that has been suspended.
* @param userId The user for which to check.
- * @return Name of the package that suspended the given package. Returns {@code null} if the
- * given package is not currently suspended and the platform package name - i.e.
- * {@code "android"} - if the package was suspended by a device admin.
+ * @return User id and package name of the package that suspended the given package. Returns
+ * {@code null} if the given package is not currently suspended and the platform package name
+ * - i.e. {@code "android"} - if the package was suspended by a device admin.
*/
- public abstract String getSuspendingPackage(String suspendedPackage, int userId);
+ public abstract UserPackage getSuspendingPackage(String suspendedPackage, int userId);
/**
* Suspend or unsuspend packages upon admin request.
@@ -312,13 +312,13 @@
* suspended application.
*
* @param suspendedPackage The package that has been suspended.
- * @param suspendingPackage
+ * @param suspendingPackage The package responsible for suspension.
* @param userId The user for which to check.
* @return A {@link SuspendDialogInfo} object describing the dialog to be shown.
*/
@Nullable
public abstract SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage,
- String suspendingPackage, int userId);
+ UserPackage suspendingPackage, int userId);
/**
* Gets any distraction flags set via
@@ -1168,14 +1168,14 @@
public abstract void clearBlockUninstallForUser(@UserIdInt int userId);
/**
- * Unsuspends all packages suspended by the given package for the user.
+ * Unsuspends all packages suspended by an admin for the user.
*/
- public abstract void unsuspendForSuspendingPackage(String suspendingPackage, int userId);
+ public abstract void unsuspendAdminSuspendedPackages(int userId);
/**
- * Returns {@code true} if the package is suspending any packages for the user.
+ * Returns {@code true} if an admin is suspending any packages for the user.
*/
- public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId);
+ public abstract boolean isAdminSuspendingAnyPackages(int userId);
/**
* Register to listen for loading progress of an installed package.
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 329aac6..9f279b1 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -48,8 +48,6 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.am.DropboxRateLimiter;
-import com.android.server.os.TombstoneProtos;
-import com.android.server.os.TombstoneProtos.Tombstone;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -62,14 +60,11 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermissions;
-import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import java.util.stream.Collectors;
/**
* Performs a number of miscellaneous, non-system-critical actions
@@ -337,12 +332,12 @@
*
* @param ctx Context
* @param tombstone path to the tombstone
- * @param tombstoneProto the parsed proto tombstone
+ * @param proto whether the tombstone is stored as proto
* @param processName the name of the process corresponding to the tombstone
* @param tmpFileLock the lock for reading/writing tmp files
*/
public static void addTombstoneToDropBox(
- Context ctx, File tombstone, Tombstone tombstoneProto, String processName,
+ Context ctx, File tombstone, boolean proto, String processName,
ReentrantLock tmpFileLock) {
final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
if (db == null) {
@@ -352,33 +347,31 @@
// Check if we should rate limit and abort early if needed.
DropboxRateLimiter.RateLimitResult rateLimitResult =
- sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
+ sDropboxRateLimiter.shouldRateLimit(
+ proto ? TAG_TOMBSTONE_PROTO_WITH_HEADERS : TAG_TOMBSTONE, processName);
if (rateLimitResult.shouldRateLimit()) return;
HashMap<String, Long> timestamps = readTimestamps();
try {
- // Remove the memory data from the proto.
- Tombstone tombstoneProtoWithoutMemory = removeMemoryFromTombstone(tombstoneProto);
-
- final byte[] tombstoneBytes = tombstoneProtoWithoutMemory.toByteArray();
-
- // Use JNI to call the c++ proto to text converter and add the headers to the tombstone.
- String tombstoneWithoutMemory = new StringBuilder(getBootHeadersToLogAndUpdate())
- .append(rateLimitResult.createHeader())
- .append(getTombstoneText(tombstoneBytes))
- .toString();
-
- // Add the tombstone without memory data to dropbox.
- db.addText(TAG_TOMBSTONE, tombstoneWithoutMemory);
-
- // Add the tombstone proto to dropbox.
- if (recordFileTimestamp(tombstone, timestamps)) {
- tmpFileLock.lock();
- try {
- addAugmentedProtoToDropbox(tombstone, tombstoneBytes, db, rateLimitResult);
- } finally {
- tmpFileLock.unlock();
+ if (proto) {
+ if (recordFileTimestamp(tombstone, timestamps)) {
+ // We need to attach the count indicating the number of dropped dropbox entries
+ // due to rate limiting. Do this by enclosing the proto tombsstone in a
+ // container proto that has the dropped entry count and the proto tombstone as
+ // bytes (to avoid the complexity of reading and writing nested protos).
+ tmpFileLock.lock();
+ try {
+ addAugmentedProtoToDropbox(tombstone, db, rateLimitResult);
+ } finally {
+ tmpFileLock.unlock();
+ }
}
+ } else {
+ // Add the header indicating how many events have been dropped due to rate limiting.
+ final String headers = getBootHeadersToLogAndUpdate()
+ + rateLimitResult.createHeader();
+ addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
+ TAG_TOMBSTONE);
}
} catch (IOException e) {
Slog.e(TAG, "Can't log tombstone", e);
@@ -387,8 +380,11 @@
}
private static void addAugmentedProtoToDropbox(
- File tombstone, byte[] tombstoneBytes, DropBoxManager db,
+ File tombstone, DropBoxManager db,
DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
+ // Read the proto tombstone file as bytes.
+ final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
+
final File tombstoneProtoWithHeaders = File.createTempFile(
tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
Files.setPosixFilePermissions(
@@ -421,8 +417,6 @@
}
}
- private static native String getTombstoneText(byte[] tombstoneBytes);
-
private static void addLastkToDropBox(
DropBoxManager db, HashMap<String, Long> timestamps,
String headers, String footers, String filename, int maxSize,
@@ -440,31 +434,6 @@
addFileWithFootersToDropBox(db, timestamps, headers, footers, filename, maxSize, tag);
}
- /** Removes memory information from the Tombstone proto. */
- @VisibleForTesting
- public static Tombstone removeMemoryFromTombstone(Tombstone tombstoneProto) {
- Tombstone.Builder tombstoneBuilder = tombstoneProto.toBuilder()
- .clearMemoryMappings()
- .clearThreads()
- .putAllThreads(tombstoneProto.getThreadsMap().entrySet()
- .stream()
- .map(BootReceiver::clearMemoryDump)
- .collect(Collectors.toMap(e->e.getKey(), e->e.getValue())));
-
- if (tombstoneProto.hasSignalInfo()) {
- tombstoneBuilder.setSignalInfo(
- tombstoneProto.getSignalInfo().toBuilder().clearFaultAdjacentMetadata());
- }
-
- return tombstoneBuilder.build();
- }
-
- private static AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread> clearMemoryDump(
- Map.Entry<Integer, TombstoneProtos.Thread> e) {
- return new AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread>(
- e.getKey(), e.getValue().toBuilder().clearMemoryDump().build());
- }
-
private static void addFileToDropBox(
DropBoxManager db, HashMap<String, Long> timestamps,
String headers, String filename, int maxSize, String tag) throws IOException {
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index 2ba3a1d..1d1e2d9 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -30,7 +30,6 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
-import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.IVpnManager;
import android.net.Network;
@@ -89,8 +88,6 @@
private final Context mUserAllContext;
private final Dependencies mDeps;
-
- private final ConnectivityManager mCm;
private final VpnProfileStore mVpnProfileStore;
private final INetworkManagementService mNMS;
private final INetd mNetd;
@@ -164,7 +161,6 @@
mHandler = mHandlerThread.getThreadHandler();
mVpnProfileStore = mDeps.getVpnProfileStore();
mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
- mCm = mContext.getSystemService(ConnectivityManager.class);
mNMS = mDeps.getINetworkManagementService();
mNetd = mDeps.getNetd();
mUserManager = mContext.getSystemService(UserManager.class);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index df8f17a..3ae55271 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -70,7 +70,6 @@
import static android.os.PowerExemptionManager.REASON_OPT_OUT_REQUESTED;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_PLATFORM_VPN;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_VPN;
-import static android.os.PowerExemptionManager.REASON_OTHER;
import static android.os.PowerExemptionManager.REASON_PACKAGE_INSTALLER;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
@@ -127,7 +126,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.Manifest;
-import android.Manifest.permission;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1085,7 +1083,7 @@
// Use that as a shortcut if possible to avoid having to recheck all the conditions.
final boolean whileInUseAllowsUiJobScheduling =
ActivityManagerService.doesReasonCodeAllowSchedulingUserInitiatedJobs(
- r.getFgsAllowWIU());
+ r.getFgsAllowWiu_forStart());
r.updateAllowUiJobScheduling(whileInUseAllowsUiJobScheduling
|| mAm.canScheduleUserInitiatedJobs(callingUid, callingPid, callingPackage));
} else {
@@ -2320,7 +2318,7 @@
// If the foreground service is not started from TOP process, do not allow it to
// have while-in-use location/camera/microphone access.
- if (!r.isFgsAllowedWIU()) {
+ if (!r.isFgsAllowedWiu_forCapabilities()) {
Slog.w(TAG,
"Foreground service started from background can not have "
+ "location/camera/microphone access: service "
@@ -2436,7 +2434,7 @@
// mAllowWhileInUsePermissionInFgs.
r.mAllowStartForegroundAtEntering = r.getFgsAllowStart();
r.mAllowWhileInUsePermissionInFgsAtEntering =
- r.isFgsAllowedWIU();
+ r.isFgsAllowedWiu_forCapabilities();
r.mStartForegroundCount++;
r.mFgsEnterTime = SystemClock.uptimeMillis();
if (!stopProcStatsOp) {
@@ -2632,7 +2630,7 @@
policy.getForegroundServiceTypePolicyInfo(type, defaultToType);
final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy(
mAm.mContext, r.packageName, r.app.uid, r.app.getPid(),
- r.isFgsAllowedWIU(), policyInfo);
+ r.isFgsAllowedWiu_forStart(), policyInfo);
RuntimeException exception = null;
switch (code) {
case FGS_TYPE_POLICY_CHECK_DEPRECATED: {
@@ -7580,78 +7578,76 @@
* @param callingUid caller app's uid.
* @param intent intent to start/bind service.
* @param r the service to start.
- * @param isBindService True if it's called from bindService().
+ * @param inBindService True if it's called from bindService().
* @param forBoundFgs set to true if it's called from Service.startForeground() for a
* service that's not started but bound.
- * @return true if allow, false otherwise.
*/
private void setFgsRestrictionLocked(String callingPackage,
int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
- BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService,
+ BackgroundStartPrivileges backgroundStartPrivileges, boolean inBindService,
boolean forBoundFgs) {
- @ReasonCode int allowWIU;
+ @ReasonCode int allowWiu;
@ReasonCode int allowStart;
// If called from bindService(), do not update the actual fields, but instead
// keep it in a separate set of fields.
- if (isBindService) {
- allowWIU = r.mAllowWIUInBindService;
- allowStart = r.mAllowStartInBindService;
+ if (inBindService) {
+ allowWiu = r.mAllowWiu_inBindService;
+ allowStart = r.mAllowStart_inBindService;
} else {
- allowWIU = r.mAllowWhileInUsePermissionInFgsReasonNoBinding;
- allowStart = r.mAllowStartForegroundNoBinding;
+ allowWiu = r.mAllowWiu_noBinding;
+ allowStart = r.mAllowStart_noBinding;
}
- // Check DeviceConfig flag.
- if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
- if (allowWIU == REASON_DENIED) {
- // BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
- // Note REASON_OTHER since there's no other suitable reason.
- allowWIU = REASON_OTHER;
- }
- }
-
- if ((allowWIU == REASON_DENIED)
- || (allowStart == REASON_DENIED)) {
+ if ((allowWiu == REASON_DENIED) || (allowStart == REASON_DENIED)) {
@ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges);
// We store them to compare the old and new while-in-use logics to each other.
// (They're not used for any other purposes.)
- if (allowWIU == REASON_DENIED) {
- allowWIU = allowWhileInUse;
+ if (allowWiu == REASON_DENIED) {
+ allowWiu = allowWhileInUse;
}
if (allowStart == REASON_DENIED) {
allowStart = shouldAllowFgsStartForegroundWithBindingCheckLocked(
allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
- backgroundStartPrivileges, isBindService);
+ backgroundStartPrivileges, inBindService);
}
}
- if (isBindService) {
- r.mAllowWIUInBindService = allowWIU;
- r.mAllowStartInBindService = allowStart;
+ if (inBindService) {
+ r.mAllowWiu_inBindService = allowWiu;
+ r.mAllowStart_inBindService = allowStart;
} else {
if (!forBoundFgs) {
- // This is for "normal" situation.
- r.mAllowWhileInUsePermissionInFgsReasonNoBinding = allowWIU;
- r.mAllowStartForegroundNoBinding = allowStart;
+ // This is for "normal" situation -- either:
+ // - in Context.start[Foreground]Service()
+ // - or, in Service.startForeground() on a started service.
+ r.mAllowWiu_noBinding = allowWiu;
+ r.mAllowStart_noBinding = allowStart;
} else {
- // This logic is only for logging, so we only update the "by-binding" fields.
- if (r.mAllowWIUByBindings == REASON_DENIED) {
- r.mAllowWIUByBindings = allowWIU;
+ // Service.startForeground() is called on a service that's not started, but bound.
+ // In this case, we set them to "byBindings", not "noBinding", because
+ // we don't want to use them when we calculate the "legacy" code.
+ //
+ // We don't want to set them to "no binding" codes, because on U-QPR1 and below,
+ // we didn't call setFgsRestrictionLocked() in the code path which sets
+ // forBoundFgs to true, and we wanted to preserve the original behavior in other
+ // places to compare the legacy and new logic.
+ if (r.mAllowWiu_byBindings == REASON_DENIED) {
+ r.mAllowWiu_byBindings = allowWiu;
}
- if (r.mAllowStartByBindings == REASON_DENIED) {
- r.mAllowStartByBindings = allowStart;
+ if (r.mAllowStart_byBindings == REASON_DENIED) {
+ r.mAllowStart_byBindings = allowStart;
}
}
// Also do a binding client check, unless called from bindService().
- if (r.mAllowWIUByBindings == REASON_DENIED) {
- r.mAllowWIUByBindings =
+ if (r.mAllowWiu_byBindings == REASON_DENIED) {
+ r.mAllowWiu_byBindings =
shouldAllowFgsWhileInUsePermissionByBindingsLocked(callingUid);
}
- if (r.mAllowStartByBindings == REASON_DENIED) {
- r.mAllowStartByBindings = r.mAllowWIUByBindings;
+ if (r.mAllowStart_byBindings == REASON_DENIED) {
+ r.mAllowStart_byBindings = r.mAllowWiu_byBindings;
}
}
}
@@ -7660,13 +7656,13 @@
* Reset various while-in-use and BFSL related information.
*/
void resetFgsRestrictionLocked(ServiceRecord r) {
- r.clearFgsAllowWIU();
+ r.clearFgsAllowWiu();
r.clearFgsAllowStart();
r.mInfoAllowStartForeground = null;
r.mInfoTempFgsAllowListReason = null;
r.mLoggedInfoAllowStartForeground = false;
- r.updateAllowUiJobScheduling(r.isFgsAllowedWIU());
+ r.updateAllowUiJobScheduling(r.isFgsAllowedWiu_forStart());
}
boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
@@ -8284,7 +8280,8 @@
allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
} else {
- allowWhileInUsePermissionInFgs = r.isFgsAllowedWIU();
+ // TODO: Also log "forStart"
+ allowWhileInUsePermissionInFgs = r.isFgsAllowedWiu_forCapabilities();
fgsStartReasonCode = r.getFgsAllowStart();
}
final int callerTargetSdkVersion = r.mRecentCallerApplicationInfo != null
@@ -8323,12 +8320,12 @@
mAm.getUidProcessCapabilityLocked(r.mRecentCallingUid),
0,
0,
- r.mAllowWhileInUsePermissionInFgsReasonNoBinding,
- r.mAllowWIUInBindService,
- r.mAllowWIUByBindings,
- r.mAllowStartForegroundNoBinding,
- r.mAllowStartInBindService,
- r.mAllowStartByBindings,
+ r.mAllowWiu_noBinding,
+ r.mAllowWiu_inBindService,
+ r.mAllowWiu_byBindings,
+ r.mAllowStart_noBinding,
+ r.mAllowStart_inBindService,
+ r.mAllowStart_byBindings,
fgsStartApi,
fgsRestrictionRecalculated);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index d0ab287..626b70b 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -275,7 +275,7 @@
@VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
@VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 10_000L;
- @VisibleForTesting static final boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = true;
+ @VisibleForTesting static final boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = false;
@VisibleForTesting static final boolean DEFAULT_FREEZER_BINDER_ENABLED = true;
@VisibleForTesting static final long DEFAULT_FREEZER_BINDER_DIVISOR = 4;
@VisibleForTesting static final int DEFAULT_FREEZER_BINDER_OFFSET = 500;
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index fc8ad6bc..05303f6f 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -507,7 +507,8 @@
r.appInfo.uid,
r.shortInstanceName,
fgsState, // FGS State
- r.isFgsAllowedWIU(), // allowWhileInUsePermissionInFgs
+ // TODO: Also log "forStart"
+ r.isFgsAllowedWiu_forCapabilities(), // allowWhileInUsePermissionInFgs
r.getFgsAllowStart(), // fgsStartReasonCode
r.appInfo.targetSdkVersion,
r.mRecentCallingUid,
@@ -535,12 +536,12 @@
ActivityManager.PROCESS_CAPABILITY_NONE,
apiDurationBeforeFgsStart,
apiDurationAfterFgsEnd,
- r.mAllowWhileInUsePermissionInFgsReasonNoBinding,
- r.mAllowWIUInBindService,
- r.mAllowWIUByBindings,
- r.mAllowStartForegroundNoBinding,
- r.mAllowStartInBindService,
- r.mAllowStartByBindings,
+ r.mAllowWiu_noBinding,
+ r.mAllowWiu_inBindService,
+ r.mAllowWiu_byBindings,
+ r.mAllowStart_noBinding,
+ r.mAllowStart_inBindService,
+ r.mAllowStart_byBindings,
FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
false
);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 3424578a..b507a60 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2254,7 +2254,7 @@
if (s.isForeground) {
final int fgsType = s.foregroundServiceType;
- if (s.isFgsAllowedWIU()) {
+ if (s.isFgsAllowedWiu_forCapabilities()) {
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 0fba739..08b129e 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -37,6 +37,9 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -81,6 +84,37 @@
// Maximum number of times it can fail during execution before giving up.
static final int MAX_DONE_EXECUTING_COUNT = 6;
+
+ // Compat IDs for the new FGS logic. For now, we just disable all of them.
+ // TODO: Enable them at some point, but only for V+ builds.
+
+ /**
+ * Compat ID to enable the new FGS start logic, for permission calculation used for
+ * per-FGS-type eligibility calculation.
+ * (See also android.app.ForegroundServiceTypePolicy)
+ */
+ @ChangeId
+ // @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Disabled
+ static final long USE_NEW_WIU_LOGIC_FOR_START = 311208629L;
+
+ /**
+ * Compat ID to enable the new FGS start logic, for capability calculation.
+ */
+ @ChangeId
+ // Always enabled
+ @Disabled
+ static final long USE_NEW_WIU_LOGIC_FOR_CAPABILITIES = 313677553L;
+
+ /**
+ * Compat ID to enable the new FGS start logic, for deciding whether to allow FGS start from
+ * the background.
+ */
+ @ChangeId
+ // @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Disabled
+ static final long USE_NEW_BFSL_LOGIC = 311208749L;
+
final ActivityManagerService ams;
final ComponentName name; // service component.
final ComponentName instanceName; // service component's per-instance name.
@@ -178,7 +212,7 @@
// If it's not DENIED, while-in-use permissions are allowed.
// while-in-use permissions in FGS started from background might be restricted.
@PowerExemptionManager.ReasonCode
- int mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
+ int mAllowWiu_noBinding = REASON_DENIED;
// A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state.
boolean mAllowWhileInUsePermissionInFgsAtEntering;
@@ -208,7 +242,7 @@
// allow the service becomes foreground service? Service started from background may not be
// allowed to become a foreground service.
@PowerExemptionManager.ReasonCode
- int mAllowStartForegroundNoBinding = REASON_DENIED;
+ int mAllowStart_noBinding = REASON_DENIED;
// A copy of mAllowStartForeground's value when the service is entering FGS state.
@PowerExemptionManager.ReasonCode
int mAllowStartForegroundAtEntering = REASON_DENIED;
@@ -220,72 +254,172 @@
boolean mLoggedInfoAllowStartForeground;
@PowerExemptionManager.ReasonCode
- int mAllowWIUInBindService = REASON_DENIED;
+ int mAllowWiu_inBindService = REASON_DENIED;
@PowerExemptionManager.ReasonCode
- int mAllowWIUByBindings = REASON_DENIED;
+ int mAllowWiu_byBindings = REASON_DENIED;
@PowerExemptionManager.ReasonCode
- int mAllowStartInBindService = REASON_DENIED;
+ int mAllowStart_inBindService = REASON_DENIED;
@PowerExemptionManager.ReasonCode
- int mAllowStartByBindings = REASON_DENIED;
+ int mAllowStart_byBindings = REASON_DENIED;
- @PowerExemptionManager.ReasonCode
- int getFgsAllowWIU() {
- return mAllowWhileInUsePermissionInFgsReasonNoBinding != REASON_DENIED
- ? mAllowWhileInUsePermissionInFgsReasonNoBinding
- : mAllowWIUInBindService;
+ /**
+ * Whether to use the new "while-in-use permission" logic for FGS start
+ */
+ private boolean useNewWiuLogic_forStart() {
+ return Flags.newFgsRestrictionLogic() // This flag should only be set on V+
+ && CompatChanges.isChangeEnabled(USE_NEW_WIU_LOGIC_FOR_START, appInfo.uid);
}
- boolean isFgsAllowedWIU() {
- return getFgsAllowWIU() != REASON_DENIED;
+ /**
+ * Whether to use the new "while-in-use permission" logic for capabilities
+ */
+ private boolean useNewWiuLogic_forCapabilities() {
+ return Flags.newFgsRestrictionLogic() // This flag should only be set on V+
+ && CompatChanges.isChangeEnabled(USE_NEW_WIU_LOGIC_FOR_CAPABILITIES, appInfo.uid);
}
+ /**
+ * Whether to use the new "FGS BG start exemption" logic.
+ */
+ private boolean useNewBfslLogic() {
+ return Flags.newFgsRestrictionLogic() // This flag should only be set on V+
+ && CompatChanges.isChangeEnabled(USE_NEW_BFSL_LOGIC, appInfo.uid);
+ }
+
+
+ @PowerExemptionManager.ReasonCode
+ private int getFgsAllowWiu_legacy() {
+ // In the legacy mode (U-), we use mAllowWiu_inBindService and mAllowWiu_noBinding.
+ return reasonOr(
+ mAllowWiu_noBinding,
+ mAllowWiu_inBindService
+ );
+ }
+
+ @PowerExemptionManager.ReasonCode
+ private int getFgsAllowWiu_new() {
+ // In the new mode, use by-bindings instead of in-bind-service
+ return reasonOr(
+ mAllowWiu_noBinding,
+ mAllowWiu_byBindings
+ );
+ }
+
+ /**
+ * We use this logic for ForegroundServiceTypePolicy and UIDT eligibility check.
+ */
+ @PowerExemptionManager.ReasonCode
+ int getFgsAllowWiu_forStart() {
+ if (useNewWiuLogic_forStart()) {
+ return getFgsAllowWiu_new();
+ } else {
+ return getFgsAllowWiu_legacy();
+ }
+ }
+
+ /**
+ * We use this logic for the capability calculation in oom-adjuster.
+ */
+ @PowerExemptionManager.ReasonCode
+ int getFgsAllowWiu_forCapabilities() {
+ if (useNewWiuLogic_forCapabilities()) {
+ return getFgsAllowWiu_new();
+ } else {
+ // If alwaysUseNewLogicForWiuCapabilities() isn't set, just use the same logic as
+ // getFgsAllowWiu_forStart().
+ return getFgsAllowWiu_forStart();
+ }
+ }
+
+ /**
+ * We use this logic for ForegroundServiceTypePolicy and UIDT eligibility check.
+ */
+ boolean isFgsAllowedWiu_forStart() {
+ return getFgsAllowWiu_forStart() != REASON_DENIED;
+ }
+
+ /**
+ * We use this logic for the capability calculation in oom-adjuster.
+ */
+ boolean isFgsAllowedWiu_forCapabilities() {
+ return getFgsAllowWiu_forCapabilities() != REASON_DENIED;
+ }
+
+ @PowerExemptionManager.ReasonCode
+ private int getFgsAllowStart_legacy() {
+ // This is used for BFSL (background FGS launch) exemption.
+ // Originally -- on U-QPR1 and before -- we only used [in-bind-service] + [no-binding].
+ // This would exclude certain "valid" situations, so in U-QPR2, we started
+ // using [by-bindings] too.
+ return reasonOr(
+ mAllowStart_noBinding,
+ mAllowStart_inBindService,
+ mAllowStart_byBindings
+ );
+ }
+
+ @PowerExemptionManager.ReasonCode
+ private int getFgsAllowStart_new() {
+ // In the new mode, we don't use [in-bind-service].
+ return reasonOr(
+ mAllowStart_noBinding,
+ mAllowStart_byBindings
+ );
+ }
+
+ /**
+ * Calculate a BFSL exemption code.
+ */
@PowerExemptionManager.ReasonCode
int getFgsAllowStart() {
- return mAllowStartForegroundNoBinding != REASON_DENIED
- ? mAllowStartForegroundNoBinding
- : (mAllowStartByBindings != REASON_DENIED
- ? mAllowStartByBindings
- : mAllowStartInBindService);
+ if (useNewBfslLogic()) {
+ return getFgsAllowStart_new();
+ } else {
+ return getFgsAllowStart_legacy();
+ }
}
+ /**
+ * Return whether BFSL is allowed or not.
+ */
boolean isFgsAllowedStart() {
return getFgsAllowStart() != REASON_DENIED;
}
- void clearFgsAllowWIU() {
- mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
- mAllowWIUInBindService = REASON_DENIED;
- mAllowWIUByBindings = REASON_DENIED;
+ void clearFgsAllowWiu() {
+ mAllowWiu_noBinding = REASON_DENIED;
+ mAllowWiu_inBindService = REASON_DENIED;
+ mAllowWiu_byBindings = REASON_DENIED;
}
void clearFgsAllowStart() {
- mAllowStartForegroundNoBinding = REASON_DENIED;
- mAllowStartInBindService = REASON_DENIED;
- mAllowStartByBindings = REASON_DENIED;
+ mAllowStart_noBinding = REASON_DENIED;
+ mAllowStart_inBindService = REASON_DENIED;
+ mAllowStart_byBindings = REASON_DENIED;
}
@PowerExemptionManager.ReasonCode
- int reasonOr(@PowerExemptionManager.ReasonCode int first,
+ static int reasonOr(
+ @PowerExemptionManager.ReasonCode int first,
@PowerExemptionManager.ReasonCode int second) {
return first != REASON_DENIED ? first : second;
}
- boolean allowedChanged(@PowerExemptionManager.ReasonCode int first,
- @PowerExemptionManager.ReasonCode int second) {
- return (first == REASON_DENIED) != (second == REASON_DENIED);
+ @PowerExemptionManager.ReasonCode
+ static int reasonOr(
+ @PowerExemptionManager.ReasonCode int first,
+ @PowerExemptionManager.ReasonCode int second,
+ @PowerExemptionManager.ReasonCode int third) {
+ return first != REASON_DENIED ? first : reasonOr(second, third);
}
- String changeMessage(@PowerExemptionManager.ReasonCode int first,
- @PowerExemptionManager.ReasonCode int second) {
- return reasonOr(first, second) == REASON_DENIED ? "DENIED"
- : ("ALLOWED ("
- + reasonCodeToString(first)
- + "+"
- + reasonCodeToString(second)
- + ")");
+ boolean allowedChanged(
+ @PowerExemptionManager.ReasonCode int legacyCode,
+ @PowerExemptionManager.ReasonCode int newCode) {
+ return (legacyCode == REASON_DENIED) != (newCode == REASON_DENIED);
}
private String getFgsInfoForWtf() {
@@ -295,15 +429,14 @@
}
void maybeLogFgsLogicChange() {
- final int origWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
- mAllowWIUInBindService);
- final int newWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
- mAllowWIUByBindings);
- final int origStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartInBindService);
- final int newStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+ final int wiuLegacy = getFgsAllowWiu_legacy();
+ final int wiuNew = getFgsAllowWiu_new();
- final boolean wiuChanged = allowedChanged(origWiu, newWiu);
- final boolean startChanged = allowedChanged(origStart, newStart);
+ final int startLegacy = getFgsAllowStart_legacy();
+ final int startNew = getFgsAllowStart_new();
+
+ final boolean wiuChanged = allowedChanged(wiuLegacy, wiuNew);
+ final boolean startChanged = allowedChanged(startLegacy, startNew);
if (!wiuChanged && !startChanged) {
return;
@@ -311,15 +444,14 @@
final String message = "FGS logic changed:"
+ (wiuChanged ? " [WIU changed]" : "")
+ (startChanged ? " [BFSL changed]" : "")
- + " OW:" // Orig-WIU
- + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding,
- mAllowWIUInBindService)
- + " NW:" // New-WIU
- + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding, mAllowWIUByBindings)
- + " OS:" // Orig-start
- + changeMessage(mAllowStartForegroundNoBinding, mAllowStartInBindService)
- + " NS:" // New-start
- + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings)
+ + " Orig WIU:"
+ + reasonCodeToString(wiuLegacy)
+ + " New WIU:"
+ + reasonCodeToString(wiuNew)
+ + " Orig BFSL:"
+ + reasonCodeToString(startLegacy)
+ + " New BFSL:"
+ + reasonCodeToString(startNew)
+ getFgsInfoForWtf();
Slog.wtf(TAG_SERVICE, message);
}
@@ -587,6 +719,7 @@
proto.write(ServiceRecordProto.AppInfo.RES_DIR, appInfo.publicSourceDir);
}
proto.write(ServiceRecordProto.AppInfo.DATA_DIR, appInfo.dataDir);
+ proto.write(ServiceRecordProto.AppInfo.TARGET_SDK_VERSION, appInfo.targetSdkVersion);
proto.end(appInfoToken);
}
if (app != null) {
@@ -611,8 +744,10 @@
ProtoUtils.toDuration(proto, ServiceRecordProto.LAST_ACTIVITY_TIME, lastActivity, now);
ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now);
proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg);
+
+ // TODO: Log "forStart" too.
proto.write(ServiceRecordProto.ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS,
- isFgsAllowedWIU());
+ isFgsAllowedWiu_forCapabilities());
if (startRequested || delayedStop || lastStartId != 0) {
long startToken = proto.start(ServiceRecordProto.START);
@@ -691,15 +826,25 @@
proto.end(shortFgsToken);
}
+ // TODO: Dump all the mAllowWiu* and mAllowStart* fields as needed.
+
proto.end(token);
}
+ void dumpReasonCode(PrintWriter pw, String prefix, String fieldName, int code) {
+ pw.print(prefix);
+ pw.print(fieldName);
+ pw.print("=");
+ pw.println(PowerExemptionManager.reasonCodeToString(code));
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("intent={");
pw.print(intent.getIntent().toShortString(false, true, false, false));
pw.println('}');
pw.print(prefix); pw.print("packageName="); pw.println(packageName);
pw.print(prefix); pw.print("processName="); pw.println(processName);
+ pw.print(prefix); pw.print("targetSdkVersion="); pw.println(appInfo.targetSdkVersion);
if (permission != null) {
pw.print(prefix); pw.print("permission="); pw.println(permission);
}
@@ -727,26 +872,38 @@
pw.print(prefix); pw.print("mIsAllowedBgActivityStartsByStart=");
pw.println(mBackgroundStartPrivilegesByStartMerged);
}
- pw.print(prefix); pw.print("mAllowWhileInUsePermissionInFgsReason=");
- pw.println(PowerExemptionManager.reasonCodeToString(
- mAllowWhileInUsePermissionInFgsReasonNoBinding));
- pw.print(prefix); pw.print("mAllowWIUInBindService=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUInBindService));
- pw.print(prefix); pw.print("mAllowWIUByBindings=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUByBindings));
+ pw.print(prefix); pw.print("useNewWiuLogic_forCapabilities()=");
+ pw.println(useNewWiuLogic_forCapabilities());
+ pw.print(prefix); pw.print("useNewWiuLogic_forStart()=");
+ pw.println(useNewWiuLogic_forStart());
+ pw.print(prefix); pw.print("useNewBfslLogic()=");
+ pw.println(useNewBfslLogic());
+
+ dumpReasonCode(pw, prefix, "mAllowWiu_noBinding", mAllowWiu_noBinding);
+ dumpReasonCode(pw, prefix, "mAllowWiu_inBindService", mAllowWiu_inBindService);
+ dumpReasonCode(pw, prefix, "mAllowWiu_byBindings", mAllowWiu_byBindings);
+
+ dumpReasonCode(pw, prefix, "getFgsAllowWiu_legacy", getFgsAllowWiu_legacy());
+ dumpReasonCode(pw, prefix, "getFgsAllowWiu_new", getFgsAllowWiu_new());
+
+ dumpReasonCode(pw, prefix, "getFgsAllowWiu_forStart", getFgsAllowWiu_forStart());
+ dumpReasonCode(pw, prefix, "getFgsAllowWiu_forCapabilities",
+ getFgsAllowWiu_forCapabilities());
pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling);
pw.print(prefix); pw.print("recentCallingPackage=");
pw.println(mRecentCallingPackage);
pw.print(prefix); pw.print("recentCallingUid=");
pw.println(mRecentCallingUid);
- pw.print(prefix); pw.print("allowStartForeground=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForegroundNoBinding));
- pw.print(prefix); pw.print("mAllowStartInBindService=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartInBindService));
- pw.print(prefix); pw.print("mAllowStartByBindings=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartByBindings));
+
+ dumpReasonCode(pw, prefix, "mAllowStart_noBinding", mAllowStart_noBinding);
+ dumpReasonCode(pw, prefix, "mAllowStart_inBindService", mAllowStart_inBindService);
+ dumpReasonCode(pw, prefix, "mAllowStart_byBindings", mAllowStart_byBindings);
+
+ dumpReasonCode(pw, prefix, "getFgsAllowStart_legacy", getFgsAllowStart_legacy());
+ dumpReasonCode(pw, prefix, "getFgsAllowStart_new", getFgsAllowStart_new());
+ dumpReasonCode(pw, prefix, "getFgsAllowStart", getFgsAllowStart());
pw.print(prefix); pw.print("startForegroundCount=");
pw.println(mStartForegroundCount);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a6b532c..badd7f0 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -3050,8 +3050,8 @@
/**
* Returns whether the given user requires credential entry at this time. This is used to
- * intercept activity launches for locked work apps due to work challenge being triggered
- * or when the profile user is yet to be unlocked.
+ * intercept activity launches for apps corresponding to locked profiles due to separate
+ * challenge being triggered or when the profile user is yet to be unlocked.
*/
protected boolean shouldConfirmCredentials(@UserIdInt int userId) {
if (getStartedUserState(userId) == null) {
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index d9e8ddd..654aebd 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -28,3 +28,10 @@
description: "Restrict network access for certain applications in BFGS process state"
bug: "304347838"
}
+# Whether to use the new while-in-use / BG-FGS-start logic
+flag {
+ namespace: "backstage_power"
+ name: "new_fgs_restriction_logic"
+ description: "Enable the new FGS restriction logic"
+ bug: "276963716"
+}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index b3fb9c9..8b7e56e 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -20,6 +20,7 @@
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.Intent.EXTRA_REPLACING;
import static android.server.app.Flags.gameDefaultFrameRate;
+import static android.server.app.Flags.disableGameModeWhenAppTop;
import static com.android.internal.R.styleable.GameModeConfig_allowGameAngleDriver;
import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscaling;
@@ -181,7 +182,9 @@
@Nullable
final MyUidObserver mUidObserver;
@GuardedBy("mUidObserverLock")
- private final Set<Integer> mForegroundGameUids = new HashSet<>();
+ private final Set<Integer> mGameForegroundUids = new HashSet<>();
+ @GuardedBy("mUidObserverLock")
+ private final Set<Integer> mNonGameForegroundUids = new HashSet<>();
private final GameManagerServiceSystemPropertiesWrapper mSysProps;
private float mGameDefaultFrameRateValue;
@@ -238,12 +241,10 @@
FileUtils.S_IRUSR | FileUtils.S_IWUSR
| FileUtils.S_IRGRP | FileUtils.S_IWGRP,
-1, -1);
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
mGameServiceController = new GameServiceController(
context, BackgroundThread.getExecutor(),
- new GameServiceProviderSelectorImpl(
- context.getResources(),
- context.getPackageManager()),
+ new GameServiceProviderSelectorImpl(context.getResources(), mPackageManager),
new GameServiceProviderInstanceFactoryImpl(context));
} else {
mGameServiceController = null;
@@ -2245,7 +2246,7 @@
// Update all foreground games' frame rate.
synchronized (mUidObserverLock) {
- for (int uid : mForegroundGameUids) {
+ for (int uid : mGameForegroundUids) {
setGameDefaultFrameRateOverride(uid, getGameDefaultFrameRate(isEnabled));
}
}
@@ -2261,31 +2262,44 @@
@Override
public void onUidGone(int uid, boolean disabled) {
synchronized (mUidObserverLock) {
- disableGameMode(uid);
+ handleUidMovedOffTop(uid);
}
}
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
+ switch (procState) {
+ case ActivityManager.PROCESS_STATE_TOP:
+ handleUidMovedToTop(uid);
+ return;
+ default:
+ handleUidMovedOffTop(uid);
+ }
+ }
+
+ private void handleUidMovedToTop(int uid) {
+ final String[] packages = mPackageManager.getPackagesForUid(uid);
+ if (packages == null || packages.length == 0) {
+ return;
+ }
+
+ final int userId = mContext.getUserId();
+ final boolean isNotGame = Arrays.stream(packages).noneMatch(
+ p -> isPackageGame(p, userId));
synchronized (mUidObserverLock) {
-
- if (procState != ActivityManager.PROCESS_STATE_TOP) {
- disableGameMode(uid);
+ if (isNotGame) {
+ if (disableGameModeWhenAppTop()) {
+ if (!mGameForegroundUids.isEmpty() && mNonGameForegroundUids.isEmpty()) {
+ Slog.v(TAG, "Game power mode OFF (first non-game in foreground)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, false);
+ }
+ mNonGameForegroundUids.add(uid);
+ }
return;
}
-
- final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
- if (packages == null || packages.length == 0) {
- return;
- }
-
- final int userId = mContext.getUserId();
- if (!Arrays.stream(packages).anyMatch(p -> isPackageGame(p, userId))) {
- return;
- }
-
- if (mForegroundGameUids.isEmpty()) {
- Slog.v(TAG, "Game power mode ON (process state was changed to foreground)");
+ if (mGameForegroundUids.isEmpty() && (!disableGameModeWhenAppTop()
+ || mNonGameForegroundUids.isEmpty())) {
+ Slog.v(TAG, "Game power mode ON (first game in foreground)");
mPowerManagerInternal.setPowerMode(Mode.GAME, true);
}
final boolean isGameDefaultFrameRateDisabled =
@@ -2293,22 +2307,26 @@
PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED, false);
setGameDefaultFrameRateOverride(uid,
getGameDefaultFrameRate(!isGameDefaultFrameRateDisabled));
- mForegroundGameUids.add(uid);
+ mGameForegroundUids.add(uid);
}
}
- private void disableGameMode(int uid) {
+ private void handleUidMovedOffTop(int uid) {
synchronized (mUidObserverLock) {
- if (!mForegroundGameUids.contains(uid)) {
- return;
+ if (mGameForegroundUids.contains(uid)) {
+ mGameForegroundUids.remove(uid);
+ if (mGameForegroundUids.isEmpty() && (!disableGameModeWhenAppTop()
+ || mNonGameForegroundUids.isEmpty())) {
+ Slog.v(TAG, "Game power mode OFF (no games in foreground)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, false);
+ }
+ } else if (disableGameModeWhenAppTop() && mNonGameForegroundUids.contains(uid)) {
+ mNonGameForegroundUids.remove(uid);
+ if (mNonGameForegroundUids.isEmpty() && !mGameForegroundUids.isEmpty()) {
+ Slog.v(TAG, "Game power mode ON (only games in foreground)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, true);
+ }
}
- mForegroundGameUids.remove(uid);
- if (!mForegroundGameUids.isEmpty()) {
- return;
- }
- Slog.v(TAG,
- "Game power mode OFF (process remomved or state changed to background)");
- mPowerManagerInternal.setPowerMode(Mode.GAME, false);
}
}
}
diff --git a/services/core/java/com/android/server/app/flags.aconfig b/services/core/java/com/android/server/app/flags.aconfig
index f2e4783..0673013 100644
--- a/services/core/java/com/android/server/app/flags.aconfig
+++ b/services/core/java/com/android/server/app/flags.aconfig
@@ -6,4 +6,11 @@
description: "This flag guards the new behavior with the addition of Game Default Frame Rate feature."
bug: "286084594"
is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+ name: "disable_game_mode_when_app_top"
+ namespace: "game"
+ description: "Disable game power mode when a non-game app is also top and visible"
+ bug: "299295925"
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 8091753..dada72e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -56,6 +56,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.PrintWriterPrinter;
import com.android.internal.annotations.GuardedBy;
@@ -1640,7 +1641,7 @@
return mBtHelper.getLeAudioDeviceGroupId(device);
}
- /*package*/ List<String> getLeAudioGroupAddresses(int groupId) {
+ /*package*/ List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) {
return mBtHelper.getLeAudioGroupAddresses(groupId);
}
@@ -2651,9 +2652,9 @@
}
}
- List<String> getDeviceAddresses(AudioDeviceAttributes device) {
+ List<String> getDeviceIdentityAddresses(AudioDeviceAttributes device) {
synchronized (mDeviceStateLock) {
- return mDeviceInventory.getDeviceAddresses(device);
+ return mDeviceInventory.getDeviceIdentityAddresses(device);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index d138f24..e05824a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -566,23 +566,40 @@
final int mDeviceType;
final @NonNull String mDeviceName;
final @NonNull String mDeviceAddress;
+ @NonNull String mDeviceIdentityAddress;
int mDeviceCodecFormat;
- @NonNull String mPeerDeviceAddress;
final int mGroupId;
+ @NonNull String mPeerDeviceAddress;
+ @NonNull String mPeerIdentityDeviceAddress;
/** Disabled operating modes for this device. Use a negative logic so that by default
* an empty list means all modes are allowed.
* See BluetoothAdapter.AUDIO_MODE_DUPLEX and BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY */
@NonNull ArraySet<String> mDisabledModes = new ArraySet(0);
- DeviceInfo(int deviceType, String deviceName, String deviceAddress,
- int deviceCodecFormat, String peerDeviceAddress, int groupId) {
+ DeviceInfo(int deviceType, String deviceName, String address,
+ String identityAddress, int codecFormat,
+ int groupId, String peerAddress, String peerIdentityAddress) {
mDeviceType = deviceType;
- mDeviceName = deviceName == null ? "" : deviceName;
- mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
- mDeviceCodecFormat = deviceCodecFormat;
- mPeerDeviceAddress = peerDeviceAddress == null ? "" : peerDeviceAddress;
+ mDeviceName = TextUtils.emptyIfNull(deviceName);
+ mDeviceAddress = TextUtils.emptyIfNull(address);
+ mDeviceIdentityAddress = TextUtils.emptyIfNull(identityAddress);
+ mDeviceCodecFormat = codecFormat;
mGroupId = groupId;
+ mPeerDeviceAddress = TextUtils.emptyIfNull(peerAddress);
+ mPeerIdentityDeviceAddress = TextUtils.emptyIfNull(peerIdentityAddress);
+ }
+
+ /** Constructor for all devices except A2DP sink and LE Audio */
+ DeviceInfo(int deviceType, String deviceName, String address) {
+ this(deviceType, deviceName, address, null, AudioSystem.AUDIO_FORMAT_DEFAULT);
+ }
+
+ /** Constructor for A2DP sink devices */
+ DeviceInfo(int deviceType, String deviceName, String address,
+ String identityAddress, int codecFormat) {
+ this(deviceType, deviceName, address, identityAddress, codecFormat,
+ BluetoothLeAudio.GROUP_ID_INVALID, null, null);
}
void setModeDisabled(String mode) {
@@ -601,25 +618,20 @@
return isModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
}
- DeviceInfo(int deviceType, String deviceName, String deviceAddress,
- int deviceCodecFormat) {
- this(deviceType, deviceName, deviceAddress, deviceCodecFormat,
- null, BluetoothLeAudio.GROUP_ID_INVALID);
- }
-
- DeviceInfo(int deviceType, String deviceName, String deviceAddress) {
- this(deviceType, deviceName, deviceAddress, AudioSystem.AUDIO_FORMAT_DEFAULT);
- }
-
@Override
public String toString() {
return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
+ " (" + AudioSystem.getDeviceName(mDeviceType)
+ ") name:" + mDeviceName
+ " addr:" + Utils.anonymizeBluetoothAddress(mDeviceType, mDeviceAddress)
+ + " identity addr:"
+ + Utils.anonymizeBluetoothAddress(mDeviceType, mDeviceIdentityAddress)
+ " codec: " + Integer.toHexString(mDeviceCodecFormat)
- + " peer addr:" + mPeerDeviceAddress
+ " group:" + mGroupId
+ + " peer addr:"
+ + Utils.anonymizeBluetoothAddress(mDeviceType, mPeerDeviceAddress)
+ + " peer identity addr:"
+ + Utils.anonymizeBluetoothAddress(mDeviceType, mPeerIdentityDeviceAddress)
+ " disabled modes: " + mDisabledModes + "]";
}
@@ -947,20 +959,44 @@
}
+ /**
+ * Goes over all connected LE Audio devices in the provided group ID and
+ * update:
+ * - the peer address according to the addres of other device in the same
+ * group (can also clear the peer address is not anymore in the group)
+ * - The dentity address if not yet set.
+ * LE Audio buds in a pair are in the same group.
+ * @param groupId the LE Audio group to update
+ */
/*package*/ void onUpdateLeAudioGroupAddresses(int groupId) {
synchronized (mDevicesLock) {
+ // <address, identy address>
+ List<Pair<String, String>> addresses = new ArrayList<>();
for (DeviceInfo di : mConnectedDevices.values()) {
if (di.mGroupId == groupId) {
- List<String> addresses = mDeviceBroker.getLeAudioGroupAddresses(groupId);
+ if (addresses.isEmpty()) {
+ addresses = mDeviceBroker.getLeAudioGroupAddresses(groupId);
+ }
if (di.mPeerDeviceAddress.equals("")) {
- for (String addr : addresses) {
- if (!addr.equals(di.mDeviceAddress)) {
- di.mPeerDeviceAddress = addr;
+ for (Pair<String, String> addr : addresses) {
+ if (!addr.first.equals(di.mDeviceAddress)) {
+ di.mPeerDeviceAddress = addr.first;
+ di.mPeerIdentityDeviceAddress = addr.second;
break;
}
}
- } else if (!addresses.contains(di.mPeerDeviceAddress)) {
+ } else if (!addresses.contains(
+ new Pair(di.mPeerDeviceAddress, di.mPeerIdentityDeviceAddress))) {
di.mPeerDeviceAddress = "";
+ di.mPeerIdentityDeviceAddress = "";
+ }
+ if (di.mDeviceIdentityAddress.equals("")) {
+ for (Pair<String, String> addr : addresses) {
+ if (addr.first.equals(di.mDeviceAddress)) {
+ di.mDeviceIdentityAddress = addr.second;
+ break;
+ }
+ }
}
}
}
@@ -1964,7 +2000,7 @@
mDeviceBroker.clearA2dpSuspended(true /* internalOnly */);
final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
- address, codec);
+ address, btInfo.mDevice.getIdentityAddress(), codec);
final String diKey = di.getKey();
mConnectedDevices.put(diKey, di);
// on a connection always overwrite the device seen by AudioPolicy, see comment above when
@@ -2381,12 +2417,15 @@
// Find LE Group ID and peer headset address if available
final int groupId = mDeviceBroker.getLeAudioDeviceGroupId(btInfo.mDevice);
String peerAddress = "";
+ String peerIdentityAddress = "";
if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
- List<String> addresses = mDeviceBroker.getLeAudioGroupAddresses(groupId);
+ List<Pair<String, String>> addresses =
+ mDeviceBroker.getLeAudioGroupAddresses(groupId);
if (addresses.size() > 1) {
- for (String addr : addresses) {
- if (!addr.equals(address)) {
- peerAddress = addr;
+ for (Pair<String, String> addr : addresses) {
+ if (!addr.first.equals(address)) {
+ peerAddress = addr.first;
+ peerIdentityAddress = addr.second;
break;
}
}
@@ -2420,8 +2459,9 @@
// Reset LEA suspend state each time a new sink is connected
mDeviceBroker.clearLeAudioSuspended(true /* internalOnly */);
mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
- new DeviceInfo(device, name, address, codec,
- peerAddress, groupId));
+ new DeviceInfo(device, name, address,
+ btInfo.mDevice.getIdentityAddress(), codec,
+ groupId, peerAddress, peerIdentityAddress));
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
addAudioDeviceInInventoryIfNeeded(device, address, peerAddress,
@@ -2806,18 +2846,19 @@
mDevRoleCapturePresetDispatchers.finishBroadcast();
}
- List<String> getDeviceAddresses(AudioDeviceAttributes device) {
+ List<String> getDeviceIdentityAddresses(AudioDeviceAttributes device) {
List<String> addresses = new ArrayList<String>();
final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
device.getAddress());
synchronized (mDevicesLock) {
DeviceInfo di = mConnectedDevices.get(key);
if (di != null) {
- if (!di.mDeviceAddress.isEmpty()) {
- addresses.add(di.mDeviceAddress);
+ if (!di.mDeviceIdentityAddress.isEmpty()) {
+ addresses.add(di.mDeviceIdentityAddress);
}
- if (!di.mPeerDeviceAddress.isEmpty()) {
- addresses.add(di.mPeerDeviceAddress);
+ if (!di.mPeerIdentityDeviceAddress.isEmpty()
+ && !di.mPeerIdentityDeviceAddress.equals(di.mDeviceIdentityAddress)) {
+ addresses.add(di.mPeerIdentityDeviceAddress);
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0c78231..ea791b7 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -33,6 +33,7 @@
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static android.media.AudioManager.STREAM_SYSTEM;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.INVALID_UID;
import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
@@ -116,6 +117,7 @@
import android.media.AudioSystem;
import android.media.AudioTrack;
import android.media.BluetoothProfileConnectionInfo;
+import android.media.FadeManagerConfiguration;
import android.media.IAudioDeviceVolumeDispatcher;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioModeDispatcher;
@@ -4513,6 +4515,8 @@
+ bluetoothMacAddressAnonymization());
pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
+ disablePrescaleAbsoluteVolume());
+ pw.println("\tandroid.media.audiopolicy.enableFadeManagerConfiguration:"
+ + enableFadeManagerConfiguration());
}
private void dumpAudioMode(PrintWriter pw) {
@@ -12614,6 +12618,47 @@
}
/**
+ * see {@link AudioPolicy#setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int setFadeManagerConfigurationForFocusLoss(
+ @NonNull FadeManagerConfiguration fmcForFocusLoss) {
+ super.setFadeManagerConfigurationForFocusLoss_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+ Objects.requireNonNull(fmcForFocusLoss,
+ "Fade manager config for focus loss cannot be null");
+ validateFadeManagerConfiguration(fmcForFocusLoss);
+
+ return mPlaybackMonitor.setFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS,
+ fmcForFocusLoss);
+ }
+
+ /**
+ * see {@link AudioPolicy#clearFadeManagerConfigurationForFocusLoss()}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int clearFadeManagerConfigurationForFocusLoss() {
+ super.clearFadeManagerConfigurationForFocusLoss_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+
+ return mPlaybackMonitor.clearFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS);
+ }
+
+ /**
+ * see {@link AudioPolicy#getFadeManagerConfigurationForFocusLoss()}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss() {
+ super.getFadeManagerConfigurationForFocusLoss_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+
+ return mPlaybackMonitor.getFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS);
+ }
+
+ /**
* @see AudioManager#getHalVersion
*/
public @Nullable AudioHalVersionInfo getHalVersion() {
@@ -12814,6 +12859,19 @@
}
}
+ private void ensureFadeManagerConfigIsEnabled() {
+ Preconditions.checkState(enableFadeManagerConfiguration(),
+ "Fade manager configuration not supported");
+ }
+
+ private void validateFadeManagerConfiguration(FadeManagerConfiguration fmc) {
+ // validate permission of audio attributes
+ List<AudioAttributes> attrs = fmc.getAudioAttributesWithVolumeShaperConfigs();
+ for (int index = 0; index < attrs.size(); index++) {
+ validateAudioAttributesUsage(attrs.get(index));
+ }
+ }
+
//======================
// Audio policy callbacks from AudioSystem for dynamic policies
//======================
@@ -13114,6 +13172,7 @@
+ "could not link to " + projection + " binder death", e);
}
}
+
int status = connectMixes();
if (status != AudioSystem.SUCCESS) {
release();
@@ -13471,6 +13530,43 @@
}
}
+ /**
+ * see {@link AudioManager#dispatchAudioFocusChangeWithFade(AudioFocusInfo, int, AudioPolicy,
+ * List, FadeManagerConfiguration)}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int dispatchFocusChangeWithFade(AudioFocusInfo afi, int focusChange,
+ IAudioPolicyCallback pcb, List<AudioFocusInfo> otherActiveAfis,
+ FadeManagerConfiguration transientFadeMgrConfig) {
+ super.dispatchFocusChangeWithFade_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+ Objects.requireNonNull(afi, "AudioFocusInfo cannot be null");
+ Objects.requireNonNull(pcb, "AudioPolicy callback cannot be null");
+ Objects.requireNonNull(otherActiveAfis,
+ "Other active AudioFocusInfo list cannot be null");
+ if (transientFadeMgrConfig != null) {
+ validateFadeManagerConfiguration(transientFadeMgrConfig);
+ }
+
+ synchronized (mAudioPolicies) {
+ Preconditions.checkState(mAudioPolicies.containsKey(pcb.asBinder()),
+ "Unregistered AudioPolicy for focus dispatch with fade");
+
+ // set the transient fade manager config to be used for handling this focus change
+ if (transientFadeMgrConfig != null) {
+ mPlaybackMonitor.setTransientFadeManagerConfiguration(focusChange,
+ transientFadeMgrConfig);
+ }
+ int status = mMediaFocusControl.dispatchFocusChangeWithFade(afi, focusChange,
+ otherActiveAfis);
+
+ if (transientFadeMgrConfig != null) {
+ mPlaybackMonitor.clearTransientFadeManagerConfiguration(focusChange);
+ }
+ return status;
+ }
+ }
//======================
// Audioserver state dispatch
@@ -13775,8 +13871,8 @@
return activeAssistantUids;
}
- List<String> getDeviceAddresses(AudioDeviceAttributes device) {
- return mDeviceBroker.getDeviceAddresses(device);
+ List<String> getDeviceIdentityAddresses(AudioDeviceAttributes device) {
+ return mDeviceBroker.getDeviceIdentityAddresses(device);
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 8075618..a818c30 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -58,6 +58,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
import com.android.server.utils.EventLogger;
@@ -1077,8 +1078,8 @@
return mLeAudio.getGroupId(device);
}
- /*package*/ List<String> getLeAudioGroupAddresses(int groupId) {
- List<String> addresses = new ArrayList<String>();
+ /*package*/ List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) {
+ List<Pair<String, String>> addresses = new ArrayList<>();
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null || mLeAudio == null) {
return addresses;
@@ -1086,7 +1087,7 @@
List<BluetoothDevice> activeDevices = adapter.getActiveDevices(BluetoothProfile.LE_AUDIO);
for (BluetoothDevice device : activeDevices) {
if (device != null && mLeAudio.getGroupId(device) == groupId) {
- addresses.add(device.getAddress());
+ addresses.add(new Pair(device.getAddress(), device.getIdentityAddress()));
}
}
return addresses;
diff --git a/services/core/java/com/android/server/audio/FadeConfigurations.java b/services/core/java/com/android/server/audio/FadeConfigurations.java
index 2e27c76..37ecf0b 100644
--- a/services/core/java/com/android/server/audio/FadeConfigurations.java
+++ b/services/core/java/com/android/server/audio/FadeConfigurations.java
@@ -16,13 +16,22 @@
package com.android.server.audio;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.media.AudioAttributes;
+import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
+import android.media.FadeManagerConfiguration;
import android.media.VolumeShaper;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Class to encapsulate configurations used for fading players
@@ -69,51 +78,229 @@
private static final int INVALID_UID = -1;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mDefaultFadeManagerConfig;
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mUpdatedFadeManagerConfig;
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mTransientFadeManagerConfig;
+ /** active fade manager is one of: transient > updated > default */
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mActiveFadeManagerConfig;
+
+ /**
+ * Sets the custom fade manager configuration
+ *
+ * @param fadeManagerConfig custom fade manager configuration
+ * @return {@link AudioManager#SUCCESS} if setting custom fade manager configuration succeeds
+ * or {@link AudioManager#ERROR} otherwise (example - when fade manager configuration
+ * feature is disabled)
+ */
+ public int setFadeManagerConfiguration(
+ @NonNull FadeManagerConfiguration fadeManagerConfig) {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+
+ synchronized (mLock) {
+ mUpdatedFadeManagerConfig = Objects.requireNonNull(fadeManagerConfig,
+ "Fade manager configuration cannot be null");
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Clears the fade manager configuration that was previously set with
+ * {@link #setFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if previously set fade manager configuration is cleared
+ * or {@link AudioManager#ERROR} otherwise (example, when fade manager configuration feature
+ * is disabled)
+ */
+ public int clearFadeManagerConfiguration() {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+
+ synchronized (mLock) {
+ mUpdatedFadeManagerConfig = null;
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Returns the active fade manager configuration
+ *
+ * @return {@code null} if feature is disabled, or the custom fade manager configuration if set,
+ * or default fade manager configuration if not set.
+ */
+ @Nullable
+ public FadeManagerConfiguration getFadeManagerConfiguration() {
+ if (!enableFadeManagerConfiguration()) {
+ return null;
+ }
+
+ synchronized (mLock) {
+ return mActiveFadeManagerConfig;
+ }
+ }
+
+ /**
+ * Sets the transient fade manager configuration
+ *
+ * @param fadeManagerConfig custom fade manager configuration
+ * @return {@link AudioManager#SUCCESS} if setting custom fade manager configuration succeeds
+ * or {@link AudioManager#ERROR} otherwise (example - when fade manager configuration is
+ * disabled)
+ */
+ public int setTransientFadeManagerConfiguration(
+ @NonNull FadeManagerConfiguration fadeManagerConfig) {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+
+ synchronized (mLock) {
+ mTransientFadeManagerConfig = Objects.requireNonNull(fadeManagerConfig,
+ "Transient FadeManagerConfiguration cannot be null");
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Clears the transient fade manager configuration that was previously set with
+ * {@link #setTransientFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if previously set transient fade manager configuration
+ * is cleared or {@link AudioManager#ERROR} otherwise (example - when fade manager
+ * configuration is disabled)
+ */
+ public int clearTransientFadeManagerConfiguration() {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+ synchronized (mLock) {
+ mTransientFadeManagerConfig = null;
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Query if fade should be enforecd on players
+ *
+ * @return {@code true} if fade is enabled or using default configurations, {@code false}
+ * otherwise.
+ */
+ public boolean isFadeEnabled() {
+ if (!enableFadeManagerConfiguration()) {
+ return true;
+ }
+
+ synchronized (mLock) {
+ return getUpdatedFadeManagerConfigLocked().isFadeEnabled();
+ }
+ }
+
/**
* Query {@link android.media.AudioAttributes.AttributeUsage usages} that are allowed to
* fade
+ *
* @return list of {@link android.media.AudioAttributes.AttributeUsage}
*/
@NonNull
public List<Integer> getFadeableUsages() {
- return DEFAULT_FADEABLE_USAGES;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADEABLE_USAGES;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return an empty list instead
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getFadeableUsages()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Query {@link android.media.AudioAttributes.AttributeContentType content types} that are
* exempted from fade enforcement
+ *
* @return list of {@link android.media.AudioAttributes.AttributeContentType}
*/
@NonNull
public List<Integer> getUnfadeableContentTypes() {
- return DEFAULT_UNFADEABLE_CONTENT_TYPES;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_CONTENT_TYPES;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return an empty list instead
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeableContentTypes()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Query {@link android.media.AudioPlaybackConfiguration.PlayerType player types} that are
* exempted from fade enforcement
+ *
* @return list of {@link android.media.AudioPlaybackConfiguration.PlayerType}
*/
@NonNull
public List<Integer> getUnfadeablePlayerTypes() {
- return DEFAULT_UNFADEABLE_PLAYER_TYPES;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_PLAYER_TYPES;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return an empty list instead
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeablePlayerTypes()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Get the {@link android.media.VolumeShaper.Configuration} configuration to be applied
* for the fade-out
+ *
* @param aa The {@link android.media.AudioAttributes}
* @return {@link android.media.VolumeShaper.Configuration} for the
- * {@link android.media.AudioAttributes.AttributeUsage} or default volume shaper if not
- * configured
+ * {@link android.media.AudioAttributes} or default volume shaper if not configured
*/
@NonNull
public VolumeShaper.Configuration getFadeOutVolumeShaperConfig(@NonNull AudioAttributes aa) {
- return DEFAULT_FADEOUT_VSHAPE;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADEOUT_VSHAPE;
+ }
+ return getOptimalFadeOutVolShaperConfig(aa);
}
/**
+ * Get the {@link android.media.VolumeShaper.Configuration} configuration to be applied for the
+ * fade in
+ *
+ * @param aa The {@link android.media.AudioAttributes}
+ * @return {@link android.media.VolumeShaper.Configuration} for the
+ * {@link android.media.AudioAttributes} or {@code null} otherwise
+ */
+ @Nullable
+ public VolumeShaper.Configuration getFadeInVolumeShaperConfig(@NonNull AudioAttributes aa) {
+ if (!enableFadeManagerConfiguration()) {
+ return null;
+ }
+ return getOptimalFadeInVolShaperConfig(aa);
+ }
+
+
+ /**
* Get the duration to fade out a player of type usage
+ *
* @param aa The {@link android.media.AudioAttributes}
* @return duration in milliseconds for the
* {@link android.media.AudioAttributes} or default duration if not configured
@@ -122,22 +309,73 @@
if (!isFadeable(aa, INVALID_UID, AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN)) {
return 0;
}
- return DEFAULT_FADE_OUT_DURATION_MS;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADE_OUT_DURATION_MS;
+ }
+ return getOptimalFadeOutDuration(aa);
}
/**
- * Get the delay to fade in offending players that do not stop after losing audio focus.
+ * Get the delay to fade in offending players that do not stop after losing audio focus
+ *
* @param aa The {@link android.media.AudioAttributes}
* @return delay in milliseconds for the
* {@link android.media.AudioAttributes.Attribute} or default delay if not configured
*/
public long getDelayFadeInOffenders(@NonNull AudioAttributes aa) {
- return DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
+ }
+
+ synchronized (mLock) {
+ return getUpdatedFadeManagerConfigLocked().getFadeInDelayForOffenders();
+ }
+ }
+
+ /**
+ * Query {@link android.media.AudioAttributes} that are exempted from fade enforcement
+ *
+ * @return list of {@link android.media.AudioAttributes}
+ */
+ @NonNull
+ public List<AudioAttributes> getUnfadeableAudioAttributes() {
+ // unfadeable audio attributes is only supported with fade manager configurations
+ if (!enableFadeManagerConfiguration()) {
+ return Collections.EMPTY_LIST;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return empty list
+ return fadeManagerConfig.isFadeEnabled()
+ ? fadeManagerConfig.getUnfadeableAudioAttributes() : Collections.EMPTY_LIST;
+ }
+ }
+
+ /**
+ * Query uids that are exempted from fade enforcement
+ *
+ * @return list of uids
+ */
+ @NonNull
+ public List<Integer> getUnfadeableUids() {
+ // unfadeable uids is only supported with fade manager configurations
+ if (!enableFadeManagerConfiguration()) {
+ return Collections.EMPTY_LIST;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return empty list
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeableUids()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Check if it is allowed to fade for the given {@link android.media.AudioAttributes},
- * client uid and {@link android.media.AudioPlaybackConfiguration.PlayerType} config.
+ * client uid and {@link android.media.AudioPlaybackConfiguration.PlayerType} config
+ *
* @param aa The {@link android.media.AudioAttributes}
* @param uid The uid of the client owning the player
* @param playerType The {@link android.media.AudioPlaybackConfiguration.PlayerType}
@@ -145,36 +383,173 @@
*/
public boolean isFadeable(@NonNull AudioAttributes aa, int uid,
@AudioPlaybackConfiguration.PlayerType int playerType) {
- if (isPlayerTypeUnfadeable(playerType)) {
- if (DEBUG) {
- Slog.i(TAG, "not fadeable: player type:" + playerType);
+ synchronized (mLock) {
+ if (isPlayerTypeUnfadeableLocked(playerType)) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: player type:" + playerType);
+ }
+ return false;
}
- return false;
- }
- if (isContentTypeUnfadeable(aa.getContentType())) {
- if (DEBUG) {
- Slog.i(TAG, "not fadeable: content type:" + aa.getContentType());
+ if (isContentTypeUnfadeableLocked(aa.getContentType())) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: content type:" + aa.getContentType());
+ }
+ return false;
}
- return false;
- }
- if (!isUsageFadeable(aa.getUsage())) {
- if (DEBUG) {
- Slog.i(TAG, "not fadeable: usage:" + aa.getUsage());
+ if (!isUsageFadeableLocked(aa.getSystemUsage())) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: usage:" + aa.getUsage());
+ }
+ return false;
}
- return false;
+ // new configs using fade manager configuration
+ if (isUnfadeableForFadeMgrConfigLocked(aa, uid)) {
+ return false;
+ }
+ return true;
}
- return true;
}
- private boolean isUsageFadeable(int usage) {
- return getFadeableUsages().contains(usage);
+ /** Tries to get the fade out volume shaper config closest to the audio attributes */
+ private VolumeShaper.Configuration getOptimalFadeOutVolShaperConfig(AudioAttributes aa) {
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // check if the specific audio attributes has a volume shaper config defined
+ VolumeShaper.Configuration volShaperConfig =
+ fadeManagerConfig.getFadeOutVolumeShaperConfigForAudioAttributes(aa);
+ if (volShaperConfig != null) {
+ return volShaperConfig;
+ }
+
+ // get the volume shaper config for usage
+ // for fadeable usages, this should never return null
+ return fadeManagerConfig.getFadeOutVolumeShaperConfigForUsage(
+ aa.getSystemUsage());
+ }
}
- private boolean isContentTypeUnfadeable(int contentType) {
- return getUnfadeableContentTypes().contains(contentType);
+ /** Tries to get the fade in volume shaper config closest to the audio attributes */
+ private VolumeShaper.Configuration getOptimalFadeInVolShaperConfig(AudioAttributes aa) {
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // check if the specific audio attributes has a volume shaper config defined
+ VolumeShaper.Configuration volShaperConfig =
+ fadeManagerConfig.getFadeInVolumeShaperConfigForAudioAttributes(aa);
+ if (volShaperConfig != null) {
+ return volShaperConfig;
+ }
+
+ // get the volume shaper config for usage
+ // for fadeable usages, this should never return null
+ return fadeManagerConfig.getFadeInVolumeShaperConfigForUsage(aa.getSystemUsage());
+ }
}
- private boolean isPlayerTypeUnfadeable(int playerType) {
- return getUnfadeablePlayerTypes().contains(playerType);
+ /** Tries to get the duration closest to the audio attributes */
+ private long getOptimalFadeOutDuration(AudioAttributes aa) {
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // check if specific audio attributes has a duration defined
+ long duration = fadeManagerConfig.getFadeOutDurationForAudioAttributes(aa);
+ if (duration != FadeManagerConfiguration.DURATION_NOT_SET) {
+ return duration;
+ }
+
+ // get the duration for usage
+ // for fadeable usages, this should never return DURATION_NOT_SET
+ return fadeManagerConfig.getFadeOutDurationForUsage(aa.getSystemUsage());
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isUnfadeableForFadeMgrConfigLocked(AudioAttributes aa, int uid) {
+ if (isAudioAttributesUnfadeableLocked(aa)) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: aa:" + aa);
+ }
+ return true;
+ }
+ if (isUidUnfadeableLocked(uid)) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: uid:" + uid);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ private boolean isUsageFadeableLocked(int usage) {
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADEABLE_USAGES.contains(usage);
+ }
+ return getUpdatedFadeManagerConfigLocked().isUsageFadeable(usage);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isContentTypeUnfadeableLocked(int contentType) {
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_CONTENT_TYPES.contains(contentType);
+ }
+ return getUpdatedFadeManagerConfigLocked().isContentTypeUnfadeable(contentType);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isPlayerTypeUnfadeableLocked(int playerType) {
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_PLAYER_TYPES.contains(playerType);
+ }
+ return getUpdatedFadeManagerConfigLocked().isPlayerTypeUnfadeable(playerType);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isAudioAttributesUnfadeableLocked(AudioAttributes aa) {
+ if (!enableFadeManagerConfiguration()) {
+ // default fade configs do not support unfadeable audio attributes, hence return false
+ return false;
+ }
+ return getUpdatedFadeManagerConfigLocked().isAudioAttributesUnfadeable(aa);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isUidUnfadeableLocked(int uid) {
+ if (!enableFadeManagerConfiguration()) {
+ // default fade configs do not support unfadeable uids, hence return false
+ return false;
+ }
+ return getUpdatedFadeManagerConfigLocked().isUidUnfadeable(uid);
+ }
+
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration getUpdatedFadeManagerConfigLocked() {
+ if (mActiveFadeManagerConfig == null) {
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return mActiveFadeManagerConfig;
+ }
+
+ /** Priority between fade manager configs: Transient > Updated > Default */
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration getActiveFadeMgrConfigLocked() {
+ // below configs are arranged in the order of priority
+ // configs placed higher have higher priority
+ if (mTransientFadeManagerConfig != null) {
+ return mTransientFadeManagerConfig;
+ }
+
+ if (mUpdatedFadeManagerConfig != null) {
+ return mUpdatedFadeManagerConfig;
+ }
+
+ // default - must be the lowest priority
+ return getDefaultFadeManagerConfigLocked();
+ }
+
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration getDefaultFadeManagerConfigLocked() {
+ if (mDefaultFadeManagerConfig == null) {
+ mDefaultFadeManagerConfig = new FadeManagerConfiguration.Builder().build();
+ }
+ return mDefaultFadeManagerConfig;
}
}
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index 1171f97..2cceb5a 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -16,21 +16,24 @@
package com.android.server.audio;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
+import android.media.FadeManagerConfiguration;
import android.media.VolumeShaper;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Objects;
+import java.util.List;
+import java.util.Map;
/**
* Class to handle fading out players
@@ -40,14 +43,6 @@
public static final String TAG = "AS.FadeOutManager";
private static final boolean DEBUG = PlaybackActivityMonitor.DEBUG;
- private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED =
- new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY)
- .createIfNeeded()
- .build();
-
- // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp
- private static final VolumeShaper.Operation PLAY_SKIP_RAMP =
- new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build();
private final Object mLock = new Object();
@@ -57,16 +52,81 @@
@GuardedBy("mLock")
private final SparseArray<FadedOutApp> mUidToFadedAppsMap = new SparseArray<>();
- @GuardedBy("mLock")
- private FadeConfigurations mFadeConfigurations;
+ private final FadeConfigurations mFadeConfigurations = new FadeConfigurations();
- public FadeOutManager() {
- mFadeConfigurations = new FadeConfigurations();
+ /**
+ * Sets the custom fade manager configuration to be used for player fade out and in
+ *
+ * @param fadeManagerConfig custom fade manager configuration
+ * @return {@link AudioManager#SUCCESS} if setting fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int setFadeManagerConfiguration(FadeManagerConfiguration fadeManagerConfig) {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.setFadeManagerConfiguration(fadeManagerConfig);
+ }
}
- public FadeOutManager(FadeConfigurations fadeConfigurations) {
- mFadeConfigurations = Objects.requireNonNull(fadeConfigurations,
- "Fade configurations can not be null");
+ /**
+ * Clears the fade manager configuration that was previously set with
+ * {@link #setFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if clearing fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int clearFadeManagerConfiguration() {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.clearFadeManagerConfiguration();
+ }
+ }
+
+ /**
+ * Returns the active fade manager configuration
+ *
+ * @return the {@link FadeManagerConfiguration}
+ */
+ FadeManagerConfiguration getFadeManagerConfiguration() {
+ return mFadeConfigurations.getFadeManagerConfiguration();
+ }
+
+ /**
+ * Sets the transient fade manager configuration to be used for player fade out and in
+ *
+ * @param fadeManagerConfig fade manager config that has higher priority than the existing
+ * fade manager configuration. This is expected to be transient.
+ * @return {@link AudioManager#SUCCESS} if setting fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int setTransientFadeManagerConfiguration(FadeManagerConfiguration fadeManagerConfig) {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.setTransientFadeManagerConfiguration(fadeManagerConfig);
+ }
+ }
+
+ /**
+ * Clears the transient fade manager configuration that was previously set with
+ * {@link #setTransientFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if clearing fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int clearTransientFadeManagerConfiguration() {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.clearTransientFadeManagerConfiguration();
+ }
+ }
+
+ /**
+ * Query if fade is enblead and can be enforced on players
+ *
+ * @return {@code true} if fade is enabled, {@code false} otherwise.
+ */
+ boolean isFadeEnabled() {
+ return mFadeConfigurations.isFadeEnabled();
}
// TODO explore whether a shorter fade out would be a better UX instead of not fading out at all
@@ -128,7 +188,7 @@
}
}
- void fadeOutUid(int uid, ArrayList<AudioPlaybackConfiguration> players) {
+ void fadeOutUid(int uid, List<AudioPlaybackConfiguration> players) {
Slog.i(TAG, "fadeOutUid() uid:" + uid);
synchronized (mLock) {
if (!mUidToFadedAppsMap.contains(uid)) {
@@ -148,15 +208,31 @@
* @param uid the uid for the app to unfade out
* @param players map of current available players (so we can get an APC from piid)
*/
- void unfadeOutUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) {
+ void unfadeOutUid(int uid, Map<Integer, AudioPlaybackConfiguration> players) {
Slog.i(TAG, "unfadeOutUid() uid:" + uid);
synchronized (mLock) {
- final FadedOutApp fa = mUidToFadedAppsMap.get(uid);
+ FadedOutApp fa = mUidToFadedAppsMap.get(uid);
if (fa == null) {
return;
}
mUidToFadedAppsMap.remove(uid);
- fa.removeUnfadeAll(players);
+
+ if (!enableFadeManagerConfiguration()) {
+ fa.removeUnfadeAll(players);
+ return;
+ }
+
+ // since fade manager configs may have volume-shaper config per audio attributes,
+ // iterate through each palyer and gather respective configs for fade in
+ ArrayList<AudioPlaybackConfiguration> apcs = new ArrayList<>(players.values());
+ for (int index = 0; index < apcs.size(); index++) {
+ AudioPlaybackConfiguration apc = apcs.get(index);
+ VolumeShaper.Configuration config = mFadeConfigurations
+ .getFadeInVolumeShaperConfig(apc.getAudioAttributes());
+ fa.fadeInPlayer(apc, config);
+ }
+ // ideal case all players should be faded in
+ fa.clear();
}
}
@@ -209,16 +285,6 @@
}
}
- /**
- * Update fade configurations used for player fade operations
- * @param fadeConfigurations set of configs that define fade properties
- */
- void setFadeConfigurations(@NonNull FadeConfigurations fadeConfigurations) {
- synchronized (mLock) {
- mFadeConfigurations = fadeConfigurations;
- }
- }
-
void dump(PrintWriter pw) {
synchronized (mLock) {
for (int index = 0; index < mUidToFadedAppsMap.size(); index++) {
@@ -232,6 +298,15 @@
* Class to group players from a common app, that are faded out.
*/
private static final class FadedOutApp {
+ private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED =
+ new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY)
+ .createIfNeeded()
+ .build();
+
+ // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp
+ private static final VolumeShaper.Operation PLAY_SKIP_RAMP =
+ new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build();
+
private final int mUid;
// key -> piid; value -> volume shaper config applied
private final SparseArray<VolumeShaper.Configuration> mFadedPlayers = new SparseArray<>();
@@ -269,17 +344,8 @@
return;
}
if (apc.getPlayerProxy() != null) {
- try {
- PlaybackActivityMonitor.sEventLogger.enqueue(
- (new PlaybackActivityMonitor.FadeOutEvent(apc, skipRamp)).printLog(
- TAG));
- apc.getPlayerProxy().applyVolumeShaper(volShaper,
- skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
- mFadedPlayers.put(piid, volShaper);
- } catch (Exception e) {
- Slog.e(TAG, "Error fading out player piid:" + piid
- + " uid:" + apc.getClientUid(), e);
- }
+ applyVolumeShaperInternal(apc, piid, volShaper,
+ skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
} else {
if (DEBUG) {
Slog.v(TAG, "Error fading out player piid:" + piid
@@ -288,21 +354,13 @@
}
}
- void removeUnfadeAll(HashMap<Integer, AudioPlaybackConfiguration> players) {
+ void removeUnfadeAll(Map<Integer, AudioPlaybackConfiguration> players) {
for (int index = 0; index < mFadedPlayers.size(); index++) {
int piid = mFadedPlayers.keyAt(index);
final AudioPlaybackConfiguration apc = players.get(piid);
if ((apc != null) && (apc.getPlayerProxy() != null)) {
- final VolumeShaper.Configuration volShaper = mFadedPlayers.valueAt(index);
- try {
- PlaybackActivityMonitor.sEventLogger.enqueue(
- (new EventLogger.StringEvent("unfading out piid:"
- + piid)).printLog(TAG));
- apc.getPlayerProxy().applyVolumeShaper(volShaper,
- VolumeShaper.Operation.REVERSE);
- } catch (Exception e) {
- Slog.e(TAG, "Error unfading out player piid:" + piid + " uid:" + mUid, e);
- }
+ applyVolumeShaperInternal(apc, piid, /* volShaperConfig= */ null,
+ VolumeShaper.Operation.REVERSE);
} else {
// this piid was in the list of faded players, but wasn't found
if (DEBUG) {
@@ -314,8 +372,61 @@
mFadedPlayers.clear();
}
+ void fadeInPlayer(@NonNull AudioPlaybackConfiguration apc,
+ @Nullable VolumeShaper.Configuration config) {
+ int piid = Integer.valueOf(apc.getPlayerInterfaceId());
+ // if not found, no need to fade in since it was never faded out
+ if (!mFadedPlayers.contains(piid)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Player (piid: " + piid + ") for uid (" + mUid
+ + ") is not faded out, no need to fade in");
+ }
+ return;
+ }
+
+ mFadedPlayers.remove(piid);
+ if (apc.getPlayerProxy() != null) {
+ applyVolumeShaperInternal(apc, piid, config,
+ config != null ? PLAY_CREATE_IF_NEEDED : VolumeShaper.Operation.REVERSE);
+ } else {
+ if (DEBUG) {
+ Slog.v(TAG, "Error fading in player piid:" + piid
+ + ", player not found for uid " + mUid);
+ }
+ }
+ }
+
+ void clear() {
+ if (mFadedPlayers.size() > 0) {
+ if (DEBUG) {
+ Slog.v(TAG, "Non empty faded players list being cleared! Faded out players:"
+ + mFadedPlayers);
+ }
+ }
+ // should the players be faded in irrespective?
+ mFadedPlayers.clear();
+ }
+
void removeReleased(@NonNull AudioPlaybackConfiguration apc) {
mFadedPlayers.delete(Integer.valueOf(apc.getPlayerInterfaceId()));
}
+
+ private void applyVolumeShaperInternal(AudioPlaybackConfiguration apc, int piid,
+ VolumeShaper.Configuration volShaperConfig, VolumeShaper.Operation operation) {
+ VolumeShaper.Configuration config = volShaperConfig;
+ // when operation is reverse, use the fade out volume shaper config instead
+ if (operation.equals(VolumeShaper.Operation.REVERSE)) {
+ config = mFadedPlayers.get(piid);
+ }
+ try {
+ PlaybackActivityMonitor.sEventLogger.enqueue(
+ (new PlaybackActivityMonitor.FadeEvent(apc, config, operation))
+ .printLog(TAG));
+ apc.getPlayerProxy().applyVolumeShaper(config, operation);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error fading player piid:" + piid + " uid:" + mUid
+ + " operation:" + operation, e);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index 00c04ff..f462539 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -33,6 +33,7 @@
import com.android.server.pm.UserManagerInternal;
import java.io.PrintWriter;
+import java.util.List;
/**
* @hide
@@ -534,6 +535,33 @@
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
+ @GuardedBy("MediaFocusControl.mAudioFocusLock")
+ int dispatchFocusChangeWithFadeLocked(int focusChange, List<FocusRequester> otherActiveFrs) {
+ if (focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
+ || focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
+ || focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
+ || focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+ mFocusLossFadeLimbo = false;
+ mFocusController.restoreVShapedPlayers(this);
+ } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS
+ && mFocusController.shouldEnforceFade()) {
+ for (int index = 0; index < otherActiveFrs.size(); index++) {
+ // candidate for fade-out before a receiving a loss
+ if (mFocusController.fadeOutPlayers(otherActiveFrs.get(index), /* loser= */ this)) {
+ // active players are being faded out, delay the dispatch of focus loss
+ // mark this instance as being faded so it's not released yet as the focus loss
+ // will be dispatched later, it is now in limbo mode
+ mFocusLossFadeLimbo = true;
+ mFocusController.postDelayedLossAfterFade(this,
+ mFocusController.getFadeOutDurationOnFocusLossMillis(
+ this.getAudioAttributes()));
+ return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
+ }
+ }
+ }
+ return dispatchFocusChange(focusChange);
+ }
+
void dispatchFocusResultFromExtPolicy(int requestResult) {
final IAudioFocusDispatcher fd = mFocusDispatcher;
if (fd == null) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 58f5d5e..0df0006 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -16,6 +16,8 @@
package com.android.server.audio;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -195,6 +197,15 @@
}
return mFocusEnforcer.getFadeInDelayForOffendersMillis(aa);
}
+
+ @Override
+ public boolean shouldEnforceFade() {
+ if (!enableFadeManagerConfiguration()) {
+ return ENFORCE_FADEOUT_FOR_FOCUS_LOSS;
+ }
+
+ return mFocusEnforcer.shouldEnforceFade();
+ }
//==========================================================================================
// AudioFocus
//==========================================================================================
@@ -861,14 +872,17 @@
return;
}
}
- final FocusRequester fr;
- if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
- fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
- } else {
- fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
- }
- if (fr != null) {
- fr.dispatchFocusResultFromExtPolicy(requestResult);
+ synchronized (mAudioFocusLock) {
+ FocusRequester fr = getFocusRequesterLocked(afi.getClientId(),
+ /* shouldRemove= */ requestResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED);
+ if (fr != null) {
+ fr.dispatchFocusResultFromExtPolicy(requestResult);
+ // if fade is enabled for external focus policies, apply it when setting
+ // focus result as well
+ if (enableFadeManagerConfiguration()) {
+ fr.handleFocusGainFromRequest(requestResult);
+ }
+ }
}
}
@@ -902,24 +916,80 @@
+ afi.getClientId());
}
synchronized (mAudioFocusLock) {
- if (mFocusPolicy == null) {
- if (DEBUG) { Log.v(TAG, "> failed: no focus policy" ); }
- return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
- }
- final FocusRequester fr;
- if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
- fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
- } else {
- fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
- }
+ FocusRequester fr = getFocusRequesterLocked(afi.getClientId(),
+ /* shouldRemove= */ focusChange == AudioManager.AUDIOFOCUS_LOSS);
if (fr == null) {
- if (DEBUG) { Log.v(TAG, "> failed: no such focus requester known" ); }
+ if (DEBUG) {
+ Log.v(TAG, "> failed: no such focus requester known");
+ }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
return fr.dispatchFocusChange(focusChange);
}
}
+ int dispatchFocusChangeWithFade(AudioFocusInfo afi, int focusChange,
+ List<AudioFocusInfo> otherActiveAfis) {
+ if (DEBUG) {
+ Log.v(TAG, "dispatchFocusChangeWithFade " + AudioManager.audioFocusToString(focusChange)
+ + " to afi client=" + afi.getClientId()
+ + " other active afis=" + otherActiveAfis);
+ }
+
+ synchronized (mAudioFocusLock) {
+ String clientId = afi.getClientId();
+ // do not remove the entry since it can be posted for fade
+ FocusRequester fr = getFocusRequesterLocked(clientId, /* shouldRemove= */ false);
+ if (fr == null) {
+ if (DEBUG) {
+ Log.v(TAG, "> failed: no such focus requester known");
+ }
+ return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ }
+
+ // convert other AudioFocusInfo to corresponding FocusRequester
+ ArrayList<FocusRequester> otherActiveFrs = new ArrayList<>();
+ for (int index = 0; index < otherActiveAfis.size(); index++) {
+ FocusRequester otherFr = getFocusRequesterLocked(
+ otherActiveAfis.get(index).getClientId(), /* shouldRemove= */ false);
+ if (otherFr == null) {
+ continue;
+ }
+ otherActiveFrs.add(otherFr);
+ }
+
+ int status = fr.dispatchFocusChangeWithFadeLocked(focusChange, otherActiveFrs);
+ if (status != AudioManager.AUDIOFOCUS_REQUEST_DELAYED
+ && focusChange == AudioManager.AUDIOFOCUS_LOSS) {
+ mFocusOwnersForFocusPolicy.remove(clientId);
+ }
+
+ return status;
+ }
+ }
+
+ @GuardedBy("mAudioFocusLock")
+ private FocusRequester getFocusRequesterLocked(String clientId, boolean shouldRemove) {
+ if (mFocusPolicy == null) {
+ if (DEBUG) {
+ Log.v(TAG, "> failed: no focus policy");
+ }
+ return null;
+ }
+
+ FocusRequester fr;
+ if (shouldRemove) {
+ fr = mFocusOwnersForFocusPolicy.remove(clientId);
+ } else {
+ fr = mFocusOwnersForFocusPolicy.get(clientId);
+ }
+
+ if (fr == null && DEBUG) {
+ Log.v(TAG, "> failed: no such focus requester known");
+ }
+ return fr;
+ }
+
private void dumpExtFocusPolicyFocusOwners(PrintWriter pw) {
final Set<Entry<String, FocusRequester>> owners = mFocusOwnersForFocusPolicy.entrySet();
final Iterator<Entry<String, FocusRequester>> ownerIterator = owners.iterator();
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index bc9b9b4..e69fbbd 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -38,6 +38,7 @@
import android.media.AudioPlaybackConfiguration.FormatInfo;
import android.media.AudioPlaybackConfiguration.PlayerMuteEvent;
import android.media.AudioSystem;
+import android.media.FadeManagerConfiguration;
import android.media.IPlaybackConfigDispatcher;
import android.media.PlayerBase;
import android.media.VolumeShaper;
@@ -156,8 +157,7 @@
private final int mMaxAlarmVolume;
private int mPrivilegedAlarmActiveCount = 0;
private final Consumer<AudioDeviceAttributes> mMuteAwaitConnectionTimeoutCb;
- private final FadeOutManager mFadeOutManager;
-
+ private final FadeOutManager mFadeOutManager = new FadeOutManager();
PlaybackActivityMonitor(Context context, int maxAlarmVolume,
Consumer<AudioDeviceAttributes> muteTimeoutCallback) {
@@ -167,7 +167,6 @@
AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
mMuteAwaitConnectionTimeoutCb = muteTimeoutCallback;
initEventHandler();
- mFadeOutManager = new FadeOutManager(new FadeConfigurations());
}
//=================================================================
@@ -971,6 +970,12 @@
return mFadeOutManager.getFadeInDelayForOffendersMillis(aa);
}
+ @Override
+ public boolean shouldEnforceFade() {
+ return mFadeOutManager.isFadeEnabled();
+ }
+
+
//=================================================================
// Track playback activity listeners
@@ -1010,6 +1015,27 @@
}
}
+ int setFadeManagerConfiguration(int focusType, FadeManagerConfiguration fadeMgrConfig) {
+ return mFadeOutManager.setFadeManagerConfiguration(fadeMgrConfig);
+ }
+
+ int clearFadeManagerConfiguration(int focusType) {
+ return mFadeOutManager.clearFadeManagerConfiguration();
+ }
+
+ FadeManagerConfiguration getFadeManagerConfiguration(int focusType) {
+ return mFadeOutManager.getFadeManagerConfiguration();
+ }
+
+ int setTransientFadeManagerConfiguration(int focusType,
+ FadeManagerConfiguration fadeMgrConfig) {
+ return mFadeOutManager.setTransientFadeManagerConfiguration(fadeMgrConfig);
+ }
+
+ int clearTransientFadeManagerConfiguration(int focusType) {
+ return mFadeOutManager.clearTransientFadeManagerConfiguration();
+ }
+
/**
* Inner class to track clients that want to be notified of playback updates
*/
@@ -1337,6 +1363,38 @@
}
}
+ static final class FadeEvent extends EventLogger.Event {
+ private final int mPlayerIId;
+ private final int mPlayerType;
+ private final int mClientUid;
+ private final int mClientPid;
+ private final AudioAttributes mPlayerAttr;
+ private final VolumeShaper.Configuration mVShaper;
+ private final VolumeShaper.Operation mVOperation;
+
+ FadeEvent(AudioPlaybackConfiguration apc, VolumeShaper.Configuration vShaper,
+ VolumeShaper.Operation vOperation) {
+ mPlayerIId = apc.getPlayerInterfaceId();
+ mClientUid = apc.getClientUid();
+ mClientPid = apc.getClientPid();
+ mPlayerAttr = apc.getAudioAttributes();
+ mPlayerType = apc.getPlayerType();
+ mVShaper = vShaper;
+ mVOperation = vOperation;
+ }
+
+ @Override
+ public String eventToString() {
+ return "Fade Event:" + " player piid:" + mPlayerIId
+ + " uid/pid:" + mClientUid + "/" + mClientPid
+ + " player type:"
+ + AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType)
+ + " attr:" + mPlayerAttr
+ + " volume shaper:" + mVShaper
+ + " volume operation:" + mVOperation;
+ }
+ }
+
private abstract static class VolumeShaperEvent extends EventLogger.Event {
private final int mPlayerIId;
private final boolean mSkipRamp;
diff --git a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
index f1d42f3..4a29eca 100644
--- a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
+++ b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
@@ -79,4 +79,11 @@
* @return delay in milliseconds
*/
long getFadeInDelayForOffendersMillis(@NonNull AudioAttributes aa);
+
+ /**
+ * Check if the fade should be enforced
+ *
+ * @return {@code true} if fade should be enforced, {@code false} otherwise
+ */
+ boolean shouldEnforceFade();
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 4f7f31d..8428f12 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -1639,8 +1639,7 @@
return -1;
}
final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
- List<String> deviceAddresses = mAudioService.getDeviceAddresses(currentDevice);
-
+ List<String> deviceAddresses = mAudioService.getDeviceIdentityAddresses(currentDevice);
// We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion
// with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR
// and Sensor.TYPE_GAME_ROTATION_VECTOR are supported internally by
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index dafea9a..d5d8fd2 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -51,9 +51,13 @@
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceService;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintService;
@@ -122,8 +126,6 @@
/**
* Allows to test with various device sensor configurations.
- * @param context
- * @return
*/
@VisibleForTesting
public String[] getConfiguration(Context context) {
@@ -131,6 +133,30 @@
}
/**
+ * Allows to test with various device sensor configurations.
+ */
+ @VisibleForTesting
+ public String[] getFingerprintConfiguration(Context context) {
+ return getConfiguration(context);
+ }
+
+ /**
+ * Allows to test with various device sensor configurations.
+ */
+ @VisibleForTesting
+ public String[] getFaceConfiguration(Context context) {
+ return getConfiguration(context);
+ }
+
+ /**
+ * Allows to test with various device sensor configurations.
+ */
+ @VisibleForTesting
+ public String[] getIrisConfiguration(Context context) {
+ return getConfiguration(context);
+ }
+
+ /**
* Allows us to mock FingerprintService for testing
*/
@VisibleForTesting
@@ -173,6 +199,22 @@
}
return false;
}
+
+ /**
+ * Allows to test with various fingerprint aidl instances.
+ */
+ @VisibleForTesting
+ public String[] getFingerprintAidlInstances() {
+ return ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
+ }
+
+ /**
+ * Allows to test with various face aidl instances.
+ */
+ @VisibleForTesting
+ public String[] getFaceAidlInstances() {
+ return ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+ }
}
private final class AuthServiceImpl extends IAuthService.Stub {
@@ -717,12 +759,129 @@
hidlConfigs = null;
}
- // Registers HIDL and AIDL authenticators, but only HIDL configs need to be provided.
- registerAuthenticators(hidlConfigs);
+ if (com.android.server.biometrics.Flags.deHidl()) {
+ Slog.d(TAG, "deHidl flag is on.");
+ registerAuthenticators();
+ } else {
+ // Registers HIDL and AIDL authenticators, but only HIDL configs need to be provided.
+ registerAuthenticators(hidlConfigs);
+ }
mInjector.publishBinderService(this, mImpl);
}
+ private void registerAuthenticators() {
+ registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
+ mInjector.getFingerprintConfiguration(getContext()));
+ registerFaceSensors(mInjector.getFaceAidlInstances(),
+ mInjector.getFaceConfiguration(getContext()));
+ registerIrisSensors(mInjector.getIrisConfiguration(getContext()));
+ }
+
+ private void registerIrisSensors(String[] hidlConfigStrings) {
+ final SensorConfig[] hidlConfigs;
+ if (!mInjector.isHidlDisabled(getContext())) {
+ final int firstApiLevel = SystemProperties.getInt(SYSPROP_FIRST_API_LEVEL, 0);
+ final int apiLevel = SystemProperties.getInt(SYSPROP_API_LEVEL, firstApiLevel);
+ if (hidlConfigStrings.length == 0 && apiLevel == Build.VERSION_CODES.R) {
+ // For backwards compatibility with R where biometrics could work without being
+ // configured in config_biometric_sensors. In the absence of a vendor provided
+ // configuration, we assume the weakest biometric strength (i.e. convenience).
+ Slog.w(TAG, "Found R vendor partition without config_biometric_sensors");
+ hidlConfigStrings = generateRSdkCompatibleConfiguration();
+ }
+ hidlConfigs = new SensorConfig[hidlConfigStrings.length];
+ for (int i = 0; i < hidlConfigStrings.length; ++i) {
+ hidlConfigs[i] = new SensorConfig(hidlConfigStrings[i]);
+ }
+ } else {
+ hidlConfigs = null;
+ }
+
+ final List<SensorPropertiesInternal> hidlIrisSensors = new ArrayList<>();
+
+ if (hidlConfigs != null) {
+ for (SensorConfig sensor : hidlConfigs) {
+ switch (sensor.modality) {
+ case TYPE_IRIS:
+ hidlIrisSensors.add(getHidlIrisSensorProps(sensor.id, sensor.strength));
+ break;
+
+ default:
+ Slog.e(TAG, "Unknown modality: " + sensor.modality);
+ }
+ }
+ }
+
+ final IIrisService irisService = mInjector.getIrisService();
+ if (irisService != null) {
+ try {
+ irisService.registerAuthenticators(hidlIrisSensors);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering iris authenticators", e);
+ }
+ } else if (hidlIrisSensors.size() > 0) {
+ Slog.e(TAG, "HIDL iris configuration exists, but IrisService is null.");
+ }
+ }
+
+ private void registerFaceSensors(final String[] faceAidlInstances,
+ final String[] hidlConfigStrings) {
+ final FaceSensorConfigurations mFaceSensorConfigurations =
+ new FaceSensorConfigurations(hidlConfigStrings != null
+ && hidlConfigStrings.length > 0);
+
+ if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+ mFaceSensorConfigurations.addHidlConfigs(
+ hidlConfigStrings, getContext());
+ }
+
+ if (faceAidlInstances != null && faceAidlInstances.length > 0) {
+ mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
+ name -> IFace.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(name))));
+ }
+
+ final IFaceService faceService = mInjector.getFaceService();
+ if (faceService != null) {
+ try {
+ faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering face authenticators", e);
+ }
+ } else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
+ Slog.e(TAG, "Face configuration exists, but FaceService is null.");
+ }
+ }
+
+ private void registerFingerprintSensors(final String[] fingerprintAidlInstances,
+ final String[] hidlConfigStrings) {
+ final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
+ new FingerprintSensorConfigurations(!(hidlConfigStrings != null
+ && hidlConfigStrings.length > 0));
+
+ if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+ mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, getContext());
+ }
+
+ if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
+ mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
+ name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(name))));
+ }
+
+ final IFingerprintService fingerprintService = mInjector.getFingerprintService();
+ if (fingerprintService != null) {
+ try {
+ fingerprintService.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+ }
+ } else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
+ Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
+ }
+ }
+
/**
* Generates an array of string configs with entries that correspond to the biometric features
* declared on the device. Returns an empty array if no biometric features are declared.
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 037ea38a..89b638b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -202,7 +202,7 @@
};
@VisibleForTesting
- BiometricScheduler(@NonNull String tag,
+ public BiometricScheduler(@NonNull String tag,
@NonNull Handler handler,
@SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
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 57feedc..0c3dfa7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -387,6 +387,11 @@
return isAuthentication || isDetection;
}
+ /** If this operation is {@link StartUserClient}. */
+ public boolean isStartUserOperation() {
+ return mClientMonitor instanceof StartUserClient<?, ?>;
+ }
+
/** If this operation performs acquisition {@link AcquisitionClient}. */
public boolean isAcquisitionOperation() {
return mClientMonitor instanceof AcquisitionClient;
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 7f8f38f..6daaad1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -54,8 +54,6 @@
// is all done internally.
super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
0 /* cookie */, sensorId, logger, biometricContext);
- //, BiometricsProtoEnums.ACTION_ENUMERATE,
- // BiometricsProtoEnums.CLIENT_UNKNOWN);
mEnrolledList = enrolledList;
mUtils = utils;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index 8075470..3753bbd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -135,7 +135,7 @@
final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
- if (nextUserId == currentUserId) {
+ if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) {
super.startNextOperationIfIdle();
} else if (currentUserId == UserHandle.USER_NULL) {
final BaseClientMonitor startClient =
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 578d9dc..6af223b 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
@@ -36,6 +36,7 @@
import android.hardware.biometrics.face.SensorProps;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
+import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.FaceServiceReceiver;
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
@@ -55,6 +56,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.Surface;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
@@ -76,6 +78,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.function.Supplier;
/**
* A service to manage multiple clients that want to access the face HAL API.
@@ -86,7 +89,7 @@
protected static final String TAG = "FaceService";
- private final FaceServiceWrapper mServiceWrapper;
+ @VisibleForTesting final FaceServiceWrapper mServiceWrapper;
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final LockPatternUtils mLockPatternUtils;
@NonNull
@@ -94,11 +97,18 @@
@NonNull
private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
mBiometricStateCallback;
+ @NonNull
+ private final FaceProviderFunction mFaceProviderFunction;
+
+ interface FaceProviderFunction {
+ FaceProvider getFaceProvider(Pair<String, SensorProps[]> filteredSensorProps,
+ boolean resetLockoutRequiresChallenge);
+ }
/**
* Receives the incoming binder calls from FaceManager.
*/
- private final class FaceServiceWrapper extends IFaceService.Stub {
+ @VisibleForTesting final class FaceServiceWrapper extends IFaceService.Stub {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@@ -672,7 +682,8 @@
final SensorProps[] props = face.getSensorProps();
final FaceProvider provider = new FaceProvider(getContext(),
mBiometricStateCallback, props, instance, mLockoutResetDispatcher,
- BiometricContext.getInstance(getContext()));
+ BiometricContext.getInstance(getContext()),
+ false /* resetLockoutRequiresChallenge */);
providers.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -704,6 +715,55 @@
});
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ public void registerAuthenticatorsLegacy(
+ FaceSensorConfigurations faceSensorConfigurations) {
+ super.registerAuthenticatorsLegacy_enforcePermission();
+
+ if (!faceSensorConfigurations.hasSensorConfigurations()) {
+ Slog.d(TAG, "No face sensors to register.");
+ return;
+ }
+ mRegistry.registerAll(() -> getProviders(faceSensorConfigurations));
+ }
+
+ private List<ServiceProvider> getProviders(
+ FaceSensorConfigurations faceSensorConfigurations) {
+ final List<ServiceProvider> providers = new ArrayList<>();
+ final Pair<String, SensorProps[]> filteredSensorProps =
+ filterAvailableHalInstances(faceSensorConfigurations);
+ providers.add(mFaceProviderFunction.getFaceProvider(filteredSensorProps,
+ faceSensorConfigurations.getResetLockoutRequiresChallenge()));
+ return providers;
+ }
+
+ @NonNull
+ private Pair<String, SensorProps[]> filterAvailableHalInstances(
+ FaceSensorConfigurations faceSensorConfigurations) {
+ Pair<String, SensorProps[]> finalSensorPair = faceSensorConfigurations.getSensorPair();
+
+ if (faceSensorConfigurations.isSingleSensorConfigurationPresent()) {
+ return finalSensorPair;
+ }
+
+ final Pair<String, SensorProps[]> virtualSensorProps = faceSensorConfigurations
+ .getSensorPairForInstance("virtual");
+
+ if (Utils.isVirtualEnabled(getContext())) {
+ if (virtualSensorProps != null) {
+ return virtualSensorProps;
+ } else {
+ Slog.e(TAG, "Could not find virtual interface while it is enabled");
+ return finalSensorPair;
+ }
+ } else {
+ if (virtualSensorProps != null) {
+ return faceSensorConfigurations.getSensorPairNotForInstance("virtual");
+ }
+ }
+ return finalSensorPair;
+ }
+
private Pair<List<FaceSensorPropertiesInternal>, List<String>>
filterAvailableHalInstances(
@NonNull List<FaceSensorPropertiesInternal> hidlInstances,
@@ -752,20 +812,36 @@
}
public FaceService(Context context) {
+ this(context, null /* faceProviderFunction */, () -> IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+ }
+
+ @VisibleForTesting FaceService(Context context, FaceProviderFunction faceProviderFunction,
+ Supplier<IBiometricService> biometricServiceSupplier) {
super(context);
mServiceWrapper = new FaceServiceWrapper();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
- mRegistry = new FaceServiceRegistry(mServiceWrapper,
- () -> IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+ mRegistry = new FaceServiceRegistry(mServiceWrapper, biometricServiceSupplier);
mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@Override
public void onAllAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
mBiometricStateCallback.start(mRegistry.getProviders());
}
});
+
+ if (Flags.deHidl()) {
+ mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
+ ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
+ getContext(), mBiometricStateCallback,
+ filteredSensorProps.second,
+ filteredSensorProps.first, mLockoutResetDispatcher,
+ BiometricContext.getInstance(getContext()),
+ resetLockoutRequiresChallenge));
+ } else {
+ mFaceProviderFunction = ((filteredSensorProps, resetLockoutRequiresChallenge) -> null);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
index e5d4a63..ef2be79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
@@ -24,7 +24,8 @@
* the user changes.
*/
public class LockoutHalImpl implements LockoutTracker {
- private @LockoutMode int mCurrentUserLockoutMode;
+ @LockoutMode
+ private int mCurrentUserLockoutMode;
@Override
public int getLockoutModeForUser(int userId) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
index 57f5b34..098be21 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
@@ -27,6 +27,7 @@
import android.hardware.keymaster.HardwareAuthToken;
import android.util.Slog;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -59,6 +60,20 @@
void onHardwareUnavailable();
}
+ /**
+ * Interface to send results to the AidlResponseHandler's owner.
+ */
+ public interface AidlResponseHandlerCallback {
+ /**
+ * Invoked when enrollment is successful.
+ */
+ void onEnrollSuccess();
+ /**
+ * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+ */
+ void onHardwareUnavailable();
+ }
+
private static final String TAG = "AidlResponseHandler";
@NonNull
@@ -68,7 +83,7 @@
private final int mSensorId;
private final int mUserId;
@NonNull
- private final LockoutTracker mLockoutCache;
+ private final LockoutTracker mLockoutTracker;
@NonNull
private final LockoutResetDispatcher mLockoutResetDispatcher;
@@ -76,6 +91,8 @@
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull
private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+ @NonNull
+ private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
public AidlResponseHandler(@NonNull Context context,
@NonNull BiometricScheduler scheduler, int sensorId, int userId,
@@ -83,14 +100,33 @@
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
@NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+ this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
+ authSessionCoordinator, hardwareUnavailableCallback,
+ new AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {}
+
+ @Override
+ public void onHardwareUnavailable() {}
+ });
+ }
+
+ public AidlResponseHandler(@NonNull Context context,
+ @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull LockoutTracker lockoutTracker,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
+ @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
mContext = context;
mScheduler = scheduler;
mSensorId = sensorId;
mUserId = userId;
- mLockoutCache = lockoutTracker;
+ mLockoutTracker = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mAuthSessionCoordinator = authSessionCoordinator;
mHardwareUnavailableCallback = hardwareUnavailableCallback;
+ mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
}
@Override
@@ -106,13 +142,13 @@
@Override
public void onChallengeGenerated(long challenge) {
handleResponse(FaceGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(mSensorId,
- mUserId, challenge), null);
+ mUserId, challenge));
}
@Override
public void onChallengeRevoked(long challenge) {
handleResponse(FaceRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(mSensorId,
- mUserId, challenge), null);
+ mUserId, challenge));
}
@Override
@@ -123,7 +159,7 @@
return;
}
c.onAuthenticationFrame(AidlConversionUtils.toFrameworkAuthenticationFrame(frame));
- }, null);
+ });
}
@Override
@@ -134,7 +170,7 @@
return;
}
c.onEnrollmentFrame(AidlConversionUtils.toFrameworkEnrollmentFrame(frame));
- }, null);
+ });
}
@Override
@@ -149,9 +185,13 @@
handleResponse(ErrorConsumer.class, (c) -> {
c.onError(error, vendorCode);
if (error == Error.HW_UNAVAILABLE) {
- mHardwareUnavailableCallback.onHardwareUnavailable();
+ if (Flags.deHidl()) {
+ mAidlResponseHandlerCallback.onHardwareUnavailable();
+ } else {
+ mHardwareUnavailableCallback.onHardwareUnavailable();
+ }
}
- }, null);
+ });
}
@Override
@@ -167,7 +207,12 @@
.getUniqueName(mContext, currentUserId);
final Face face = new Face(name, enrollmentId, mSensorId);
- handleResponse(FaceEnrollClient.class, (c) -> c.onEnrollResult(face, remaining), null);
+ handleResponse(FaceEnrollClient.class, (c) -> {
+ c.onEnrollResult(face, remaining);
+ if (remaining == 0) {
+ mAidlResponseHandlerCallback.onEnrollSuccess();
+ }
+ });
}
@Override
@@ -179,37 +224,37 @@
byteList.add(b);
}
handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
- true /* authenticated */, byteList), null);
+ true /* authenticated */, byteList));
}
@Override
public void onAuthenticationFailed() {
final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId);
handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
- false /* authenticated */, null /* hat */), null);
+ false /* authenticated */, null /* hat */));
}
@Override
public void onLockoutTimed(long durationMillis) {
- handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), null);
+ handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis));
}
@Override
public void onLockoutPermanent() {
- handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+ handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent);
}
@Override
public void onLockoutCleared() {
handleResponse(FaceResetLockoutClient.class, FaceResetLockoutClient::onLockoutCleared,
(c) -> FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
- mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- Utils.getCurrentStrength(mSensorId), -1 /* requestId */));
+ mLockoutTracker, mLockoutResetDispatcher, mAuthSessionCoordinator,
+ Utils.getCurrentStrength(mSensorId), -1 /* requestId */));
}
@Override
public void onInteractionDetected() {
- handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected, null);
+ handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected);
}
@Override
@@ -219,23 +264,23 @@
final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
final int finalI = i;
handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(face,
- enrollmentIds.length - finalI - 1), null);
+ enrollmentIds.length - finalI - 1 /* remaining */));
}
} else {
handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(
- null /* identifier */, 0 /* remaining */), null);
+ null /* identifier */, 0 /* remaining */));
}
}
@Override
public void onFeaturesRetrieved(byte[] features) {
handleResponse(FaceGetFeatureClient.class, (c) -> c.onFeatureGet(true /* success */,
- features), null);
+ features));
}
@Override
public void onFeatureSet(byte feature) {
- handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */), null);
+ handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */));
}
@Override
@@ -245,33 +290,32 @@
final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
final int finalI = i;
handleResponse(RemovalConsumer.class,
- (c) -> c.onRemoved(face, enrollmentIds.length - finalI - 1),
- null);
+ (c) -> c.onRemoved(face,
+ enrollmentIds.length - finalI - 1 /* remaining */));
}
} else {
handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */,
- 0 /* remaining */), null);
+ 0 /* remaining */));
}
}
@Override
public void onAuthenticatorIdRetrieved(long authenticatorId) {
handleResponse(FaceGetAuthenticatorIdClient.class, (c) -> c.onAuthenticatorIdRetrieved(
- authenticatorId), null);
+ authenticatorId));
}
@Override
public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
handleResponse(FaceInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
- newAuthenticatorId), null);
+ newAuthenticatorId));
}
/**
* Handles acquired messages sent by the HAL (specifically for HIDL HAL).
*/
public void onAcquired(int acquiredInfo, int vendorCode) {
- handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
- null);
+ handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode));
}
/**
@@ -288,7 +332,7 @@
lockoutMode = LockoutTracker.LOCKOUT_TIMED;
}
- mLockoutCache.setLockoutModeForUser(mUserId, lockoutMode);
+ mLockoutTracker.setLockoutModeForUser(mUserId, lockoutMode);
if (duration == 0) {
mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
@@ -296,6 +340,20 @@
});
}
+ /**
+ * Handle clients which are not supported in HIDL HAL. For face, FaceInvalidationClient
+ * is the only AIDL client which is not supported in HIDL.
+ */
+ public void onUnsupportedClientScheduled() {
+ Slog.e(TAG, "FaceInvalidationClient is not supported in the HAL.");
+ handleResponse(FaceInvalidationClient.class, BaseClientMonitor::cancel);
+ }
+
+ private <T> void handleResponse(@NonNull Class<T> className,
+ @NonNull Consumer<T> action) {
+ handleResponse(className, action, null /* alternateAction */);
+ }
+
private <T> void handleResponse(@NonNull Class<T> className,
@NonNull Consumer<T> actionIfClassMatchesClient,
@Nullable Consumer<BaseClientMonitor> alternateAction) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
index e70e25a..af46f44 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
@@ -21,7 +21,7 @@
import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
import java.util.function.Supplier;
@@ -47,7 +47,7 @@
public AidlSession(Context context, Supplier<IBiometricsFace> session, int userId,
AidlResponseHandler aidlResponseHandler) {
- mSession = new AidlToHidlAdapter(context, session, userId, aidlResponseHandler);
+ mSession = new HidlToAidlSessionAdapter(context, session, userId, aidlResponseHandler);
mHalInterfaceVersion = 0;
mUserId = userId;
mAidlResponseHandler = aidlResponseHandler;
@@ -64,7 +64,7 @@
}
/** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
- AidlResponseHandler getHalSessionCallback() {
+ public AidlResponseHandler getHalSessionCallback() {
return mAidlResponseHandler;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index c15049b..c41b706 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -34,7 +34,7 @@
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
-import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
import java.util.HashMap;
import java.util.Map;
@@ -75,8 +75,8 @@
protected void startHalOperation() {
try {
ISession session = getFreshDaemon().getSession();
- if (session instanceof AidlToHidlAdapter) {
- ((AidlToHidlAdapter) session).setFeature(mFeature);
+ if (session instanceof HidlToAidlSessionAdapter) {
+ ((HidlToAidlSessionAdapter) session).setFeature(mFeature);
}
session.getFeatures();
} catch (RemoteException e) {
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 dd9c6d5..9fa15b8 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
@@ -24,6 +24,7 @@
import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
@@ -51,6 +52,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -64,11 +66,13 @@
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.InvalidationRequesterClient;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.SensorList;
import com.android.server.biometrics.sensors.face.FaceUtils;
import com.android.server.biometrics.sensors.face.ServiceProvider;
import com.android.server.biometrics.sensors.face.UsageStats;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSensorAdapter;
import org.json.JSONArray;
import org.json.JSONException;
@@ -152,9 +156,11 @@
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull BiometricContext biometricContext) {
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresChallenge) {
this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
- biometricContext, null /* daemon */);
+ biometricContext, null /* daemon */, resetLockoutRequiresChallenge,
+ false /* testHalEnabled */);
}
@VisibleForTesting FaceProvider(@NonNull Context context,
@@ -163,7 +169,8 @@
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull BiometricContext biometricContext,
- IFace daemon) {
+ @Nullable IFace daemon, boolean resetLockoutRequiresChallenge,
+ boolean testHalEnabled) {
mContext = context;
mBiometricStateCallback = biometricStateCallback;
mHalInstanceName = halInstanceName;
@@ -176,48 +183,118 @@
mBiometricContext = biometricContext;
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
+ mTestHalEnabled = testHalEnabled;
- AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
- new AuthenticationStatsBroadcastReceiver(
- mContext,
- BiometricsProtoEnums.MODALITY_FACE,
- (AuthenticationStatsCollector collector) -> {
- Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
- mAuthenticationStatsCollector = collector;
- });
+ initAuthenticationBroadcastReceiver();
+ initSensors(resetLockoutRequiresChallenge, props);
+ }
- for (SensorProps prop : props) {
- final int sensorId = prop.commonProps.sensorId;
+ private void initAuthenticationBroadcastReceiver() {
+ new AuthenticationStatsBroadcastReceiver(
+ mContext,
+ BiometricsProtoEnums.MODALITY_FACE,
+ (AuthenticationStatsCollector collector) -> {
+ Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
+ mAuthenticationStatsCollector = collector;
+ });
+ }
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- if (prop.commonProps.componentInfo != null) {
- for (ComponentInfo info : prop.commonProps.componentInfo) {
- componentInfo.add(new ComponentInfoInternal(info.componentId,
- info.hardwareVersion, info.firmwareVersion, info.serialNumber,
- info.softwareVersion));
+ private void initSensors(boolean resetLockoutRequiresChallenge, SensorProps[] props) {
+ if (Flags.deHidl()) {
+ if (resetLockoutRequiresChallenge) {
+ Slog.d(getTag(), "Adding HIDL configs");
+ for (SensorProps prop : props) {
+ addHidlSensors(prop, resetLockoutRequiresChallenge);
+ }
+ } else {
+ Slog.d(getTag(), "Adding AIDL configs");
+ for (SensorProps prop : props) {
+ addAidlSensors(prop, resetLockoutRequiresChallenge);
}
}
+ } else {
+ for (SensorProps prop : props) {
+ final int sensorId = prop.commonProps.sensorId;
- final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
- prop.commonProps.sensorId, prop.commonProps.sensorStrength,
- prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
- prop.supportsDetectInteraction, prop.halControlsPreview,
- false /* resetLockoutRequiresChallenge */);
- final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
- internalProp, lockoutResetDispatcher, mBiometricContext);
- final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
- sensor.getLazySession().get().getUserId();
- mFaceSensors.addSensor(sensorId, sensor, userId,
- new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
- }
- });
- Slog.d(getTag(), "Added: " + internalProp);
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ if (prop.commonProps.componentInfo != null) {
+ for (ComponentInfo info : prop.commonProps.componentInfo) {
+ componentInfo.add(new ComponentInfoInternal(info.componentId,
+ info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+ info.softwareVersion));
+ }
+ }
+
+ final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+ prop.commonProps.sensorId, prop.commonProps.sensorStrength,
+ prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
+ prop.supportsDetectInteraction, prop.halControlsPreview,
+ false /* resetLockoutRequiresChallenge */);
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this,
+ mContext, mHandler, internalProp, mLockoutResetDispatcher,
+ mBiometricContext);
+ sensor.init(mLockoutResetDispatcher, this);
+ final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFaceSensors.addSensor(sensorId, sensor, userId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + internalProp);
+ }
}
}
+ private void addHidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) {
+ final int sensorId = prop.commonProps.sensorId;
+ final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/" + sensorId, this,
+ mContext, mHandler, prop, mLockoutResetDispatcher,
+ mBiometricContext, resetLockoutRequiresChallenge,
+ () -> {
+ //TODO: update to make this testable
+ scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
+ null /* callback */);
+ scheduleGetFeature(sensorId, new Binder(), ActivityManager.getCurrentUser(),
+ BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, null,
+ mContext.getOpPackageName());
+ });
+ sensor.init(mLockoutResetDispatcher, this);
+ final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFaceSensors.addSensor(sensorId, sensor, userId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ scheduleGetFeature(sensorId, new Binder(), newUserId,
+ BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION,
+ null, mContext.getOpPackageName());
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFaceSensors.get(sensorId));
+ }
+
+ private void addAidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) {
+ final int sensorId = prop.commonProps.sensorId;
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext,
+ mHandler, prop, mLockoutResetDispatcher, mBiometricContext,
+ resetLockoutRequiresChallenge);
+ sensor.init(mLockoutResetDispatcher, this);
+ final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFaceSensors.addSensor(sensorId, sensor, userId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFaceSensors.get(sensorId));
+ }
+
private String getTag() {
return "FaceProvider/" + mHalInstanceName;
}
@@ -290,7 +367,10 @@
}
}
- private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
+ /**
+ * Schedules FaceGetAuthenticatorIdClient for specific sensor and user.
+ */
+ protected void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
mHandler.post(() -> {
final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
@@ -365,8 +445,12 @@
@Override
public int getLockoutModeForUser(int sensorId, int userId) {
- return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
- Utils.getCurrentStrength(sensorId));
+ if (Flags.deHidl()) {
+ return mFaceSensors.get(sensorId).getLockoutModeForUser(userId);
+ } else {
+ return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+ Utils.getCurrentStrength(sensorId));
+ }
}
@Override
@@ -376,13 +460,18 @@
@Override
public boolean isHardwareDetected(int sensorId) {
- return hasHalInstance();
+ if (Flags.deHidl()) {
+ return mFaceSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
+ } else {
+ return hasHalInstance();
+ }
}
@Override
public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFaceServiceReceiver receiver, String opPackageName) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
@@ -416,6 +505,7 @@
@Nullable Surface previewSurface, boolean debugConsent) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final int maxTemplatesPerUser = mFaceSensors.get(
sensorId).getSensorProperties().maxEnrollmentsPerUser;
final FaceEnrollClient client = new FaceEnrollClient(mContext,
@@ -427,18 +517,23 @@
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext, maxTemplatesPerUser, debugConsent);
- scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
- mBiometricStateCallback, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- ClientMonitorCallback.super.onClientFinished(clientMonitor, success);
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
+ if (Flags.deHidl()) {
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
+ } else {
+ scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
+ mBiometricStateCallback, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ ClientMonitorCallback.super.onClientFinished(clientMonitor,
+ success);
+ if (success) {
+ scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+ scheduleInvalidationRequest(sensorId, userId);
+ }
}
- }
- }));
+ }));
+ }
});
return id;
}
@@ -486,6 +581,13 @@
final int userId = options.getUserId();
final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
+ final LockoutTracker lockoutTracker;
+ if (Flags.deHidl()) {
+ lockoutTracker = mFaceSensors.get(sensorId).getLockoutTracker(true /* forAuth */);
+ } else {
+ lockoutTracker = null;
+ }
final FaceAuthenticationClient client = new FaceAuthenticationClient(
mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
@@ -493,7 +595,7 @@
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
- mUsageStats, null /* lockoutTracker */,
+ mUsageStats, lockoutTracker,
allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
@@ -555,6 +657,7 @@
private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token, int[] faceIds,
int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final FaceRemovalClient client = new FaceRemovalClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), faceIds, userId,
@@ -571,6 +674,7 @@
@Override
public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final FaceResetLockoutClient client = new FaceResetLockoutClient(
mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
@@ -578,8 +682,8 @@
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken,
- mFaceSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
- Utils.getCurrentStrength(sensorId));
+ mFaceSensors.get(sensorId).getLockoutTracker(false/* forAuth */),
+ mLockoutResetDispatcher, Utils.getCurrentStrength(sensorId));
scheduleForSensor(sensorId, client);
});
@@ -590,6 +694,7 @@
boolean enabled, @NonNull byte[] hardwareAuthToken,
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final List<Face> faces = FaceUtils.getInstance(sensorId)
.getBiometricsForUser(mContext, userId);
if (faces.isEmpty()) {
@@ -610,6 +715,7 @@
public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final List<Face> faces = FaceUtils.getInstance(sensorId)
.getBiometricsForUser(mContext, userId);
if (faces.isEmpty()) {
@@ -641,6 +747,7 @@
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final FaceInternalCleanupClient client =
new FaceInternalCleanupClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), userId,
@@ -760,4 +867,8 @@
}
biometricScheduler.startWatchdog();
}
+
+ public boolean getTestHalEnabled() {
+ return mTestHalEnabled;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index 77b5592..d02eefa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.RemoteException;
import android.util.Slog;
@@ -34,6 +35,7 @@
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
import java.util.function.Supplier;
@@ -47,7 +49,7 @@
private static final String TAG = "FaceResetLockoutClient";
private final HardwareAuthToken mHardwareAuthToken;
- private final LockoutTracker mLockoutCache;
+ private final LockoutTracker mLockoutTracker;
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final int mBiometricStrength;
@@ -60,7 +62,7 @@
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
0 /* cookie */, sensorId, logger, biometricContext);
mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
- mLockoutCache = lockoutTracker;
+ mLockoutTracker = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mBiometricStrength = biometricStrength;
}
@@ -79,7 +81,11 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().getSession().resetLockout(mHardwareAuthToken);
+ final ISession session = getFreshDaemon().getSession();
+ session.resetLockout(mHardwareAuthToken);
+ if (session instanceof HidlToAidlSessionAdapter) {
+ mCallback.onClientFinished(this, true /* success */);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Unable to reset lockout", e);
mCallback.onClientFinished(this, false /* success */);
@@ -87,7 +93,7 @@
}
void onLockoutCleared() {
- resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache,
+ resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutTracker,
mLockoutResetDispatcher, getBiometricContext().getAuthSessionCoordinator(),
mBiometricStrength, getRequestId());
mCallback.onClientFinished(this, true /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 54e66eb..3e5c599 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -21,16 +21,20 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.common.ComponentInfo;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.SensorProps;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
@@ -38,6 +42,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -49,12 +54,15 @@
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.face.FaceUtils;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
@@ -71,25 +79,53 @@
@NonNull private final IBinder mToken;
@NonNull private final Handler mHandler;
@NonNull private final FaceSensorPropertiesInternal mSensorProperties;
- @NonNull private final UserAwareBiometricScheduler mScheduler;
- @NonNull private final LockoutCache mLockoutCache;
+ @NonNull private BiometricScheduler mScheduler;
+ @Nullable private LockoutTracker mLockoutTracker;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
- @NonNull private final Supplier<AidlSession> mLazySession;
+ @NonNull private Supplier<AidlSession> mLazySession;
@Nullable AidlSession mCurrentSession;
+ @NonNull BiometricContext mBiometricContext;
Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull BiometricContext biometricContext, AidlSession session) {
+ @NonNull BiometricContext biometricContext, @Nullable AidlSession session) {
mTag = tag;
mProvider = provider;
mContext = context;
mToken = new Binder();
mHandler = handler;
mSensorProperties = sensorProperties;
- mScheduler = new UserAwareBiometricScheduler(tag,
+ mBiometricContext = biometricContext;
+ mAuthenticatorIds = new HashMap<>();
+ }
+
+ Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext) {
+ this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
+ biometricContext, null);
+ }
+
+ public Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresChallenge) {
+ this(tag, provider, context, handler,
+ getFaceSensorPropertiesInternal(prop, resetLockoutRequiresChallenge),
+ lockoutResetDispatcher, biometricContext, null);
+ }
+
+ /**
+ * Initialize biometric scheduler, lockout tracker and session for the sensor.
+ */
+ public void init(LockoutResetDispatcher lockoutResetDispatcher,
+ FaceProvider provider) {
+ mScheduler = new UserAwareBiometricScheduler(mTag,
BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
() -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
new UserAwareBiometricScheduler.UserSwitchCallback() {
@@ -98,7 +134,7 @@
public StopUserClient<?> getStopUserClient(int userId) {
return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), biometricContext,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
() -> mCurrentSession = null);
}
@@ -107,13 +143,36 @@
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
final int sensorId = mSensorProperties.sensorId;
- final AidlResponseHandler resultController = new AidlResponseHandler(
- mContext, mScheduler, sensorId, newUserId,
- mLockoutCache, lockoutResetDispatcher,
- biometricContext.getAuthSessionCoordinator(), () -> {
- Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
- mCurrentSession = null;
- });
+ final AidlResponseHandler resultController;
+ if (Flags.deHidl()) {
+ resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutTracker, lockoutResetDispatcher,
+ mBiometricContext.getAuthSessionCoordinator(), () -> {},
+ new AidlResponseHandler.AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {
+ mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+ newUserId);
+ mProvider.scheduleInvalidationRequest(sensorId,
+ newUserId);
+ }
+
+ @Override
+ public void onHardwareUnavailable() {
+ Slog.e(mTag, "Face sensor hardware unavailable.");
+ mCurrentSession = null;
+ }
+ });
+ } else {
+ resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutTracker, lockoutResetDispatcher,
+ mBiometricContext.getAuthSessionCoordinator(), () -> {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
+ });
+ }
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
(userIdStarted, newSession, halInterfaceVersion) -> {
@@ -136,32 +195,42 @@
return new FaceStartUserClient(mContext, provider::getHalInstance,
mToken, newUserId, mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), biometricContext,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
resultController, userStartedCallback);
}
});
- mLockoutCache = new LockoutCache();
- mAuthenticatorIds = new HashMap<>();
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+ mLockoutTracker = new LockoutCache();
}
- Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
- @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull BiometricContext biometricContext) {
- this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
- biometricContext, null);
+ private static FaceSensorPropertiesInternal getFaceSensorPropertiesInternal(SensorProps prop,
+ boolean resetLockoutRequiresChallenge) {
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ if (prop.commonProps.componentInfo != null) {
+ for (ComponentInfo info : prop.commonProps.componentInfo) {
+ componentInfo.add(new ComponentInfoInternal(info.componentId,
+ info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+ info.softwareVersion));
+ }
+ }
+ final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+ prop.commonProps.sensorId, prop.commonProps.sensorStrength,
+ prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
+ prop.supportsDetectInteraction, prop.halControlsPreview,
+ resetLockoutRequiresChallenge);
+
+ return internalProp;
}
- @NonNull Supplier<AidlSession> getLazySession() {
+ @NonNull public Supplier<AidlSession> getLazySession() {
return mLazySession;
}
- @NonNull FaceSensorPropertiesInternal getSensorProperties() {
+ @NonNull protected FaceSensorPropertiesInternal getSensorProperties() {
return mSensorProperties;
}
- @VisibleForTesting @Nullable AidlSession getSessionForUser(int userId) {
+ @VisibleForTesting @Nullable protected AidlSession getSessionForUser(int userId) {
if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
@@ -174,15 +243,18 @@
mProvider, this);
}
- @NonNull BiometricScheduler getScheduler() {
+ @NonNull public BiometricScheduler getScheduler() {
return mScheduler;
}
- @NonNull LockoutCache getLockoutCache() {
- return mLockoutCache;
+ @NonNull protected LockoutTracker getLockoutTracker(boolean forAuth) {
+ if (forAuth) {
+ return null;
+ }
+ return mLockoutTracker;
}
- @NonNull Map<Integer, Long> getAuthenticatorIds() {
+ @NonNull protected Map<Integer, Long> getAuthenticatorIds() {
return mAuthenticatorIds;
}
@@ -253,4 +325,49 @@
mScheduler.reset();
mCurrentSession = null;
}
+
+ protected BiometricContext getBiometricContext() {
+ return mBiometricContext;
+ }
+
+ protected Handler getHandler() {
+ return mHandler;
+ }
+
+ protected Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Schedules FaceUpdateActiveUserClient for user id.
+ */
+ public void scheduleFaceUpdateActiveUserClient(int userId) {}
+
+ /**
+ * Returns true if the sensor hardware is detected.
+ */
+ public boolean isHardwareDetected(String halInstanceName) {
+ if (mTestHalEnabled) {
+ return true;
+ }
+ return ServiceManager.checkService(IFace.DESCRIPTOR + "/" + halInstanceName) != null;
+ }
+
+ /**
+ * Returns lockout mode of this sensor.
+ */
+ @LockoutTracker.LockoutMode
+ public int getLockoutModeForUser(int userId) {
+ return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+ Utils.getCurrentStrength(mSensorProperties.sensorId));
+ }
+
+ public void setScheduler(BiometricScheduler scheduler) {
+ mScheduler = scheduler;
+ }
+
+ public void setLazySession(
+ Supplier<AidlSession> lazySession) {
+ mLazySession = lazySession;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
index 8385c3f..0c34d70 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
@@ -27,13 +27,14 @@
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.HalClientMonitor;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.face.aidl.AidlSession;
import java.io.File;
import java.util.Map;
import java.util.function.Supplier;
-public class FaceUpdateActiveUserClient extends HalClientMonitor<IBiometricsFace> {
+public class FaceUpdateActiveUserClient extends StartUserClient<IBiometricsFace, AidlSession> {
private static final String TAG = "FaceUpdateActiveUserClient";
private static final String FACE_DATA_DIR = "facedata";
@@ -45,8 +46,18 @@
int sensorId, @NonNull BiometricLogger logger,
@NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
@NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, logger, biometricContext);
+ this(context, lazyDaemon, (newUserId, newUser, halInterfaceVersion) -> {},
+ userId, owner, sensorId, logger, biometricContext, hasEnrolledBiometrics,
+ authenticatorIds);
+ }
+
+ FaceUpdateActiveUserClient(@NonNull Context context,
+ @NonNull Supplier<IBiometricsFace> lazyDaemon, UserStartedCallback userStartedCallback,
+ int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
+ @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, null /* token */, userId, sensorId, logger, biometricContext,
+ userStartedCallback);
mHasEnrolledBiometrics = hasEnrolledBiometrics;
mAuthenticatorIds = authenticatorIds;
}
@@ -77,6 +88,7 @@
daemon.setActiveUser(getTargetUserId(), storePath.getAbsolutePath());
mAuthenticatorIds.put(getTargetUserId(),
mHasEnrolledBiometrics ? daemon.getAuthenticatorId().value : 0L);
+ mUserStartedCallback.onUserStarted(getTargetUserId(), null, 0);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to setActiveUser: " + e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
index 36a9790..7a574ce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
@@ -112,4 +112,8 @@
void onAuthenticatorIdRetrieved(long authenticatorId) {
mAidlResponseHandler.onAuthenticatorIdRetrieved(authenticatorId);
}
+
+ void onUnsupportedClientScheduled() {
+ mAidlResponseHandler.onUnsupportedClientScheduled();
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
new file mode 100644
index 0000000..6355cb5
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2023 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.biometrics.sensors.face.hidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.face.SensorProps;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.os.Handler;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.face.FaceUtils;
+import com.android.server.biometrics.sensors.face.LockoutHalImpl;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.face.aidl.AidlSession;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+import com.android.server.biometrics.sensors.face.aidl.Sensor;
+
+/**
+ * Convert HIDL sensor configurations to an AIDL Sensor.
+ */
+public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRecipient{
+
+ private static final String TAG = "HidlToAidlSensorAdapter";
+
+ private IBiometricsFace mDaemon;
+ private AidlSession mSession;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+ private final Runnable mInternalCleanupAndGetFeatureRunnable;
+ private final FaceProvider mFaceProvider;
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
+ private final AuthSessionCoordinator mAuthSessionCoordinator;
+ private final AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ private final StartUserClient.UserStartedCallback<AidlSession> mUserStartedCallback =
+ (newUserId, newUser, halInterfaceVersion) -> {
+ if (newUserId != mCurrentUserId) {
+ handleUserChanged(newUserId);
+ }
+ };
+ private LockoutHalImpl mLockoutTracker;
+
+ public HidlToAidlSensorAdapter(@NonNull String tag,
+ @NonNull FaceProvider provider,
+ @NonNull Context context,
+ @NonNull Handler handler,
+ @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresChallenge,
+ @NonNull Runnable internalCleanupAndGetFeatureRunnable) {
+ this(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
+ resetLockoutRequiresChallenge, internalCleanupAndGetFeatureRunnable,
+ new AuthSessionCoordinator(), null /* daemon */,
+ null /* onEnrollSuccessCallback */);
+ }
+
+ @VisibleForTesting
+ HidlToAidlSensorAdapter(@NonNull String tag,
+ @NonNull FaceProvider provider,
+ @NonNull Context context,
+ @NonNull Handler handler,
+ @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresChallenge,
+ @NonNull Runnable internalCleanupAndGetFeatureRunnable,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @Nullable IBiometricsFace daemon,
+ @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+ super(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
+ resetLockoutRequiresChallenge);
+ mInternalCleanupAndGetFeatureRunnable = internalCleanupAndGetFeatureRunnable;
+ mFaceProvider = provider;
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+ mAuthSessionCoordinator = authSessionCoordinator;
+ mDaemon = daemon;
+ mAidlResponseHandlerCallback = aidlResponseHandlerCallback == null
+ ? new AidlResponseHandler.AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {
+ scheduleFaceUpdateActiveUserClient(mCurrentUserId);
+ }
+
+ @Override
+ public void onHardwareUnavailable() {
+ mDaemon = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }
+ } : aidlResponseHandlerCallback;
+ }
+
+ @Override
+ public void scheduleFaceUpdateActiveUserClient(int userId) {
+ getScheduler().scheduleClientMonitor(getFaceUpdateActiveUserClient(userId));
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ Slog.d(TAG, "HAL died.");
+ mDaemon = null;
+ }
+
+ @Override
+ public boolean isHardwareDetected(String halInstanceName) {
+ return getIBiometricsFace() != null;
+ }
+
+ @Override
+ @LockoutTracker.LockoutMode
+ public int getLockoutModeForUser(int userId) {
+ return mLockoutTracker.getLockoutModeForUser(userId);
+ }
+
+ @Override
+ public void init(LockoutResetDispatcher lockoutResetDispatcher,
+ FaceProvider provider) {
+ setScheduler(new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
+ null /* gestureAvailabilityTracker */));
+ setLazySession(this::getSession);
+ mLockoutTracker = new LockoutHalImpl();
+ }
+
+ @Override
+ @VisibleForTesting @Nullable protected AidlSession getSessionForUser(int userId) {
+ if (mSession != null && mSession.getUserId() == userId) {
+ return mSession;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected LockoutTracker getLockoutTracker(boolean forAuth) {
+ return mLockoutTracker;
+ }
+
+ @NonNull AidlSession getSession() {
+ if (mDaemon != null && mSession != null) {
+ return mSession;
+ } else {
+ return mSession = new AidlSession(getContext(), this::getIBiometricsFace,
+ mCurrentUserId, getAidlResponseHandler());
+ }
+ }
+
+ private AidlResponseHandler getAidlResponseHandler() {
+ return new AidlResponseHandler(getContext(), getScheduler(), getSensorProperties().sensorId,
+ mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher,
+ mAuthSessionCoordinator, () -> {}, mAidlResponseHandlerCallback);
+ }
+
+ private IBiometricsFace getIBiometricsFace() {
+ if (mFaceProvider.getTestHalEnabled()) {
+ final TestHal testHal = new TestHal(getContext(), getSensorProperties().sensorId);
+ testHal.setCallback(new HidlToAidlCallbackConverter(getAidlResponseHandler()));
+ return testHal;
+ }
+
+ if (mDaemon != null) {
+ return mDaemon;
+ }
+
+ Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
+ + getScheduler().getCurrentClient());
+
+ try {
+ mDaemon = IBiometricsFace.getService();
+ } catch (java.util.NoSuchElementException e) {
+ // Service doesn't exist or cannot be opened.
+ Slog.w(TAG, "NoSuchElementException", e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get face HAL", e);
+ }
+
+ if (mDaemon == null) {
+ Slog.w(TAG, "Face HAL not available");
+ return null;
+ }
+
+ mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
+
+ scheduleLoadAuthenticatorIds();
+ mInternalCleanupAndGetFeatureRunnable.run();
+ return mDaemon;
+ }
+
+ @VisibleForTesting void handleUserChanged(int newUserId) {
+ Slog.d(TAG, "User changed. Current user is " + newUserId);
+ mSession = null;
+ mCurrentUserId = newUserId;
+ }
+
+ private void scheduleLoadAuthenticatorIds() {
+ // Note that this can be performed on the scheduler (as opposed to being done immediately
+ // when the HAL is (re)loaded, since
+ // 1) If this is truly the first time it's being performed (e.g. system has just started),
+ // this will be run very early and way before any applications need to generate keys.
+ // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
+ // just been reloaded), the framework already has a cache of the authenticatorIds. This
+ // is safe because authenticatorIds only change when A) new template has been enrolled,
+ // or B) all templates are removed.
+ getHandler().post(() -> {
+ for (UserInfo user : UserManager.get(getContext()).getAliveUsers()) {
+ final int targetUserId = user.id;
+ if (!getAuthenticatorIds().containsKey(targetUserId)) {
+ scheduleFaceUpdateActiveUserClient(targetUserId);
+ }
+ }
+ });
+ }
+
+ private FaceUpdateActiveUserClient getFaceUpdateActiveUserClient(int userId) {
+ return new FaceUpdateActiveUserClient(getContext(), this::getIBiometricsFace,
+ mUserStartedCallback, userId, TAG, getSensorProperties().sensorId,
+ BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
+ !FaceUtils.getInstance(getSensorProperties().sensorId).getBiometricsForUser(
+ getContext(), userId).isEmpty(),
+ getAuthenticatorIds());
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
similarity index 88%
rename from services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
rename to services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
index 489b213..5daf2d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
@@ -47,34 +47,37 @@
import java.util.function.Supplier;
/**
- * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ * Adapter to convert HIDL methods into AIDL interface {@link ISession}.
*/
-public class AidlToHidlAdapter implements ISession {
+public class HidlToAidlSessionAdapter implements ISession {
- private final String TAG = "AidlToHidlAdapter";
+ private static final String TAG = "HidlToAidlSessionAdapter";
+
private static final int CHALLENGE_TIMEOUT_SEC = 600;
@DurationMillisLong
private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
@DurationMillisLong
private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS = CHALLENGE_TIMEOUT_SEC * 1000;
private static final int INVALID_VALUE = -1;
+ @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75;
+
private final Clock mClock;
private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
- @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75;
+ private final int mUserId;
+ private final Context mContext;
+
private long mGenerateChallengeCreatedAt = INVALID_VALUE;
private long mGenerateChallengeResult = INVALID_VALUE;
@NonNull private Supplier<IBiometricsFace> mSession;
- private final int mUserId;
private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
- private final Context mContext;
private int mFeature = INVALID_VALUE;
- public AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session,
+ public HidlToAidlSessionAdapter(Context context, Supplier<IBiometricsFace> session,
int userId, AidlResponseHandler aidlResponseHandler) {
this(context, session, userId, aidlResponseHandler, Clock.systemUTC());
}
- AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, int userId,
+ HidlToAidlSessionAdapter(Context context, Supplier<IBiometricsFace> session, int userId,
AidlResponseHandler aidlResponseHandler, Clock clock) {
mSession = session;
mUserId = userId;
@@ -83,42 +86,11 @@
setCallback(aidlResponseHandler);
}
- private void setCallback(AidlResponseHandler aidlResponseHandler) {
- mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
- try {
- mSession.get().setCallback(mHidlToAidlCallbackConverter);
- } catch (RemoteException e) {
- Slog.d(TAG, "Failed to set callback");
- }
- }
-
@Override
public IBinder asBinder() {
return null;
}
- private boolean isGeneratedChallengeCacheValid() {
- return mGenerateChallengeCreatedAt != INVALID_VALUE
- && mGenerateChallengeResult != INVALID_VALUE
- && mClock.millis() - mGenerateChallengeCreatedAt
- < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
- }
-
- private void incrementChallengeCount() {
- mGeneratedChallengeCount.add(0, mClock.millis());
- }
-
- private int decrementChallengeCount() {
- final long now = mClock.millis();
- // ignore values that are old in case generate/revoke calls are not matched
- // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
- mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
- if (!mGeneratedChallengeCount.isEmpty()) {
- mGeneratedChallengeCount.remove(0);
- }
- return mGeneratedChallengeCount.size();
- }
-
@Override
public void generateChallenge() throws RemoteException {
incrementChallengeCount();
@@ -150,7 +122,7 @@
@Override
public EnrollmentStageConfig[] getEnrollmentConfig(byte enrollmentType) throws RemoteException {
- //unsupported in HIDL
+ Slog.e(TAG, "getEnrollmentConfig unsupported in HIDL");
return null;
}
@@ -244,19 +216,6 @@
}
}
- private int getFaceId() {
- FaceManager faceManager = mContext.getSystemService(FaceManager.class);
- List<Face> faces = faceManager.getEnrolledFaces(mUserId);
- if (faces.isEmpty()) {
- Slog.d(TAG, "No faces to get feature from.");
- mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
- BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */);
- return INVALID_VALUE;
- }
-
- return faces.get(0).getBiometricId();
- }
-
@Override
public void getAuthenticatorId() throws RemoteException {
long authenticatorId = mSession.get().getAuthenticatorId().value;
@@ -265,7 +224,8 @@
@Override
public void invalidateAuthenticatorId() throws RemoteException {
- //unsupported in HIDL
+ Slog.e(TAG, "invalidateAuthenticatorId unsupported in HIDL");
+ mHidlToAidlCallbackConverter.onUnsupportedClientScheduled();
}
@Override
@@ -279,47 +239,105 @@
@Override
public void close() throws RemoteException {
- //Unsupported in HIDL
+ Slog.e(TAG, "close unsupported in HIDL");
}
@Override
public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Slog.e(TAG, "authenticateWithContext unsupported in HIDL");
+ return authenticate(operationId);
}
@Override
public ICancellationSignal enrollWithContext(HardwareAuthToken hat, byte type, byte[] features,
NativeHandle previewSurface, OperationContext context) throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Slog.e(TAG, "enrollWithContext unsupported in HIDL");
+ return enroll(hat, type, features, previewSurface);
}
@Override
public ICancellationSignal detectInteractionWithContext(OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Slog.e(TAG, "detectInteractionWithContext unsupported in HIDL");
+ return detectInteraction();
}
@Override
public void onContextChanged(OperationContext context) throws RemoteException {
- //Unsupported in HIDL
+ Slog.e(TAG, "onContextChanged unsupported in HIDL");
}
@Override
public int getInterfaceVersion() throws RemoteException {
- //Unsupported in HIDL
+ Slog.e(TAG, "getInterfaceVersion unsupported in HIDL");
return 0;
}
@Override
public String getInterfaceHash() throws RemoteException {
+ Slog.e(TAG, "getInterfaceHash unsupported in HIDL");
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal enrollWithOptions(FaceEnrollOptions options) {
//Unsupported in HIDL
return null;
}
+ private boolean isGeneratedChallengeCacheValid() {
+ return mGenerateChallengeCreatedAt != INVALID_VALUE
+ && mGenerateChallengeResult != INVALID_VALUE
+ && mClock.millis() - mGenerateChallengeCreatedAt
+ < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
+ }
+
+ private void incrementChallengeCount() {
+ mGeneratedChallengeCount.add(0, mClock.millis());
+ }
+
+ private int decrementChallengeCount() {
+ final long now = mClock.millis();
+ // ignore values that are old in case generate/revoke calls are not matched
+ // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
+ mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
+ if (!mGeneratedChallengeCount.isEmpty()) {
+ mGeneratedChallengeCount.remove(0);
+ }
+ return mGeneratedChallengeCount.size();
+ }
+
+ private void setCallback(AidlResponseHandler aidlResponseHandler) {
+ mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+ try {
+ if (mSession.get() != null) {
+ long halId = mSession.get().setCallback(mHidlToAidlCallbackConverter).value;
+ Slog.d(TAG, "Face HAL ready, HAL ID: " + halId);
+ if (halId == 0) {
+ Slog.d(TAG, "Unable to set HIDL callback.");
+ }
+ } else {
+ Slog.e(TAG, "Unable to set HIDL callback. HIDL daemon is null.");
+ }
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to set callback");
+ }
+ }
+
+ private int getFaceId() {
+ FaceManager faceManager = mContext.getSystemService(FaceManager.class);
+ List<Face> faces = faceManager.getEnrolledFaces(mUserId);
+ if (faces.isEmpty()) {
+ Slog.d(TAG, "No faces to get feature from.");
+ mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+ BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */);
+ return INVALID_VALUE;
+ }
+
+ return faces.get(0).getBiometricId();
+ }
+
/**
* Cancellation in HIDL occurs for the entire session, instead of a specific client.
*/
@@ -345,10 +363,4 @@
return null;
}
}
-
- @Override
- public ICancellationSignal enrollWithOptions(FaceEnrollOptions options) {
- //Unsupported in HIDL
- return null;
- }
}
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 83b306b..e01d672 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
@@ -45,9 +45,11 @@
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintServiceReceiver;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
@@ -80,6 +82,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
@@ -127,6 +130,8 @@
@NonNull
private final Function<String, FingerprintProvider> mFingerprintProvider;
@NonNull
+ private final FingerprintProviderFunction mFingerprintProviderFunction;
+ @NonNull
private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal>
mBiometricStateCallback;
@NonNull
@@ -136,6 +141,11 @@
@NonNull
private final FingerprintServiceRegistry mRegistry;
+ interface FingerprintProviderFunction {
+ FingerprintProvider getFingerprintProvider(Pair<String, SensorProps[]> filteredSensorProp,
+ boolean resetLockoutRequiresHardwareAuthToken);
+ }
+
/** Receives the incoming binder calls from FingerprintManager. */
@VisibleForTesting
final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
@@ -874,6 +884,18 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
+ public void registerAuthenticatorsLegacy(
+ @NonNull FingerprintSensorConfigurations fingerprintSensorConfigurations) {
+ super.registerAuthenticatorsLegacy_enforcePermission();
+ if (!fingerprintSensorConfigurations.hasSensorConfigurations()) {
+ Slog.d(TAG, "No fingerprint sensors available.");
+ return;
+ }
+ mRegistry.registerAll(() -> getProviders(fingerprintSensorConfigurations));
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override // Binder call
public void registerAuthenticators(
@NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
super.registerAuthenticators_enforcePermission();
@@ -1021,7 +1043,8 @@
() -> IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
() -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR),
- null /* fingerprintProvider */);
+ null /* fingerprintProvider */,
+ null /* fingerprintProviderFunction */);
}
@VisibleForTesting
@@ -1029,7 +1052,8 @@
BiometricContext biometricContext,
Supplier<IBiometricService> biometricServiceSupplier,
Supplier<String[]> aidlInstanceNameSupplier,
- Function<String, FingerprintProvider> fingerprintProvider) {
+ Function<String, FingerprintProvider> fingerprintProvider,
+ FingerprintProviderFunction fingerprintProviderFunction) {
super(context);
mBiometricContext = biometricContext;
mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
@@ -1049,7 +1073,8 @@
return new FingerprintProvider(getContext(),
mBiometricStateCallback, mAuthenticationStateListeners,
fp.getSensorProps(), name, mLockoutResetDispatcher,
- mGestureAvailabilityDispatcher, mBiometricContext);
+ mGestureAvailabilityDispatcher, mBiometricContext,
+ true /* resetLockoutRequiresHardwareAuthToken */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
}
@@ -1059,6 +1084,22 @@
return null;
};
+ if (Flags.deHidl()) {
+ mFingerprintProviderFunction = fingerprintProviderFunction == null
+ ? (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) ->
+ new FingerprintProvider(
+ getContext(), mBiometricStateCallback,
+ mAuthenticationStateListeners,
+ filteredSensorProps.second,
+ filteredSensorProps.first, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher,
+ mBiometricContext,
+ resetLockoutRequiresHardwareAuthToken)
+ : fingerprintProviderFunction;
+ } else {
+ mFingerprintProviderFunction =
+ (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) -> null;
+ }
mHandler = new Handler(Looper.getMainLooper());
mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1070,6 +1111,44 @@
});
}
+ @NonNull
+ private List<ServiceProvider> getProviders(@NonNull FingerprintSensorConfigurations
+ fingerprintSensorConfigurations) {
+ final List<ServiceProvider> providers = new ArrayList<>();
+ final Pair<String, SensorProps[]> filteredSensorProps = filterAvailableHalInstances(
+ fingerprintSensorConfigurations);
+ providers.add(mFingerprintProviderFunction.getFingerprintProvider(filteredSensorProps,
+ fingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken()));
+
+ return providers;
+ }
+
+ @NonNull
+ private Pair<String, SensorProps[]> filterAvailableHalInstances(
+ FingerprintSensorConfigurations fingerprintSensorConfigurations) {
+ Pair<String, SensorProps[]> finalSensorPair =
+ fingerprintSensorConfigurations.getSensorPair();
+ if (fingerprintSensorConfigurations.isSingleSensorConfigurationPresent()) {
+ return finalSensorPair;
+ }
+
+ final Pair<String, SensorProps[]> virtualSensorPropsPair = fingerprintSensorConfigurations
+ .getSensorPairForInstance("virtual");
+ if (Utils.isVirtualEnabled(getContext())) {
+ if (virtualSensorPropsPair != null) {
+ return virtualSensorPropsPair;
+ } else {
+ Slog.e(TAG, "Could not find virtual interface while it is enabled");
+ return finalSensorPair;
+ }
+ } else {
+ if (virtualSensorPropsPair != null) {
+ return fingerprintSensorConfigurations.getSensorPairNotForInstance("virtual");
+ }
+ }
+ return finalSensorPair;
+ }
+
private Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
filterAvailableHalInstances(
@NonNull List<FingerprintSensorPropertiesInternal> hidlInstances,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
index 4a01943..bd21cf4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
@@ -25,6 +25,7 @@
import android.hardware.keymaster.HardwareAuthToken;
import android.util.Slog;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -34,9 +35,9 @@
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.RemovalConsumer;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
@@ -59,6 +60,21 @@
void onHardwareUnavailable();
}
+ /**
+ * Interface to send results to the AidlResponseHandler's owner.
+ */
+ public interface AidlResponseHandlerCallback {
+ /**
+ * Invoked when enrollment is successful.
+ */
+ void onEnrollSuccess();
+
+ /**
+ * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+ */
+ void onHardwareUnavailable();
+ }
+
private static final String TAG = "AidlResponseHandler";
@NonNull
@@ -68,28 +84,49 @@
private final int mSensorId;
private final int mUserId;
@NonNull
- private final LockoutCache mLockoutCache;
+ private final LockoutTracker mLockoutTracker;
@NonNull
private final LockoutResetDispatcher mLockoutResetDispatcher;
@NonNull
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull
private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+ @NonNull
+ private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
public AidlResponseHandler(@NonNull Context context,
@NonNull BiometricScheduler scheduler, int sensorId, int userId,
- @NonNull LockoutCache lockoutTracker,
+ @NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
@NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+ this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
+ authSessionCoordinator, hardwareUnavailableCallback,
+ new AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {}
+
+ @Override
+ public void onHardwareUnavailable() {}
+ });
+ }
+
+ public AidlResponseHandler(@NonNull Context context,
+ @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull LockoutTracker lockoutTracker,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
+ @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
mContext = context;
mScheduler = scheduler;
mSensorId = sensorId;
mUserId = userId;
- mLockoutCache = lockoutTracker;
+ mLockoutTracker = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mAuthSessionCoordinator = authSessionCoordinator;
mHardwareUnavailableCallback = hardwareUnavailableCallback;
+ mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
}
@Override
@@ -105,27 +142,26 @@
@Override
public void onChallengeGenerated(long challenge) {
handleResponse(FingerprintGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(
- mSensorId, mUserId, challenge), null);
+ mSensorId, mUserId, challenge));
}
@Override
public void onChallengeRevoked(long challenge) {
handleResponse(FingerprintRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(
- challenge), null);
+ challenge));
}
/**
* Handles acquired messages sent by the HAL (specifically for HIDL HAL).
*/
public void onAcquired(int acquiredInfo, int vendorCode) {
- handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
- null);
+ handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode));
}
@Override
public void onAcquired(byte info, int vendorCode) {
handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(
- AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode), null);
+ AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode));
}
/**
@@ -135,9 +171,13 @@
handleResponse(ErrorConsumer.class, (c) -> {
c.onError(error, vendorCode);
if (error == Error.HW_UNAVAILABLE) {
- mHardwareUnavailableCallback.onHardwareUnavailable();
+ if (Flags.deHidl()) {
+ mAidlResponseHandlerCallback.onHardwareUnavailable();
+ } else {
+ mHardwareUnavailableCallback.onHardwareUnavailable();
+ }
}
- }, null);
+ });
}
@Override
@@ -158,8 +198,12 @@
.getUniqueName(mContext, currentUserId);
final Fingerprint fingerprint = new Fingerprint(name, currentUserId,
enrollmentId, mSensorId);
- handleResponse(FingerprintEnrollClient.class, (c) -> c.onEnrollResult(fingerprint,
- remaining), null);
+ handleResponse(FingerprintEnrollClient.class, (c) -> {
+ c.onEnrollResult(fingerprint, remaining);
+ if (remaining == 0) {
+ mAidlResponseHandlerCallback.onEnrollSuccess();
+ }
+ });
}
@Override
@@ -184,13 +228,12 @@
@Override
public void onLockoutTimed(long durationMillis) {
- handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis),
- null);
+ handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis));
}
@Override
public void onLockoutPermanent() {
- handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+ handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent);
}
@Override
@@ -198,7 +241,7 @@
handleResponse(FingerprintResetLockoutClient.class,
FingerprintResetLockoutClient::onLockoutCleared,
(c) -> FingerprintResetLockoutClient.resetLocalLockoutStateToNone(
- mSensorId, mUserId, mLockoutCache, mLockoutResetDispatcher,
+ mSensorId, mUserId, mLockoutTracker, mLockoutResetDispatcher,
mAuthSessionCoordinator, Utils.getCurrentStrength(mSensorId),
-1 /* requestId */));
}
@@ -206,49 +249,74 @@
@Override
public void onInteractionDetected() {
handleResponse(FingerprintDetectClient.class,
- FingerprintDetectClient::onInteractionDetected, null);
+ FingerprintDetectClient::onInteractionDetected);
}
@Override
public void onEnrollmentsEnumerated(int[] enrollmentIds) {
if (enrollmentIds.length > 0) {
for (int i = 0; i < enrollmentIds.length; i++) {
- final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
- int finalI = i;
- handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp,
- enrollmentIds.length - finalI - 1), null);
+ onEnrollmentEnumerated(enrollmentIds[i],
+ enrollmentIds.length - i - 1 /* remaining */);
}
} else {
- handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(null,
- 0), null);
+ handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(
+ null /* identifier */,
+ 0 /* remaining */));
}
}
+ /**
+ * Handle enumerated fingerprint.
+ */
+ public void onEnrollmentEnumerated(int enrollmentId, int remaining) {
+ final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+ handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp, remaining));
+ }
+
+ /**
+ * Handle removal of fingerprint.
+ */
+ public void onEnrollmentRemoved(int enrollmentId, int remaining) {
+ final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+ handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp, remaining));
+ }
+
@Override
public void onEnrollmentsRemoved(int[] enrollmentIds) {
if (enrollmentIds.length > 0) {
for (int i = 0; i < enrollmentIds.length; i++) {
- final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
- int finalI = i;
- handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp,
- enrollmentIds.length - finalI - 1), null);
+ onEnrollmentRemoved(enrollmentIds[i], enrollmentIds.length - i - 1 /* remaining */);
}
} else {
- handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null, 0),
- null);
+ handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */,
+ 0 /* remaining */));
}
}
@Override
public void onAuthenticatorIdRetrieved(long authenticatorId) {
handleResponse(FingerprintGetAuthenticatorIdClient.class,
- (c) -> c.onAuthenticatorIdRetrieved(authenticatorId), null);
+ (c) -> c.onAuthenticatorIdRetrieved(authenticatorId));
}
@Override
public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
handleResponse(FingerprintInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
- newAuthenticatorId), null);
+ newAuthenticatorId));
+ }
+
+ /**
+ * Handle clients which are not supported in HIDL HAL.
+ */
+ public <T extends BaseClientMonitor> void onUnsupportedClientScheduled(Class<T> className) {
+ Slog.e(TAG, className + " is not supported in the HAL.");
+ handleResponse(className, (c) -> c.cancel());
+ }
+
+ private <T> void handleResponse(@NonNull Class<T> className,
+ @NonNull Consumer<T> action) {
+ handleResponse(className, action, null /* alternateAction */);
}
private <T> void handleResponse(@NonNull Class<T> className,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
index 299a310..8ff105b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
@@ -20,7 +20,7 @@
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import com.android.server.biometrics.sensors.fingerprint.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.fingerprint.hidl.HidlToAidlSessionAdapter;
import java.util.function.Supplier;
@@ -45,7 +45,7 @@
public AidlSession(@NonNull Supplier<IBiometricsFingerprint> session,
int userId, AidlResponseHandler aidlResponseHandler) {
- mSession = new AidlToHidlAdapter(session, userId, aidlResponseHandler);
+ mSession = new HidlToAidlSessionAdapter(session, userId, aidlResponseHandler);
mHalInterfaceVersion = 0;
mUserId = userId;
mAidlResponseHandler = aidlResponseHandler;
@@ -62,7 +62,7 @@
}
/** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
- AidlResponseHandler getHalSessionCallback() {
+ public AidlResponseHandler getHalSessionCallback() {
return mAidlResponseHandler;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index ea1a622..0353969 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -30,7 +30,7 @@
import java.util.Map;
import java.util.function.Supplier;
-class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> {
+public class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> {
private static final String TAG = "FingerprintGetAuthenticatorIdClient";
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 032ab87..88a11d9 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.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -73,6 +74,7 @@
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.InvalidationRequesterClient;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.SensorList;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
@@ -80,6 +82,7 @@
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.hidl.HidlToAidlSensorAdapter;
import org.json.JSONArray;
import org.json.JSONException;
@@ -165,10 +168,12 @@
@NonNull SensorProps[] props, @NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- @NonNull BiometricContext biometricContext) {
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresHardwareAuthToken) {
this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext,
- null /* daemon */);
+ null /* daemon */, resetLockoutRequiresHardwareAuthToken,
+ false /* testHalEnabled */);
}
@VisibleForTesting FingerprintProvider(@NonNull Context context,
@@ -178,7 +183,9 @@
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull BiometricContext biometricContext,
- IFingerprint daemon) {
+ @Nullable IFingerprint daemon,
+ boolean resetLockoutRequiresHardwareAuthToken,
+ boolean testHalEnabled) {
mContext = context;
mBiometricStateCallback = biometricStateCallback;
mAuthenticationStateListeners = authenticationStateListeners;
@@ -191,62 +198,136 @@
mBiometricContext = biometricContext;
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
+ mTestHalEnabled = testHalEnabled;
- AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
- new AuthenticationStatsBroadcastReceiver(
- mContext,
- BiometricsProtoEnums.MODALITY_FINGERPRINT,
- (AuthenticationStatsCollector collector) -> {
- Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
- mAuthenticationStatsCollector = collector;
- });
+ initAuthenticationBroadcastReceiver();
+ initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher);
+ }
- final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
+ private void initAuthenticationBroadcastReceiver() {
+ new AuthenticationStatsBroadcastReceiver(
+ mContext,
+ BiometricsProtoEnums.MODALITY_FINGERPRINT,
+ (AuthenticationStatsCollector collector) -> {
+ Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
+ mAuthenticationStatsCollector = collector;
+ });
+ }
- for (SensorProps prop : props) {
- final int sensorId = prop.commonProps.sensorId;
-
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- if (prop.commonProps.componentInfo != null) {
- for (ComponentInfo info : prop.commonProps.componentInfo) {
- componentInfo.add(new ComponentInfoInternal(info.componentId,
- info.hardwareVersion, info.firmwareVersion, info.serialNumber,
- info.softwareVersion));
+ private void initSensors(boolean resetLockoutRequiresHardwareAuthToken, SensorProps[] props,
+ GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ if (Flags.deHidl()) {
+ if (!resetLockoutRequiresHardwareAuthToken) {
+ Slog.d(getTag(), "Adding HIDL configs");
+ for (SensorProps sensorConfig: props) {
+ addHidlSensors(sensorConfig, gestureAvailabilityDispatcher,
+ resetLockoutRequiresHardwareAuthToken);
+ }
+ } else {
+ Slog.d(getTag(), "Adding AIDL configs");
+ final List<SensorLocationInternal> workaroundLocations =
+ getWorkaroundSensorProps(mContext);
+ for (SensorProps prop : props) {
+ addAidlSensors(prop, gestureAvailabilityDispatcher, workaroundLocations,
+ resetLockoutRequiresHardwareAuthToken);
}
}
+ } else {
+ final List<SensorLocationInternal> workaroundLocations =
+ getWorkaroundSensorProps(mContext);
- final FingerprintSensorPropertiesInternal internalProp =
- new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
- prop.commonProps.sensorStrength,
- prop.commonProps.maxEnrollmentsPerUser,
- componentInfo,
- prop.sensorType,
- prop.halControlsIllumination,
- true /* resetLockoutRequiresHardwareAuthToken */,
- !workaroundLocations.isEmpty() ? workaroundLocations :
- Arrays.stream(prop.sensorLocations).map(location ->
- new SensorLocationInternal(
- location.display,
- location.sensorLocationX,
- location.sensorLocationY,
- location.sensorRadius))
- .collect(Collectors.toList()));
- final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
- internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher,
- mBiometricContext);
- final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
- sensor.getLazySession().get().getUserId();
- mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
- new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
- }
- });
- Slog.d(getTag(), "Added: " + internalProp);
+ for (SensorProps prop : props) {
+ final int sensorId = prop.commonProps.sensorId;
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ if (prop.commonProps.componentInfo != null) {
+ for (ComponentInfo info : prop.commonProps.componentInfo) {
+ componentInfo.add(new ComponentInfoInternal(info.componentId,
+ info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+ info.softwareVersion));
+ }
+ }
+ final FingerprintSensorPropertiesInternal internalProp =
+ new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
+ prop.commonProps.sensorStrength,
+ prop.commonProps.maxEnrollmentsPerUser,
+ componentInfo,
+ prop.sensorType,
+ prop.halControlsIllumination,
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ !workaroundLocations.isEmpty() ? workaroundLocations :
+ Arrays.stream(prop.sensorLocations).map(
+ location -> new SensorLocationInternal(
+ location.display,
+ location.sensorLocationX,
+ location.sensorLocationY,
+ location.sensorRadius))
+ .collect(Collectors.toList()));
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext,
+ mHandler, internalProp, mLockoutResetDispatcher,
+ gestureAvailabilityDispatcher, mBiometricContext);
+ sensor.init(gestureAvailabilityDispatcher,
+ mLockoutResetDispatcher);
+ final int sessionUserId =
+ sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+ }
}
}
+ private void addHidlSensors(@NonNull SensorProps prop,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ boolean resetLockoutRequiresHardwareAuthToken) {
+ final int sensorId = prop.commonProps.sensorId;
+ final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/"
+ + sensorId, this, mContext, mHandler,
+ prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher,
+ mBiometricContext, resetLockoutRequiresHardwareAuthToken,
+ () -> scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
+ null /* callback */));
+ sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
+ final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+ }
+
+ private void addAidlSensors(@NonNull SensorProps prop,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ List<SensorLocationInternal> workaroundLocations,
+ boolean resetLockoutRequiresHardwareAuthToken) {
+ final int sensorId = prop.commonProps.sensorId;
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId,
+ this, mContext, mHandler,
+ prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher,
+ mBiometricContext, workaroundLocations,
+ resetLockoutRequiresHardwareAuthToken);
+ sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
+ final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+ }
+
private String getTag() {
return "FingerprintProvider/" + mHalInstanceName;
}
@@ -351,7 +432,10 @@
}
}
- private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
+ /**
+ * Schedules FingerprintGetAuthenticatorIdClient for specific sensor and user.
+ */
+ protected void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
mHandler.post(() -> {
final FingerprintGetAuthenticatorIdClient client =
new FingerprintGetAuthenticatorIdClient(mContext,
@@ -387,8 +471,8 @@
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken,
- mFingerprintSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
- Utils.getCurrentStrength(sensorId));
+ mFingerprintSensors.get(sensorId).getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcher, Utils.getCurrentStrength(sensorId));
scheduleForSensor(sensorId, client);
});
}
@@ -443,18 +527,23 @@
mFingerprintSensors.get(sensorId).getSensorProperties(),
mUdfpsOverlayController, mSidefpsController,
mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason);
- scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
- mBiometricStateCallback, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- ClientMonitorCallback.super.onClientFinished(clientMonitor, success);
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
- }
- }));
+ if (Flags.deHidl()) {
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
+ } else {
+ scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
+ mBiometricStateCallback, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ ClientMonitorCallback.super.onClientFinished(
+ clientMonitor, success);
+ if (success) {
+ scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+ scheduleInvalidationRequest(sensorId, userId);
+ }
+ }
+ }));
+ }
});
return id;
}
@@ -497,6 +586,13 @@
final int userId = options.getUserId();
final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+ final LockoutTracker lockoutTracker;
+ if (Flags.deHidl()) {
+ lockoutTracker = mFingerprintSensors.get(sensorId)
+ .getLockoutTracker(true /* forAuth */);
+ } else {
+ lockoutTracker = null;
+ }
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
@@ -510,7 +606,7 @@
mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
Utils.getCurrentStrength(sensorId),
SystemClock.elapsedRealtimeClock(),
- null /* lockoutTracker */);
+ lockoutTracker);
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
@@ -636,6 +732,9 @@
@Override
public boolean isHardwareDetected(int sensorId) {
+ if (Flags.deHidl()) {
+ return mFingerprintSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
+ }
return hasHalInstance();
}
@@ -674,8 +773,12 @@
@Override
public int getLockoutModeForUser(int sensorId, int userId) {
- return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
- Utils.getCurrentStrength(sensorId));
+ if (Flags.deHidl()) {
+ return mFingerprintSensors.get(sensorId).getLockoutModeForUser(userId);
+ } else {
+ return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+ Utils.getCurrentStrength(sensorId));
+ }
}
@Override
@@ -829,6 +932,10 @@
mTestHalEnabled = enabled;
}
+ public boolean getTestHalEnabled() {
+ return mTestHalEnabled;
+ }
+
// TODO(b/174868353): workaround for gaps in HAL interface (remove and get directly from HAL)
// reads values via an overlay instead of querying the HAL
@NonNull
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index ec225a6..387ae12 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -60,7 +60,8 @@
@Authenticators.Types int biometricStrength) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
0 /* cookie */, sensorId, biometricLogger, biometricContext);
- mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
+ mHardwareAuthToken = hardwareAuthToken == null ? null :
+ HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
mLockoutCache = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mBiometricStrength = biometricStrength;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 893cb8f..dd887bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -21,21 +21,28 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.SensorLocationInternal;
+import android.hardware.biometrics.common.ComponentInfo;
+import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -48,15 +55,20 @@
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
/**
* Maintains the state of a single sensor within an instance of the
@@ -73,15 +85,17 @@
@NonNull private final IBinder mToken;
@NonNull private final Handler mHandler;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
- @NonNull private final UserAwareBiometricScheduler mScheduler;
- @NonNull private final LockoutCache mLockoutCache;
+ @NonNull private BiometricScheduler mScheduler;
+ @NonNull private LockoutTracker mLockoutTracker;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
+ @NonNull private final BiometricContext mBiometricContext;
@Nullable AidlSession mCurrentSession;
- @NonNull private final Supplier<AidlSession> mLazySession;
+ @NonNull private Supplier<AidlSession> mLazySession;
- Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
- @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+ public Sensor(@NonNull String tag, @NonNull FingerprintProvider provider,
+ @NonNull Context context, @NonNull Handler handler,
+ @NonNull FingerprintSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull BiometricContext biometricContext, AidlSession session) {
@@ -91,8 +105,38 @@
mToken = new Binder();
mHandler = handler;
mSensorProperties = sensorProperties;
- mLockoutCache = new LockoutCache();
- mScheduler = new UserAwareBiometricScheduler(tag,
+ mBiometricContext = biometricContext;
+ mAuthenticatorIds = new HashMap<>();
+ mCurrentSession = session;
+ }
+
+ Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext) {
+ this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
+ gestureAvailabilityDispatcher, biometricContext, null);
+ }
+
+ Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull SensorProps sensorProp,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext,
+ @NonNull List<SensorLocationInternal> workaroundLocation,
+ boolean resetLockoutRequiresHardwareAuthToken) {
+ this(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(sensorProp,
+ workaroundLocation, resetLockoutRequiresHardwareAuthToken),
+ lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext, null);
+ }
+
+ /**
+ * Initialize biometric scheduler, lockout tracker and session for the sensor.
+ */
+ public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ LockoutResetDispatcher lockoutResetDispatcher) {
+ mScheduler = new UserAwareBiometricScheduler(mTag,
BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
gestureAvailabilityDispatcher,
() -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
@@ -102,7 +146,7 @@
public StopUserClient<?> getStopUserClient(int userId) {
return new FingerprintStopUserClient(mContext, mLazySession, mToken,
userId, mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), biometricContext,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
() -> mCurrentSession = null);
}
@@ -111,13 +155,38 @@
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
final int sensorId = mSensorProperties.sensorId;
- final AidlResponseHandler resultController = new AidlResponseHandler(
- mContext, mScheduler, sensorId, newUserId,
- mLockoutCache, lockoutResetDispatcher,
- biometricContext.getAuthSessionCoordinator(), () -> {
- Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
- mCurrentSession = null;
- });
+ final AidlResponseHandler resultController;
+
+ if (Flags.deHidl()) {
+ resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutTracker, lockoutResetDispatcher,
+ mBiometricContext.getAuthSessionCoordinator(), () -> {},
+ new AidlResponseHandler.AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {
+ mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+ newUserId);
+ mProvider.scheduleInvalidationRequest(sensorId,
+ newUserId);
+ }
+
+ @Override
+ public void onHardwareUnavailable() {
+ Slog.e(mTag,
+ "Fingerprint sensor hardware unavailable.");
+ mCurrentSession = null;
+ }
+ });
+ } else {
+ resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutTracker, lockoutResetDispatcher,
+ mBiometricContext.getAuthSessionCoordinator(), () -> {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
+ });
+ }
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
(userIdStarted, newSession, halInterfaceVersion) -> {
@@ -133,40 +202,58 @@
+ "sensor: "
+ sensorId
+ ", user: " + userIdStarted);
- provider.scheduleInvalidationRequest(sensorId,
+ mProvider.scheduleInvalidationRequest(sensorId,
userIdStarted);
}
};
- return new FingerprintStartUserClient(mContext, provider::getHalInstance,
+ return new FingerprintStartUserClient(mContext, mProvider::getHalInstance,
mToken, newUserId, mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), biometricContext,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
resultController, userStartedCallback);
}
});
- mAuthenticatorIds = new HashMap<>();
+ mLockoutTracker = new LockoutCache();
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
- mCurrentSession = session;
}
- Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
- @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- @NonNull BiometricContext biometricContext) {
- this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
- gestureAvailabilityDispatcher, biometricContext, null);
+ protected static FingerprintSensorPropertiesInternal getFingerprintSensorPropertiesInternal(
+ SensorProps prop, List<SensorLocationInternal> workaroundLocations,
+ boolean resetLockoutRequiresHardwareAuthToken) {
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ if (prop.commonProps.componentInfo != null) {
+ for (ComponentInfo info : prop.commonProps.componentInfo) {
+ componentInfo.add(new ComponentInfoInternal(info.componentId,
+ info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+ info.softwareVersion));
+ }
+ }
+ return new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
+ prop.commonProps.sensorStrength,
+ prop.commonProps.maxEnrollmentsPerUser,
+ componentInfo,
+ prop.sensorType,
+ prop.halControlsIllumination,
+ resetLockoutRequiresHardwareAuthToken,
+ !workaroundLocations.isEmpty() ? workaroundLocations :
+ Arrays.stream(prop.sensorLocations).map(location ->
+ new SensorLocationInternal(
+ location.display,
+ location.sensorLocationX,
+ location.sensorLocationY,
+ location.sensorRadius))
+ .collect(Collectors.toList()));
}
- @NonNull Supplier<AidlSession> getLazySession() {
+ @NonNull public Supplier<AidlSession> getLazySession() {
return mLazySession;
}
- @NonNull FingerprintSensorPropertiesInternal getSensorProperties() {
+ @NonNull public FingerprintSensorPropertiesInternal getSensorProperties() {
return mSensorProperties;
}
- @Nullable AidlSession getSessionForUser(int userId) {
+ @Nullable protected AidlSession getSessionForUser(int userId) {
if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
@@ -180,15 +267,18 @@
biometricStateCallback, mProvider, this);
}
- @NonNull BiometricScheduler getScheduler() {
+ @NonNull public BiometricScheduler getScheduler() {
return mScheduler;
}
- @NonNull LockoutCache getLockoutCache() {
- return mLockoutCache;
+ @NonNull protected LockoutTracker getLockoutTracker(boolean forAuth) {
+ if (forAuth) {
+ return null;
+ }
+ return mLockoutTracker;
}
- @NonNull Map<Integer, Long> getAuthenticatorIds() {
+ @NonNull public Map<Integer, Long> getAuthenticatorIds() {
return mAuthenticatorIds;
}
@@ -262,4 +352,49 @@
mScheduler.reset();
mCurrentSession = null;
}
+
+ @NonNull protected Handler getHandler() {
+ return mHandler;
+ }
+
+ @NonNull protected Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Returns true if the sensor hardware is detected.
+ */
+ protected boolean isHardwareDetected(String halInstance) {
+ if (mTestHalEnabled) {
+ return true;
+ }
+ return (ServiceManager.checkService(IFingerprint.DESCRIPTOR + "/" + halInstance)
+ != null);
+ }
+
+ @NonNull protected BiometricContext getBiometricContext() {
+ return mBiometricContext;
+ }
+
+ /**
+ * Returns lockout mode of this sensor.
+ */
+ @LockoutTracker.LockoutMode
+ public int getLockoutModeForUser(int userId) {
+ return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+ Utils.getCurrentStrength(mSensorProperties.sensorId));
+ }
+
+ public void setScheduler(BiometricScheduler scheduler) {
+ mScheduler = scheduler;
+ }
+
+ public void setLazySession(
+ Supplier<AidlSession> lazySession) {
+ mLazySession = lazySession;
+ }
+
+ public FingerprintProvider getProvider() {
+ return mProvider;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index a4e6025..5c5b992 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -29,7 +29,8 @@
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.HalClientMonitor;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
import java.io.File;
import java.util.Map;
@@ -38,7 +39,8 @@
/**
* Sets the HAL's current active user, and updates the framework's authenticatorId cache.
*/
-public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometricsFingerprint> {
+public class FingerprintUpdateActiveUserClient extends
+ StartUserClient<IBiometricsFingerprint, AidlSession> {
private static final String TAG = "FingerprintUpdateActiveUserClient";
private static final String FP_DATA_DIR = "fpdata";
@@ -53,11 +55,24 @@
@NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
@NonNull String owner, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- Supplier<Integer> currentUserId,
+ @NonNull Supplier<Integer> currentUserId,
boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
boolean forceUpdateAuthenticatorId) {
- super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, logger, biometricContext);
+ this(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, currentUserId,
+ hasEnrolledBiometrics, authenticatorIds, forceUpdateAuthenticatorId,
+ (newUserId, newUser, halInterfaceVersion) -> {});
+ }
+
+ FingerprintUpdateActiveUserClient(@NonNull Context context,
+ @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull Supplier<Integer> currentUserId,
+ boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
+ boolean forceUpdateAuthenticatorId,
+ @NonNull UserStartedCallback<AidlSession> userStartedCallback) {
+ super(context, lazyDaemon, null /* token */, userId, sensorId, logger, biometricContext,
+ userStartedCallback);
mCurrentUserId = currentUserId;
mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
mHasEnrolledBiometrics = hasEnrolledBiometrics;
@@ -70,6 +85,7 @@
if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) {
Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
+ mUserStartedCallback.onUserStarted(getTargetUserId(), null, 0);
callback.onClientFinished(this, true /* success */);
return;
}
@@ -119,6 +135,7 @@
getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath());
mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics
? getFreshDaemon().getAuthenticatorId() : 0L);
+ mUserStartedCallback.onUserStarted(targetId, null, 0);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to setActiveGroup: " + e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
index c3e5cbe..e9a48e7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
@@ -20,6 +20,7 @@
import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
import java.util.ArrayList;
@@ -73,12 +74,12 @@
@Override
public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
- mAidlResponseHandler.onEnrollmentsRemoved(new int[]{fingerId});
+ mAidlResponseHandler.onEnrollmentRemoved(fingerId, remaining);
}
@Override
public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
- mAidlResponseHandler.onEnrollmentsEnumerated(new int[]{fingerId});
+ mAidlResponseHandler.onEnrollmentEnumerated(fingerId, remaining);
}
void onChallengeGenerated(long challenge) {
@@ -92,4 +93,8 @@
void onResetLockout() {
mAidlResponseHandler.onLockoutCleared();
}
+
+ <T extends BaseClientMonitor> void unsupportedClientScheduled(Class<T> className) {
+ mAidlResponseHandler.onUnsupportedClientScheduled(className);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
new file mode 100644
index 0000000..0bb61415
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2023 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.biometrics.sensors.fingerprint.hidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.os.Handler;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
+import com.android.server.biometrics.sensors.fingerprint.aidl.Sensor;
+
+import java.util.ArrayList;
+
+/**
+ * Convert HIDL sensor configurations to an AIDL Sensor.
+ */
+public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRecipient {
+ private static final String TAG = "HidlToAidlSensorAdapter";
+
+ private final Runnable mInternalCleanupRunnable;
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
+ private LockoutFrameworkImpl mLockoutTracker;
+ private final AuthSessionCoordinator mAuthSessionCoordinator;
+ private final AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+ private IBiometricsFingerprint mDaemon;
+ private AidlSession mSession;
+
+ private final StartUserClient.UserStartedCallback<AidlSession> mUserStartedCallback =
+ (newUserId, newUser, halInterfaceVersion) -> {
+ if (mCurrentUserId != newUserId) {
+ handleUserChanged(newUserId);
+ }
+ };
+
+ public HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider,
+ @NonNull Context context, @NonNull Handler handler,
+ @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresHardwareAuthToken,
+ @NonNull Runnable internalCleanupRunnable) {
+ this(tag, provider, context, handler, prop, lockoutResetDispatcher,
+ gestureAvailabilityDispatcher, biometricContext,
+ resetLockoutRequiresHardwareAuthToken, internalCleanupRunnable,
+ new AuthSessionCoordinator(), null /* daemon */,
+ null /* onEnrollSuccessCallback */);
+ }
+
+ @VisibleForTesting
+ HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider,
+ @NonNull Context context, @NonNull Handler handler,
+ @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresHardwareAuthToken,
+ @NonNull Runnable internalCleanupRunnable,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @Nullable IBiometricsFingerprint daemon,
+ @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+ super(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(prop,
+ new ArrayList<>(), resetLockoutRequiresHardwareAuthToken),
+ lockoutResetDispatcher,
+ gestureAvailabilityDispatcher,
+ biometricContext, null /* session */);
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+ mInternalCleanupRunnable = internalCleanupRunnable;
+ mAuthSessionCoordinator = authSessionCoordinator;
+ mDaemon = daemon;
+ mAidlResponseHandlerCallback = aidlResponseHandlerCallback == null
+ ? new AidlResponseHandler.AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {
+ getScheduler()
+ .scheduleClientMonitor(getFingerprintUpdateActiveUserClient(
+ mCurrentUserId, true /* forceUpdateAuthenticatorIds */));
+ }
+
+ @Override
+ public void onHardwareUnavailable() {
+ mDaemon = null;
+ mSession = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }
+ } : aidlResponseHandlerCallback;
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ Slog.d(TAG, "HAL died.");
+ mSession = null;
+ mDaemon = null;
+ }
+
+ @Override
+ @LockoutTracker.LockoutMode
+ public int getLockoutModeForUser(int userId) {
+ return mLockoutTracker.getLockoutModeForUser(userId);
+ }
+
+ @Override
+ public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ LockoutResetDispatcher lockoutResetDispatcher) {
+ setLazySession(this::getSession);
+ setScheduler(new UserAwareBiometricScheduler(TAG,
+ BiometricScheduler.sensorTypeFromFingerprintProperties(getSensorProperties()),
+ gestureAvailabilityDispatcher, () -> mCurrentUserId, getUserSwitchCallback()));
+ mLockoutTracker = new LockoutFrameworkImpl(getContext(),
+ userId -> mLockoutResetDispatcher.notifyLockoutResetCallbacks(
+ getSensorProperties().sensorId));
+ }
+
+ @Override
+ @Nullable
+ protected AidlSession getSessionForUser(int userId) {
+ if (mSession != null && mSession.getUserId() == userId) {
+ return mSession;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected boolean isHardwareDetected(String halInstance) {
+ return getIBiometricsFingerprint() != null;
+ }
+
+ @NonNull
+ @Override
+ protected LockoutTracker getLockoutTracker(boolean forAuth) {
+ return mLockoutTracker;
+ }
+
+ private synchronized AidlSession getSession() {
+ if (mSession != null && mDaemon != null) {
+ return mSession;
+ } else {
+ return mSession = new AidlSession(this::getIBiometricsFingerprint,
+ mCurrentUserId, getAidlResponseHandler());
+ }
+ }
+
+ private AidlResponseHandler getAidlResponseHandler() {
+ return new AidlResponseHandler(getContext(),
+ getScheduler(),
+ getSensorProperties().sensorId,
+ mCurrentUserId,
+ mLockoutTracker,
+ mLockoutResetDispatcher,
+ mAuthSessionCoordinator,
+ () -> {}, mAidlResponseHandlerCallback);
+ }
+
+ @VisibleForTesting IBiometricsFingerprint getIBiometricsFingerprint() {
+ if (getProvider().getTestHalEnabled()) {
+ final TestHal testHal = new TestHal(getContext(), getSensorProperties().sensorId);
+ testHal.setNotify(new HidlToAidlCallbackConverter(getAidlResponseHandler()));
+ return testHal;
+ }
+
+ if (mDaemon != null) {
+ return mDaemon;
+ }
+
+ try {
+ mDaemon = IBiometricsFingerprint.getService();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get fingerprint HAL", e);
+ } catch (java.util.NoSuchElementException e) {
+ // Service doesn't exist or cannot be opened.
+ Slog.w(TAG, "NoSuchElementException", e);
+ }
+
+ if (mDaemon == null) {
+ Slog.w(TAG, "Fingerprint HAL not available");
+ return null;
+ }
+
+ mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
+
+ Slog.d(TAG, "Fingerprint HAL ready");
+
+ scheduleLoadAuthenticatorIds();
+ mInternalCleanupRunnable.run();
+ return mDaemon;
+ }
+
+ private UserAwareBiometricScheduler.UserSwitchCallback getUserSwitchCallback() {
+ return new UserAwareBiometricScheduler.UserSwitchCallback() {
+ @NonNull
+ @Override
+ public StopUserClient<?> getStopUserClient(int userId) {
+ return new StopUserClient<IBiometricsFingerprint>(getContext(),
+ HidlToAidlSensorAdapter.this::getIBiometricsFingerprint,
+ null /* token */, userId, getSensorProperties().sensorId,
+ BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
+ () -> {
+ mCurrentUserId = UserHandle.USER_NULL;
+ mSession = null;
+ }) {
+ @Override
+ public void start(@NonNull ClientMonitorCallback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ onUserStopped();
+ }
+
+ @Override
+ public void unableToStart() {
+ getCallback().onClientFinished(this, false /* success */);
+ }
+ };
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+ return getFingerprintUpdateActiveUserClient(newUserId,
+ false /* forceUpdateAuthenticatorId */);
+ }
+ };
+ }
+
+ private FingerprintUpdateActiveUserClient getFingerprintUpdateActiveUserClient(int newUserId,
+ boolean forceUpdateAuthenticatorIds) {
+ return new FingerprintUpdateActiveUserClient(getContext(),
+ this::getIBiometricsFingerprint, newUserId, TAG,
+ getSensorProperties().sensorId, BiometricLogger.ofUnknown(getContext()),
+ getBiometricContext(), () -> mCurrentUserId,
+ !FingerprintUtils.getInstance(getSensorProperties().sensorId)
+ .getBiometricsForUser(getContext(),
+ newUserId).isEmpty(), getAuthenticatorIds(), forceUpdateAuthenticatorIds,
+ mUserStartedCallback);
+ }
+
+ private void scheduleLoadAuthenticatorIds() {
+ getHandler().post(() -> {
+ for (UserInfo user : UserManager.get(getContext()).getAliveUsers()) {
+ final int targetUserId = user.id;
+ if (!getAuthenticatorIds().containsKey(targetUserId)) {
+ getScheduler().scheduleClientMonitor(getFingerprintUpdateActiveUserClient(
+ targetUserId, true /* forceUpdateAuthenticatorIds */));
+ }
+ }
+ });
+ }
+
+ @VisibleForTesting void handleUserChanged(int newUserId) {
+ Slog.d(TAG, "User changed. Current user is " + newUserId);
+ mSession = null;
+ mCurrentUserId = newUserId;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
similarity index 73%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
index b48d232..2fc00e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
@@ -25,20 +25,25 @@
import android.hardware.keymaster.HardwareAuthToken;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGetAuthenticatorIdClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInvalidationClient;
import java.util.function.Supplier;
/**
- * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ * Adapter to convert HIDL methods into AIDL interface {@link ISession}.
*/
-public class AidlToHidlAdapter implements ISession {
- private final String TAG = "AidlToHidlAdapter";
+public class HidlToAidlSessionAdapter implements ISession {
+
+ private final String TAG = "HidlToAidlSessionAdapter";
+
@VisibleForTesting
static final int ENROLL_TIMEOUT_SEC = 60;
@NonNull
@@ -46,22 +51,13 @@
private final int mUserId;
private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
- public AidlToHidlAdapter(Supplier<IBiometricsFingerprint> session, int userId,
+ public HidlToAidlSessionAdapter(Supplier<IBiometricsFingerprint> session, int userId,
AidlResponseHandler aidlResponseHandler) {
mSession = session;
mUserId = userId;
setCallback(aidlResponseHandler);
}
- private void setCallback(AidlResponseHandler aidlResponseHandler) {
- mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
- try {
- mSession.get().setNotify(mHidlToAidlCallbackConverter);
- } catch (RemoteException e) {
- Slog.d(TAG, "Failed to set callback");
- }
- }
-
@Override
public IBinder asBinder() {
return null;
@@ -125,12 +121,16 @@
@Override
public void getAuthenticatorId() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "getAuthenticatorId unsupported in HIDL");
+ mHidlToAidlCallbackConverter.unsupportedClientScheduled(
+ FingerprintGetAuthenticatorIdClient.class);
}
@Override
public void invalidateAuthenticatorId() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "invalidateAuthenticatorId unsupported in HIDL");
+ mHidlToAidlCallbackConverter.unsupportedClientScheduled(
+ FingerprintInvalidationClient.class);
}
@Override
@@ -140,72 +140,92 @@
@Override
public void close() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "close unsupported in HIDL");
}
@Override
public void onUiReady() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onUiReady unsupported in HIDL");
}
@Override
public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Log.e(TAG, "authenticateWithContext unsupported in HIDL");
+ return authenticate(operationId);
}
@Override
public ICancellationSignal enrollWithContext(HardwareAuthToken hat, OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Log.e(TAG, "enrollWithContext unsupported in HIDL");
+ return enroll(hat);
}
@Override
public ICancellationSignal detectInteractionWithContext(OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Log.e(TAG, "enrollWithContext unsupported in HIDL");
+ return detectInteraction();
}
@Override
public void onPointerDownWithContext(PointerContext context) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onPointerDownWithContext unsupported in HIDL");
+ onPointerDown(context.pointerId, (int) context.x, (int) context.y, context.minor,
+ context.major);
}
@Override
public void onPointerUpWithContext(PointerContext context) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onPointerUpWithContext unsupported in HIDL");
+ onPointerUp(context.pointerId);
}
@Override
public void onContextChanged(OperationContext context) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onContextChanged unsupported in HIDL");
}
@Override
public void onPointerCancelWithContext(PointerContext context) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onPointerCancelWithContext unsupported in HIDL");
}
@Override
public void setIgnoreDisplayTouches(boolean shouldIgnore) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "setIgnoreDisplayTouches unsupported in HIDL");
}
@Override
public int getInterfaceVersion() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "getInterfaceVersion unsupported in HIDL");
return 0;
}
@Override
public String getInterfaceHash() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "getInterfaceHash unsupported in HIDL");
return null;
}
+ private void setCallback(AidlResponseHandler aidlResponseHandler) {
+ mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+ try {
+ if (mSession.get() != null) {
+ long halId = mSession.get().setNotify(mHidlToAidlCallbackConverter);
+ Slog.d(TAG, "Fingerprint HAL ready, HAL ID: " + halId);
+ if (halId == 0) {
+ Slog.d(TAG, "Unable to set HIDL callback.");
+ }
+ } else {
+ Slog.e(TAG, "Unable to set HIDL callback. HIDL daemon is null.");
+ }
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to set callback");
+ }
+ }
+
private class Cancellation extends ICancellationSignal.Stub {
Cancellation() {}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
index 0730c67..2f77275 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
+import android.annotation.NonNull;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -31,15 +32,18 @@
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.sensors.LockoutTracker;
+import java.util.function.Function;
+
/**
* Tracks and enforces biometric lockout for biometric sensors that do not support lockout in the
* HAL.
*/
public class LockoutFrameworkImpl implements LockoutTracker {
- private static final String TAG = "LockoutTracker";
+ private static final String TAG = "LockoutFrameworkImpl";
private static final String ACTION_LOCKOUT_RESET =
"com.android.server.biometrics.sensors.fingerprint.ACTION_LOCKOUT_RESET";
private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 5;
@@ -65,22 +69,32 @@
void onLockoutReset(int userId);
}
- private final Context mContext;
private final LockoutResetCallback mLockoutResetCallback;
private final SparseBooleanArray mTimedLockoutCleared;
private final SparseIntArray mFailedAttempts;
private final AlarmManager mAlarmManager;
private final LockoutReceiver mLockoutReceiver;
private final Handler mHandler;
+ private final Function<Integer, PendingIntent> mLockoutResetIntent;
- public LockoutFrameworkImpl(Context context, LockoutResetCallback lockoutResetCallback) {
- mContext = context;
+ public LockoutFrameworkImpl(@NonNull Context context,
+ @NonNull LockoutResetCallback lockoutResetCallback) {
+ this(context, lockoutResetCallback, (userId) -> PendingIntent.getBroadcast(context, userId,
+ new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
+ }
+
+ @VisibleForTesting
+ LockoutFrameworkImpl(@NonNull Context context,
+ @NonNull LockoutResetCallback lockoutResetCallback,
+ @NonNull Function<Integer, PendingIntent> lockoutResetIntent) {
mLockoutResetCallback = lockoutResetCallback;
mTimedLockoutCleared = new SparseBooleanArray();
mFailedAttempts = new SparseIntArray();
mAlarmManager = context.getSystemService(AlarmManager.class);
mLockoutReceiver = new LockoutReceiver();
mHandler = new Handler(Looper.getMainLooper());
+ mLockoutResetIntent = lockoutResetIntent;
context.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
RESET_FINGERPRINT_LOCKOUT, null /* handler */, Context.RECEIVER_EXPORTED);
@@ -129,34 +143,18 @@
return LOCKOUT_NONE;
}
- /**
- * Clears lockout for Fingerprint HIDL HAL
- */
@Override
- public void setLockoutModeForUser(int userId, int mode) {
- mFailedAttempts.put(userId, 0);
- mTimedLockoutCleared.put(userId, true);
- // If we're asked to reset failed attempts externally (i.e. from Keyguard),
- // the alarm might still be pending; remove it.
- cancelLockoutResetForUser(userId);
- mLockoutResetCallback.onLockoutReset(userId);
- }
+ public void setLockoutModeForUser(int userId, int mode) {}
private void cancelLockoutResetForUser(int userId) {
- mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
+ mAlarmManager.cancel(mLockoutResetIntent.apply(userId));
}
private void scheduleLockoutResetForUser(int userId) {
mHandler.post(() -> {
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
- getLockoutResetIntentForUser(userId));
+ SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
+ mLockoutResetIntent.apply(userId));
});
}
-
- private PendingIntent getLockoutResetIntentForUser(int userId) {
- return PendingIntent.getBroadcast(mContext, userId,
- new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- }
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index c517058..b7ece2ea 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1299,7 +1299,7 @@
}
try {
- mNms.denyProtect(mOwnerUID);
+ mNetd.networkSetProtectDeny(mOwnerUID);
} catch (Exception e) {
Log.wtf(TAG, "Failed to disallow UID " + mOwnerUID + " to call protect() " + e);
}
@@ -1309,7 +1309,7 @@
mOwnerUID = getAppUid(mContext, newPackage, mUserId);
mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(newPackage);
try {
- mNms.allowProtect(mOwnerUID);
+ mNetd.networkSetProtectAllow(mOwnerUID);
} catch (Exception e) {
Log.wtf(TAG, "Failed to allow UID " + mOwnerUID + " to call protect() " + e);
}
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index acd253b..f2ffd4d 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -35,7 +35,6 @@
import android.util.Spline;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.display.BrightnessUtils;
import com.android.internal.util.Preconditions;
import com.android.server.display.utils.Plog;
@@ -97,28 +96,17 @@
float[] brightnessLevels = null;
float[] luxLevels = null;
switch (mode) {
- case AUTO_BRIGHTNESS_MODE_DEFAULT:
+ case AUTO_BRIGHTNESS_MODE_DEFAULT -> {
brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux();
-
brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels();
- if (brightnessLevels == null || brightnessLevels.length == 0) {
- // Load the old configuration in the range [0, 255]. The values need to be
- // normalized to the range [0, 1].
- int[] brightnessLevelsInt = resources.getIntArray(
- com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
- brightnessLevels = new float[brightnessLevelsInt.length];
- for (int i = 0; i < brightnessLevels.length; i++) {
- brightnessLevels[i] = normalizeAbsoluteBrightness(brightnessLevelsInt[i]);
- }
- }
- break;
- case AUTO_BRIGHTNESS_MODE_IDLE:
+ }
+ case AUTO_BRIGHTNESS_MODE_IDLE -> {
brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle));
luxLevels = getLuxLevels(resources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLevelsIdle));
- break;
+ }
}
// Display independent, mode independent values
@@ -426,11 +414,6 @@
}
}
- // Normalize entire brightness range to 0 - 1.
- protected static float normalizeAbsoluteBrightness(int brightness) {
- return BrightnessSynchronizer.brightnessIntToFloat(brightness);
- }
-
private Pair<float[], float[]> insertControlPoint(
float[] luxLevels, float[] brightnessLevels, float lux, float brightness) {
final int idx = findInsertionPoint(luxLevels, lux);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index d97127c..7d22a87 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -49,6 +49,7 @@
import com.android.server.display.config.BrightnessThrottlingMap;
import com.android.server.display.config.BrightnessThrottlingPoint;
import com.android.server.display.config.Density;
+import com.android.server.display.config.DisplayBrightnessMappingConfig;
import com.android.server.display.config.DisplayBrightnessPoint;
import com.android.server.display.config.DisplayConfiguration;
import com.android.server.display.config.DisplayQuirks;
@@ -57,7 +58,6 @@
import com.android.server.display.config.HighBrightnessMode;
import com.android.server.display.config.IntegerArray;
import com.android.server.display.config.LuxThrottling;
-import com.android.server.display.config.LuxToBrightnessMapping;
import com.android.server.display.config.NitsMap;
import com.android.server.display.config.NonNegativeFloatToFloatPoint;
import com.android.server.display.config.Point;
@@ -313,6 +313,21 @@
* 1000
* </darkeningLightDebounceIdleMillis>
* <luxToBrightnessMapping>
+ * <mode>default</mode>
+ * <map>
+ * <point>
+ * <first>0</first>
+ * <second>0.2</second>
+ * </point>
+ * <point>
+ * <first>80</first>
+ * <second>0.3</second>
+ * </point>
+ * </map>
+ * </luxToBrightnessMapping>
+ * <luxToBrightnessMapping>
+ * <mode>doze</mode>
+ * <setting>dim</setting>
* <map>
* <point>
* <first>0</first>
@@ -634,36 +649,8 @@
// for the corresponding values above
private float[] mBrightness;
- /**
- * Array of desired screen brightness in nits corresponding to the lux values
- * in the mBrightnessLevelsLux array. The display brightness is defined as the
- * measured brightness of an all-white image. The brightness values must be non-negative and
- * non-decreasing. This must be overridden in platform specific overlays
- */
- private float[] mBrightnessLevelsNits;
-
- /**
- * Array of desired screen brightness corresponding to the lux values
- * in the mBrightnessLevelsLux array. The brightness values must be non-negative and
- * non-decreasing. They must be between {@link PowerManager.BRIGHTNESS_MIN} and
- * {@link PowerManager.BRIGHTNESS_MAX}. This must be overridden in platform specific overlays
- */
- private float[] mBrightnessLevels;
-
- /**
- * Array of light sensor lux values to define our levels for auto-brightness support.
- *
- * The first lux value is always 0.
- *
- * The control points must be strictly increasing. Each control point corresponds to an entry
- * in the brightness values arrays. For example, if lux == luxLevels[1] (second element
- * of the levels array) then the brightness will be determined by brightnessLevels[1] (second
- * element of the brightness values array).
- *
- * Spline interpolation is used to determine the auto-brightness values for lux levels between
- * these control points.
- */
- private float[] mBrightnessLevelsLux;
+ @Nullable
+ private DisplayBrightnessMappingConfig mDisplayBrightnessMapping;
private float mBacklightMinimum = Float.NaN;
private float mBacklightMaximum = Float.NaN;
@@ -1604,24 +1591,57 @@
}
/**
- * @return Auto brightness brightening ambient lux levels
+ * @return The default auto-brightness brightening ambient lux levels
*/
public float[] getAutoBrightnessBrighteningLevelsLux() {
- return mBrightnessLevelsLux;
+ if (mDisplayBrightnessMapping == null) {
+ return null;
+ }
+ return mDisplayBrightnessMapping.getLuxArray();
+ }
+
+ /**
+ * @param mode The auto-brightness mode
+ * @param setting The brightness setting
+ * @return Auto brightness brightening ambient lux levels for the specified mode and setting
+ */
+ public float[] getAutoBrightnessBrighteningLevelsLux(String mode, String setting) {
+ if (mDisplayBrightnessMapping == null) {
+ return null;
+ }
+ return mDisplayBrightnessMapping.getLuxArray(mode, setting);
}
/**
* @return Auto brightness brightening nits levels
*/
public float[] getAutoBrightnessBrighteningLevelsNits() {
- return mBrightnessLevelsNits;
+ if (mDisplayBrightnessMapping == null) {
+ return null;
+ }
+ return mDisplayBrightnessMapping.getNitsArray();
}
/**
- * @return Auto brightness brightening levels
+ * @return The default auto-brightness brightening levels
*/
public float[] getAutoBrightnessBrighteningLevels() {
- return mBrightnessLevels;
+ if (mDisplayBrightnessMapping == null) {
+ return null;
+ }
+ return mDisplayBrightnessMapping.getBrightnessArray();
+ }
+
+ /**
+ * @param mode The auto-brightness mode
+ * @param setting The brightness setting
+ * @return Auto brightness brightening backlight levels for the specified mode and setting
+ */
+ public float[] getAutoBrightnessBrighteningLevels(String mode, String setting) {
+ if (mDisplayBrightnessMapping == null) {
+ return null;
+ }
+ return mDisplayBrightnessMapping.getBrightnessArray(mode, setting);
}
/**
@@ -1875,9 +1895,7 @@
+ mAutoBrightnessBrighteningLightDebounceIdle
+ ", mAutoBrightnessDarkeningLightDebounceIdle= "
+ mAutoBrightnessDarkeningLightDebounceIdle
- + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
- + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
- + ", mBrightnessLevels= " + Arrays.toString(mBrightnessLevels)
+ + ", mDisplayBrightnessMapping= " + mDisplayBrightnessMapping
+ ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
+ ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
+ "\n"
@@ -2568,7 +2586,8 @@
// Idle must be called after interactive, since we fall back to it if needed.
loadAutoBrightnessBrighteningLightDebounceIdle(autoBrightness);
loadAutoBrightnessDarkeningLightDebounceIdle(autoBrightness);
- loadAutoBrightnessDisplayBrightnessMapping(autoBrightness);
+ mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags,
+ autoBrightness, mBacklightToBrightnessSpline);
loadEnableAutoBrightness(autoBrightness);
}
@@ -2633,38 +2652,6 @@
}
}
- /**
- * Loads the auto-brightness display brightness mappings. Internally, this takes care of
- * loading the value from the display config, and if not present, falls back to config.xml.
- */
- private void loadAutoBrightnessDisplayBrightnessMapping(AutoBrightness autoBrightnessConfig) {
- if (mFlags.areAutoBrightnessModesEnabled() && autoBrightnessConfig != null
- && autoBrightnessConfig.getLuxToBrightnessMapping() != null) {
- LuxToBrightnessMapping mapping = autoBrightnessConfig.getLuxToBrightnessMapping();
- final int size = mapping.getMap().getPoint().size();
- mBrightnessLevels = new float[size];
- mBrightnessLevelsLux = new float[size];
- for (int i = 0; i < size; i++) {
- float backlight = mapping.getMap().getPoint().get(i).getSecond().floatValue();
- mBrightnessLevels[i] = mBacklightToBrightnessSpline.interpolate(backlight);
- mBrightnessLevelsLux[i] = mapping.getMap().getPoint().get(i).getFirst()
- .floatValue();
- }
- if (size > 0 && mBrightnessLevelsLux[0] != 0) {
- throw new IllegalArgumentException(
- "The first lux value in the display brightness mapping must be 0");
- }
- } else {
- mBrightnessLevelsNits = getFloatArray(mContext.getResources()
- .obtainTypedArray(com.android.internal.R.array
- .config_autoBrightnessDisplayValuesNits), PowerManager
- .BRIGHTNESS_OFF_FLOAT);
- mBrightnessLevelsLux = getLuxLevels(mContext.getResources()
- .getIntArray(com.android.internal.R.array
- .config_autoBrightnessLevels));
- }
- }
-
private void loadAutoBrightnessAvailableFromConfigXml() {
mAutoBrightnessAvailable = mContext.getResources().getBoolean(
R.bool.config_automatic_brightness_available);
@@ -2977,7 +2964,8 @@
}
private void loadAutoBrightnessConfigsFromConfigXml() {
- loadAutoBrightnessDisplayBrightnessMapping(null /*AutoBrightnessConfig*/);
+ mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags,
+ /* autoBrightnessConfig= */ null, mBacklightToBrightnessSpline);
}
private void loadBrightnessChangeThresholdsFromXml() {
@@ -3347,7 +3335,12 @@
return vals;
}
- private static float[] getLuxLevels(int[] lux) {
+ /**
+ * @param lux The lux array
+ * @return The lux array with 0 appended at the beginning - the first lux value should always
+ * be 0
+ */
+ public static float[] getLuxLevels(int[] lux) {
// The first control point is implicit and always at 0 lux.
float[] levels = new float[lux.length + 1];
for (int i = 0; i < lux.length; i++) {
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 1bd556b..4e341a9 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -56,6 +56,15 @@
}
}
+ @Override
+ public boolean blockScreenOn(Runnable unblocker) {
+ if (mDisplayOffloader == null) {
+ return false;
+ }
+ mDisplayOffloader.onBlockingScreenOn(unblocker);
+ return true;
+ }
+
/**
* Start the offload session. The method returns if the session is already active.
* @return Whether the session was started successfully
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 52c53f3..6d09cc9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -126,6 +126,8 @@
// To enable these logs, run:
// 'adb shell setprop persist.log.tag.DisplayPowerController2 DEBUG && adb reboot'
private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+ private static final String SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME =
+ "Screen on blocked by displayoffload";
// If true, uses the color fade on animation.
// We might want to turn this off if we cannot get a guarantee that the screen
@@ -155,6 +157,7 @@
private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15;
private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17;
+ private static final int MSG_OFFLOADING_SCREEN_ON_UNBLOCKED = 18;
@@ -339,6 +342,7 @@
// we are waiting for a callback to release it and unblock the screen.
private ScreenOnUnblocker mPendingScreenOnUnblocker;
private ScreenOffUnblocker mPendingScreenOffUnblocker;
+ private Runnable mPendingScreenOnUnblockerByDisplayOffload;
// True if we were in the process of turning off the screen.
// This allows us to recover more gracefully from situations where we abort
@@ -348,10 +352,15 @@
// The elapsed real time when the screen on was blocked.
private long mScreenOnBlockStartRealTime;
private long mScreenOffBlockStartRealTime;
+ private long mScreenOnBlockByDisplayOffloadStartRealTime;
// Screen state we reported to policy. Must be one of REPORTED_TO_POLICY_* fields.
private int mReportedScreenStateToPolicy = REPORTED_TO_POLICY_UNREPORTED;
+ // Used to deduplicate the displayoffload blocking screen on logic. One block per turning on.
+ // This value is reset when screen on is reported or the blocking is cancelled.
+ private boolean mScreenTurningOnWasBlockedByDisplayOffload;
+
// If the last recorded screen state was dozing or not.
private boolean mDozing;
@@ -472,7 +481,7 @@
private boolean mBootCompleted;
private final DisplayManagerFlags mFlags;
- private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
+ private DisplayOffloadSession mDisplayOffloadSession;
/**
* Creates the display power controller.
@@ -772,6 +781,10 @@
@Override
public void setDisplayOffloadSession(DisplayOffloadSession session) {
+ if (session == mDisplayOffloadSession) {
+ return;
+ }
+ unblockScreenOnByDisplayOffload();
mDisplayOffloadSession = session;
}
@@ -1735,6 +1748,7 @@
// reporting the display is ready because we only need to ensure the screen is in the
// right power state even as it continues to converge on the desired brightness.
final boolean ready = mPendingScreenOnUnblocker == null
+ && mPendingScreenOnUnblockerByDisplayOffload == null
&& (!mColorFadeEnabled || (!mColorFadeOnAnimator.isStarted()
&& !mColorFadeOffAnimator.isStarted()))
&& mPowerState.waitUntilClean(mCleanListener);
@@ -1983,15 +1997,69 @@
}
}
+ private void blockScreenOnByDisplayOffload(DisplayOffloadSession displayOffloadSession) {
+ if (mPendingScreenOnUnblockerByDisplayOffload != null || displayOffloadSession == null) {
+ return;
+ }
+ mScreenTurningOnWasBlockedByDisplayOffload = true;
+
+ Trace.asyncTraceBegin(
+ Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+ mScreenOnBlockByDisplayOffloadStartRealTime = SystemClock.elapsedRealtime();
+
+ mPendingScreenOnUnblockerByDisplayOffload =
+ () -> onDisplayOffloadUnblockScreenOn(displayOffloadSession);
+ if (!displayOffloadSession.blockScreenOn(mPendingScreenOnUnblockerByDisplayOffload)) {
+ mPendingScreenOnUnblockerByDisplayOffload = null;
+ long delay =
+ SystemClock.elapsedRealtime() - mScreenOnBlockByDisplayOffloadStartRealTime;
+ Slog.w(mTag, "Tried blocking screen on for offloading but failed. So, end trace after "
+ + delay + " ms.");
+ Trace.asyncTraceEnd(
+ Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+ return;
+ }
+ Slog.i(mTag, "Blocking screen on for offloading.");
+ }
+
+ private void onDisplayOffloadUnblockScreenOn(DisplayOffloadSession displayOffloadSession) {
+ Message msg = mHandler.obtainMessage(MSG_OFFLOADING_SCREEN_ON_UNBLOCKED,
+ displayOffloadSession);
+ mHandler.sendMessage(msg);
+ }
+
+ private void unblockScreenOnByDisplayOffload() {
+ if (mPendingScreenOnUnblockerByDisplayOffload == null) {
+ return;
+ }
+ mPendingScreenOnUnblockerByDisplayOffload = null;
+ long delay = SystemClock.elapsedRealtime() - mScreenOnBlockByDisplayOffloadStartRealTime;
+ Slog.i(mTag, "Unblocked screen on for offloading after " + delay + " ms");
+ Trace.asyncTraceEnd(
+ Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+ }
+
private boolean setScreenState(int state) {
return setScreenState(state, false /*reportOnly*/);
}
private boolean setScreenState(int state, boolean reportOnly) {
final boolean isOff = (state == Display.STATE_OFF);
+ final boolean isOn = (state == Display.STATE_ON);
+ final boolean changed = mPowerState.getScreenState() != state;
- if (mPowerState.getScreenState() != state
- || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
+ // If the screen is turning on, give displayoffload a chance to do something before the
+ // screen actually turns on.
+ // TODO(b/316941732): add tests for this displayoffload screen-on blocker.
+ if (isOn && changed && !mScreenTurningOnWasBlockedByDisplayOffload) {
+ blockScreenOnByDisplayOffload(mDisplayOffloadSession);
+ } else if (!isOn && mScreenTurningOnWasBlockedByDisplayOffload) {
+ // No longer turning screen on, so unblock previous screen on blocking immediately.
+ unblockScreenOnByDisplayOffload();
+ mScreenTurningOnWasBlockedByDisplayOffload = false;
+ }
+
+ if (changed || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
// If we are trying to turn screen off, give policy a chance to do something before we
// actually turn the screen off.
if (isOff && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
@@ -2007,8 +2075,9 @@
}
}
- if (!reportOnly && mPowerState.getScreenState() != state
- && readyToUpdateDisplayState()) {
+ if (!reportOnly && changed && readyToUpdateDisplayState()
+ && mPendingScreenOffUnblocker == null
+ && mPendingScreenOnUnblockerByDisplayOffload == null) {
Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
String propertyKey = "debug.tracing.screen_state";
@@ -2060,12 +2129,16 @@
}
// Return true if the screen isn't blocked.
- return mPendingScreenOnUnblocker == null;
+ return mPendingScreenOnUnblocker == null
+ && mPendingScreenOnUnblockerByDisplayOffload == null;
}
private void setReportedScreenState(int state) {
Trace.traceCounter(Trace.TRACE_TAG_POWER, "ReportedScreenStateToPolicy", state);
mReportedScreenStateToPolicy = state;
+ if (state == REPORTED_TO_POLICY_SCREEN_ON) {
+ mScreenTurningOnWasBlockedByDisplayOffload = false;
+ }
}
private void loadAmbientLightSensor() {
@@ -2813,6 +2886,12 @@
updatePowerState();
}
break;
+ case MSG_OFFLOADING_SCREEN_ON_UNBLOCKED:
+ if (mDisplayOffloadSession == msg.obj) {
+ unblockScreenOnByDisplayOffload();
+ updatePowerState();
+ }
+ break;
case MSG_CONFIGURE_BRIGHTNESS:
BrightnessConfiguration brightnessConfiguration =
(BrightnessConfiguration) msg.obj;
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 22898a6..25576ce 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -25,6 +25,7 @@
import android.content.res.Resources;
import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.sidekick.SidekickInternal;
+import android.media.MediaDrm;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
@@ -242,6 +243,10 @@
private SurfaceControl.DisplayMode mActiveSfDisplayMode;
// The active display vsync period in SurfaceFlinger
private float mActiveRenderFrameRate;
+ // The current HDCP level supported by the display, 0 indicates unset
+ // values are defined in hardware/interfaces/drm/aidl/android/hardware/drm/HdcpLevel.aidl
+ private int mConnectedHdcpLevel;
+
private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =
new DisplayEventReceiver.FrameRateOverride[0];
@@ -675,8 +680,9 @@
mInfo.yDpi = mActiveSfDisplayMode.yDpi;
mInfo.deviceProductInfo = mStaticDisplayInfo.deviceProductInfo;
- // Assume that all built-in displays that have secure output (eg. HDCP) also
- // support compositing from gralloc protected buffers.
+ if (mConnectedHdcpLevel != 0) {
+ mStaticDisplayInfo.secure = mConnectedHdcpLevel >= MediaDrm.HDCP_V1;
+ }
if (mStaticDisplayInfo.secure) {
mInfo.flags = DisplayDeviceInfo.FLAG_SECURE
| DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
@@ -1093,6 +1099,12 @@
}
}
+ public void onHdcpLevelsChangedLocked(int connectedLevel, int maxLevel) {
+ if (updateHdcpLevelsLocked(connectedLevel, maxLevel)) {
+ updateDeviceInfoLocked();
+ }
+ }
+
public boolean updateActiveModeLocked(int activeSfModeId, float renderFrameRate) {
if (mActiveSfDisplayMode.id == activeSfModeId
&& mActiveRenderFrameRate == renderFrameRate) {
@@ -1118,6 +1130,22 @@
return true;
}
+ public boolean updateHdcpLevelsLocked(int connectedLevel, int maxLevel) {
+ if (connectedLevel > maxLevel) {
+ Slog.w(TAG, "HDCP connected level: " + connectedLevel
+ + " is larger than max level: " + maxLevel
+ + ", ignoring request.");
+ return false;
+ }
+
+ if (mConnectedHdcpLevel == connectedLevel) {
+ return false;
+ }
+
+ mConnectedHdcpLevel = connectedLevel;
+ return true;
+ }
+
public void requestColorModeLocked(int colorMode) {
if (mActiveColorMode == colorMode) {
return;
@@ -1387,6 +1415,7 @@
long renderPeriod);
void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
DisplayEventReceiver.FrameRateOverride[] overrides);
+ void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel);
}
@@ -1420,6 +1449,11 @@
DisplayEventReceiver.FrameRateOverride[] overrides) {
mListener.onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);
}
+
+ @Override
+ public void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel) {
+ mListener.onHdcpLevelsChanged(physicalDisplayId, connectedLevel, maxLevel);
+ }
}
private final class LocalDisplayEventListener implements DisplayEventListener {
@@ -1489,6 +1523,26 @@
device.onFrameRateOverridesChanged(overrides);
}
}
+
+ @Override
+ public void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel) {
+ if (DEBUG) {
+ Slog.d(TAG, "onHdcpLevelsChanged(physicalDisplayId=" + physicalDisplayId
+ + ", connectedLevel=" + connectedLevel + ", maxLevel=" + maxLevel + ")");
+ }
+ synchronized (getSyncRoot()) {
+ LocalDisplayDevice device = mDevices.get(physicalDisplayId);
+ if (device == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Received hdcp levels change for unhandled physical display: "
+ + "physicalDisplayId=" + physicalDisplayId);
+ }
+ return;
+ }
+
+ device.onHdcpLevelsChangedLocked(connectedLevel, maxLevel);
+ }
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
new file mode 100644
index 0000000..2162850
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2023 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.display.config;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.util.Spline;
+
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Provides a mapping between lux and brightness values in order to support auto-brightness.
+ */
+public class DisplayBrightnessMappingConfig {
+
+ private static final String DEFAULT_BRIGHTNESS_MAPPING_KEY = "default_normal";
+
+ /**
+ * Array of desired screen brightness in nits corresponding to the lux values
+ * in the mBrightnessLevelsLuxMap.get(DEFAULT_ID) array. The display brightness is defined as
+ * the measured brightness of an all-white image. The brightness values must be non-negative and
+ * non-decreasing. This must be overridden in platform specific overlays
+ */
+ private float[] mBrightnessLevelsNits;
+
+ /**
+ * Map of arrays of desired screen brightness corresponding to the lux values
+ * in mBrightnessLevelsLuxMap, indexed by the auto-brightness mode and the brightness setting.
+ * The brightness values must be non-negative and non-decreasing. They must be between
+ * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+ *
+ * The keys are a concatenation of the auto-brightness mode and the brightness setting
+ * separated by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal,
+ * doze_dim, doze_bright.
+ */
+ private final Map<String, float[]> mBrightnessLevelsMap = new HashMap<>();
+
+ /**
+ * Map of arrays of light sensor lux values to define our levels for auto-brightness support,
+ * indexed by the auto-brightness mode and the brightness setting.
+ *
+ * The first lux value in every array is always 0.
+ *
+ * The control points must be strictly increasing. Each control point corresponds to an entry
+ * in the brightness values arrays. For example, if lux == luxLevels[1] (second element
+ * of the levels array) then the brightness will be determined by brightnessLevels[1] (second
+ * element of the brightness values array).
+ *
+ * Spline interpolation is used to determine the auto-brightness values for lux levels between
+ * these control points.
+ *
+ * The keys are a concatenation of the auto-brightness mode and the brightness setting
+ * separated by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal,
+ * doze_dim, doze_bright.
+ */
+ private final Map<String, float[]> mBrightnessLevelsLuxMap = new HashMap<>();
+
+ /**
+ * Loads the auto-brightness display brightness mappings. Internally, this takes care of
+ * loading the value from the display config, and if not present, falls back to config.xml.
+ */
+ public DisplayBrightnessMappingConfig(Context context, DisplayManagerFlags flags,
+ AutoBrightness autoBrightnessConfig, Spline backlightToBrightnessSpline) {
+ if (flags.areAutoBrightnessModesEnabled() && autoBrightnessConfig != null
+ && autoBrightnessConfig.getLuxToBrightnessMapping() != null
+ && autoBrightnessConfig.getLuxToBrightnessMapping().size() > 0) {
+ for (LuxToBrightnessMapping mapping
+ : autoBrightnessConfig.getLuxToBrightnessMapping()) {
+ final int size = mapping.getMap().getPoint().size();
+ float[] brightnessLevels = new float[size];
+ float[] brightnessLevelsLux = new float[size];
+ for (int i = 0; i < size; i++) {
+ float backlight = mapping.getMap().getPoint().get(i).getSecond().floatValue();
+ brightnessLevels[i] = backlightToBrightnessSpline.interpolate(backlight);
+ brightnessLevelsLux[i] = mapping.getMap().getPoint().get(i).getFirst()
+ .floatValue();
+ }
+ if (size == 0) {
+ throw new IllegalArgumentException(
+ "A display brightness mapping should not be empty");
+ }
+ if (brightnessLevelsLux[0] != 0) {
+ throw new IllegalArgumentException(
+ "The first lux value in the display brightness mapping must be 0");
+ }
+
+ String key = (mapping.getMode() == null ? "default" : mapping.getMode()) + "_"
+ + (mapping.getSetting() == null ? "normal" : mapping.getSetting());
+ if (mBrightnessLevelsMap.containsKey(key)
+ || mBrightnessLevelsLuxMap.containsKey(key)) {
+ throw new IllegalArgumentException(
+ "A display brightness mapping with key " + key + " already exists");
+ }
+ mBrightnessLevelsMap.put(key, brightnessLevels);
+ mBrightnessLevelsLuxMap.put(key, brightnessLevelsLux);
+ }
+ }
+
+ if (!mBrightnessLevelsMap.containsKey(DEFAULT_BRIGHTNESS_MAPPING_KEY)
+ || !mBrightnessLevelsLuxMap.containsKey(DEFAULT_BRIGHTNESS_MAPPING_KEY)) {
+ mBrightnessLevelsNits = DisplayDeviceConfig.getFloatArray(context.getResources()
+ .obtainTypedArray(com.android.internal.R.array
+ .config_autoBrightnessDisplayValuesNits), PowerManager
+ .BRIGHTNESS_OFF_FLOAT);
+
+ float[] brightnessLevelsLux = DisplayDeviceConfig.getLuxLevels(context.getResources()
+ .getIntArray(com.android.internal.R.array
+ .config_autoBrightnessLevels));
+ mBrightnessLevelsLuxMap.put(DEFAULT_BRIGHTNESS_MAPPING_KEY, brightnessLevelsLux);
+
+ // Load the old configuration in the range [0, 255]. The values need to be normalized
+ // to the range [0, 1].
+ int[] brightnessLevels = context.getResources().getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
+ mBrightnessLevelsMap.put(DEFAULT_BRIGHTNESS_MAPPING_KEY,
+ brightnessArrayIntToFloat(brightnessLevels, backlightToBrightnessSpline));
+ }
+ }
+
+ /**
+ * @return The default auto-brightness brightening ambient lux levels
+ */
+ public float[] getLuxArray() {
+ return mBrightnessLevelsLuxMap.get(DEFAULT_BRIGHTNESS_MAPPING_KEY);
+ }
+
+ /**
+ * @param mode The auto-brightness mode
+ * @param setting The brightness setting
+ * @return Auto brightness brightening ambient lux levels for the specified mode and setting
+ */
+ public float[] getLuxArray(String mode, String setting) {
+ return mBrightnessLevelsLuxMap.get(mode + "_" + setting);
+ }
+
+ /**
+ * @return Auto brightness brightening nits levels
+ */
+ public float[] getNitsArray() {
+ return mBrightnessLevelsNits;
+ }
+
+ /**
+ * @return The default auto-brightness brightening levels
+ */
+ public float[] getBrightnessArray() {
+ return mBrightnessLevelsMap.get(DEFAULT_BRIGHTNESS_MAPPING_KEY);
+ }
+
+ /**
+ * @param mode The auto-brightness mode
+ * @param setting The brightness setting
+ * @return Auto brightness brightening ambient lux levels for the specified mode and setting
+ */
+ public float[] getBrightnessArray(String mode, String setting) {
+ return mBrightnessLevelsMap.get(mode + "_" + setting);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder brightnessLevelsLuxMapString = new StringBuilder("{");
+ for (Map.Entry<String, float[]> entry : mBrightnessLevelsLuxMap.entrySet()) {
+ brightnessLevelsLuxMapString.append(entry.getKey()).append("=").append(
+ Arrays.toString(entry.getValue())).append(", ");
+ }
+ if (brightnessLevelsLuxMapString.length() > 2) {
+ brightnessLevelsLuxMapString.delete(brightnessLevelsLuxMapString.length() - 2,
+ brightnessLevelsLuxMapString.length());
+ }
+ brightnessLevelsLuxMapString.append("}");
+
+ StringBuilder brightnessLevelsMapString = new StringBuilder("{");
+ for (Map.Entry<String, float[]> entry : mBrightnessLevelsMap.entrySet()) {
+ brightnessLevelsMapString.append(entry.getKey()).append("=").append(
+ Arrays.toString(entry.getValue())).append(", ");
+ }
+ if (brightnessLevelsMapString.length() > 2) {
+ brightnessLevelsMapString.delete(brightnessLevelsMapString.length() - 2,
+ brightnessLevelsMapString.length());
+ }
+ brightnessLevelsMapString.append("}");
+
+ return "mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+ + ", mBrightnessLevelsLuxMap= " + brightnessLevelsLuxMapString
+ + ", mBrightnessLevelsMap= " + brightnessLevelsMapString;
+ }
+
+ private float[] brightnessArrayIntToFloat(int[] brightnessInt,
+ Spline backlightToBrightnessSpline) {
+ float[] brightnessFloat = new float[brightnessInt.length];
+ for (int i = 0; i < brightnessInt.length; i++) {
+ brightnessFloat[i] = backlightToBrightnessSpline.interpolate(
+ BrightnessSynchronizer.brightnessIntToFloat(brightnessInt[i]));
+ }
+ return brightnessFloat;
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 4089a81..dda50ca 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -167,13 +167,17 @@
/**
* Indicates that the IME window has re-parented to the new target when the IME control changed.
+ *
+ * @param displayId the display hosting the IME window
*/
- public abstract void onImeParentChanged();
+ public abstract void onImeParentChanged(int displayId);
/**
- * Destroys the IME surface.
+ * Destroys the IME surface for the given display.
+ *
+ * @param displayId the display hosting the IME window
*/
- public abstract void removeImeSurface();
+ public abstract void removeImeSurface(int displayId);
/**
* Updates the IME visibility, back disposition and show IME picker status for SystemUI.
@@ -298,11 +302,11 @@
}
@Override
- public void onImeParentChanged() {
+ public void onImeParentChanged(int displayId) {
}
@Override
- public void removeImeSurface() {
+ public void removeImeSurface(int displayId) {
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 16e043c..0d29b7d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5671,7 +5671,7 @@
}
@Override
- public void onImeParentChanged() {
+ public void onImeParentChanged(int displayId) {
synchronized (ImfLock.class) {
// Hide the IME method menu only when the IME surface parent is changed by the
// input target changed, in case seeing the dialog dismiss flickering during
@@ -5683,7 +5683,7 @@
}
@Override
- public void removeImeSurface() {
+ public void removeImeSurface(int displayId) {
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
}
diff --git a/services/core/java/com/android/server/location/altitude/AltitudeService.java b/services/core/java/com/android/server/location/altitude/AltitudeService.java
index b321e4d..97bf354 100644
--- a/services/core/java/com/android/server/location/altitude/AltitudeService.java
+++ b/services/core/java/com/android/server/location/altitude/AltitudeService.java
@@ -19,6 +19,8 @@
import android.content.Context;
import android.frameworks.location.altitude.AddMslAltitudeToLocationRequest;
import android.frameworks.location.altitude.AddMslAltitudeToLocationResponse;
+import android.frameworks.location.altitude.GetGeoidHeightRequest;
+import android.frameworks.location.altitude.GetGeoidHeightResponse;
import android.frameworks.location.altitude.IAltitudeService;
import android.location.Location;
import android.location.altitude.AltitudeConverter;
@@ -52,15 +54,47 @@
location.setLongitude(request.longitudeDegrees);
location.setAltitude(request.altitudeMeters);
location.setVerticalAccuracyMeters(request.verticalAccuracyMeters);
+
+ AddMslAltitudeToLocationResponse response = new AddMslAltitudeToLocationResponse();
try {
mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
} catch (IOException e) {
- throw new RemoteException(e);
+ response.success = false;
+ return response;
}
-
- AddMslAltitudeToLocationResponse response = new AddMslAltitudeToLocationResponse();
response.mslAltitudeMeters = location.getMslAltitudeMeters();
response.mslAltitudeAccuracyMeters = location.getMslAltitudeAccuracyMeters();
+ response.success = true;
+ return response;
+ }
+
+ @Override
+ public GetGeoidHeightResponse getGeoidHeight(GetGeoidHeightRequest request)
+ throws RemoteException {
+ Location location = new Location("");
+ location.setLatitude(request.latitudeDegrees);
+ location.setLongitude(request.longitudeDegrees);
+ location.setAltitude(0.0);
+ location.setVerticalAccuracyMeters(0.0f);
+
+ GetGeoidHeightResponse response = new GetGeoidHeightResponse();
+ try {
+ mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
+ } catch (IOException e) {
+ response.success = false;
+ return response;
+ }
+ // The geoid height for a location with zero WGS84 altitude is equal in value to the
+ // negative of the corresponding MSL altitude.
+ response.geoidHeightMeters = -location.getMslAltitudeMeters();
+ // The geoid height error for a location with zero vertical accuracy is equal in value to
+ // the corresponding MSL altitude accuracy.
+ response.geoidHeightErrorMeters = location.getMslAltitudeAccuracyMeters();
+ // The expiration distance and additional error are currently set to constants used by
+ // health services.
+ response.expirationDistanceMeters = 10000.0;
+ response.additionalGeoidHeightErrorMeters = 0.707f;
+ response.success = true;
return response;
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 78c8cde..403b421 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -19,6 +19,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import android.content.Context;
+import android.location.flags.Flags;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -48,6 +49,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* Handles network connection requests and network state change updates for AGPS data download.
@@ -91,6 +93,10 @@
// network with SUPL connectivity or report an error.
private static final int SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS = 20 * 1000;
+ // If the chipset does not request to release a SUPL connection before the specified timeout in
+ // milliseconds, the connection will be automatically released.
+ private static final long SUPL_CONNECTION_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(1);
+
private static final int HASH_MAP_INITIAL_CAPACITY_TO_TRACK_CONNECTED_NETWORKS = 5;
// Keeps track of networks and their state as notified by the network request callbacks.
@@ -121,6 +127,8 @@
private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
private final PowerManager.WakeLock mWakeLock;
+ private final Object mSuplConnectionReleaseOnTimeoutToken = new Object();
+
/**
* Network attributes needed when updating HAL about network connectivity status changes.
*/
@@ -609,6 +617,13 @@
mSuplConnectivityCallback,
mHandler,
SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS);
+ if (Flags.releaseSuplConnectionOnTimeout()) {
+ // Schedule to release the SUPL connection after timeout
+ mHandler.removeCallbacksAndMessages(mSuplConnectionReleaseOnTimeoutToken);
+ mHandler.postDelayed(() -> handleReleaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN),
+ mSuplConnectionReleaseOnTimeoutToken,
+ SUPL_CONNECTION_TIMEOUT_MILLIS);
+ }
} catch (RuntimeException e) {
Log.e(TAG, "Failed to request network.", e);
mSuplConnectivityCallback = null;
@@ -639,6 +654,10 @@
Log.d(TAG, message);
}
+ if (Flags.releaseSuplConnectionOnTimeout()) {
+ // Remove pending task to avoid releasing an incorrect connection
+ mHandler.removeCallbacksAndMessages(mSuplConnectionReleaseOnTimeoutToken);
+ }
if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_CLOSED) {
return;
}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 2da1a68..66e61c0 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -234,7 +234,7 @@
if (pkgList != null && (pkgList.length > 0)) {
for (String pkgName : pkgList) {
try {
- inm.removeAutomaticZenRules(pkgName);
+ inm.removeAutomaticZenRules(pkgName, /* fromUser= */ false);
inm.setNotificationPolicyAccessGranted(pkgName, false);
} catch (Exception e) {
Slog.e(TAG, "Failed to clean up rules for " + pkgName, e);
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index 8855666..71a6b5e 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -109,7 +109,6 @@
if (origin == ZenModeConfig.UPDATE_ORIGIN_INIT
|| origin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER
|| origin == ZenModeConfig.UPDATE_ORIGIN_USER
- || origin == ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
|| !mPowerManager.isInteractive()) {
unregisterScreenOffReceiver();
updateNightModeImmediately(useNightMode);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 75d3dce..135a467 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5343,13 +5343,14 @@
}
@Override
- public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
+ public void setZenMode(int mode, Uri conditionId, String reason, boolean fromUser) {
enforceSystemOrSystemUI("INotificationManager.setZenMode");
final int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
+ enforceUserOriginOnlyFromSystem(fromUser, "setZenMode");
+
try {
- mZenModeHelper.setManualZenMode(mode, conditionId,
- ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, // Checked by enforce()
+ mZenModeHelper.setManualZenMode(mode, conditionId, computeZenOrigin(fromUser),
reason, /* caller= */ null, callingUid);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -5380,7 +5381,8 @@
}
@Override
- public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String pkg) {
+ public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String pkg,
+ boolean fromUser) {
validateAutomaticZenRule(automaticZenRule);
checkCallerIsSameApp(pkg);
if (automaticZenRule.getZenPolicy() != null
@@ -5389,6 +5391,7 @@
+ "INTERRUPTION_FILTER_PRIORITY filters");
}
enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
+ enforceUserOriginOnlyFromSystem(fromUser, "addAutomaticZenRule");
// If the calling app is the system (from any user), take the package name from the
// rule's owner rather than from the caller's package.
@@ -5400,24 +5403,18 @@
}
return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
- // TODO: b/308670715: Distinguish origin properly (e.g. USER if creating a rule
- // manually in Settings).
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
- "addAutomaticZenRule", Binder.getCallingUid());
+ computeZenOrigin(fromUser), "addAutomaticZenRule", Binder.getCallingUid());
}
@Override
- public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule) {
+ public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule,
+ boolean fromUser) throws RemoteException {
validateAutomaticZenRule(automaticZenRule);
enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule");
+ enforceUserOriginOnlyFromSystem(fromUser, "updateAutomaticZenRule");
- // TODO: b/308670715: Distinguish origin properly (e.g. USER if updating a rule
- // manually in Settings).
return mZenModeHelper.updateAutomaticZenRule(id, automaticZenRule,
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
- "updateAutomaticZenRule", Binder.getCallingUid());
+ computeZenOrigin(fromUser), "updateAutomaticZenRule", Binder.getCallingUid());
}
private void validateAutomaticZenRule(AutomaticZenRule rule) {
@@ -5445,27 +5442,24 @@
}
@Override
- public boolean removeAutomaticZenRule(String id) throws RemoteException {
+ public boolean removeAutomaticZenRule(String id, boolean fromUser) throws RemoteException {
Objects.requireNonNull(id, "Id is null");
// Verify that they can modify zen rules.
enforcePolicyAccess(Binder.getCallingUid(), "removeAutomaticZenRule");
+ enforceUserOriginOnlyFromSystem(fromUser, "removeAutomaticZenRule");
- // TODO: b/308670715: Distinguish origin properly (e.g. USER if removing a rule
- // manually in Settings).
- return mZenModeHelper.removeAutomaticZenRule(id,
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ return mZenModeHelper.removeAutomaticZenRule(id, computeZenOrigin(fromUser),
"removeAutomaticZenRule", Binder.getCallingUid());
}
@Override
- public boolean removeAutomaticZenRules(String packageName) throws RemoteException {
+ public boolean removeAutomaticZenRules(String packageName, boolean fromUser)
+ throws RemoteException {
Objects.requireNonNull(packageName, "Package name is null");
enforceSystemOrSystemUI("removeAutomaticZenRules");
+ enforceUserOriginOnlyFromSystem(fromUser, "removeAutomaticZenRules");
- return mZenModeHelper.removeAutomaticZenRules(packageName,
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ return mZenModeHelper.removeAutomaticZenRules(packageName, computeZenOrigin(fromUser),
packageName + "|removeAutomaticZenRules", Binder.getCallingUid());
}
@@ -5478,28 +5472,54 @@
}
@Override
- public void setAutomaticZenRuleState(String id, Condition condition) {
+ public void setAutomaticZenRuleState(String id, Condition condition, boolean fromUser) {
Objects.requireNonNull(id, "id is null");
Objects.requireNonNull(condition, "Condition is null");
condition.validate();
enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
- // TODO: b/308670715: Distinguish origin properly (e.g. USER if toggling a rule
- // manually in Settings).
- mZenModeHelper.setAutomaticZenRuleState(id, condition,
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ if (android.app.Flags.modesApi()) {
+ if (fromUser != (condition.source == Condition.SOURCE_USER_ACTION)) {
+ throw new IllegalArgumentException(String.format(
+ "Mismatch between fromUser (%s) and condition.source (%s)",
+ fromUser, Condition.sourceToString(condition.source)));
+ }
+ }
+
+ mZenModeHelper.setAutomaticZenRuleState(id, condition, computeZenOrigin(fromUser),
Binder.getCallingUid());
}
+ @ZenModeConfig.ConfigChangeOrigin
+ private int computeZenOrigin(boolean fromUser) {
+ // "fromUser" is introduced with MODES_API, so only consider it in that case.
+ // (Non-MODES_API behavior should also not depend at all on UPDATE_ORIGIN_USER).
+ if (android.app.Flags.modesApi() && fromUser) {
+ return ZenModeConfig.UPDATE_ORIGIN_USER;
+ } else if (isCallerSystemOrSystemUi()) {
+ return ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
+ } else {
+ return ZenModeConfig.UPDATE_ORIGIN_APP;
+ }
+ }
+
+ private void enforceUserOriginOnlyFromSystem(boolean fromUser, String method) {
+ if (android.app.Flags.modesApi()
+ && fromUser
+ && !isCallerSystemOrSystemUiOrShell()) {
+ throw new SecurityException(String.format(
+ "Calling %s with fromUser == true is only allowed for system", method));
+ }
+ }
+
@Override
- public void setInterruptionFilter(String pkg, int filter) throws RemoteException {
+ public void setInterruptionFilter(String pkg, int filter, boolean fromUser) {
enforcePolicyAccess(pkg, "setInterruptionFilter");
final int zen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
if (zen == -1) throw new IllegalArgumentException("Invalid filter: " + filter);
final int callingUid = Binder.getCallingUid();
- final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
+ enforceUserOriginOnlyFromSystem(fromUser, "setInterruptionFilter");
if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) {
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, callingUid, zen);
@@ -5508,9 +5528,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- mZenModeHelper.setManualZenMode(zen, null,
- isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ mZenModeHelper.setManualZenMode(zen, null, computeZenOrigin(fromUser),
/* reason= */ "setInterruptionFilter", /* caller= */ pkg,
callingUid);
} finally {
@@ -5825,10 +5843,11 @@
* {@link Policy#PRIORITY_CATEGORY_MEDIA} from bypassing dnd
*/
@Override
- public void setNotificationPolicy(String pkg, Policy policy) {
+ public void setNotificationPolicy(String pkg, Policy policy, boolean fromUser) {
enforcePolicyAccess(pkg, "setNotificationPolicy");
+ enforceUserOriginOnlyFromSystem(fromUser, "setNotificationPolicy");
int callingUid = Binder.getCallingUid();
- boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
+ @ZenModeConfig.ConfigChangeOrigin int origin = computeZenOrigin(fromUser);
boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi()
&& !canManageGlobalZenPolicy(pkg, callingUid);
@@ -5873,14 +5892,12 @@
newVisualEffects, policy.priorityConversationSenders);
if (shouldApplyAsImplicitRule) {
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy,
+ origin);
} else {
ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion,
policy);
- mZenModeHelper.setNotificationPolicy(policy,
- isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
- callingUid);
+ mZenModeHelper.setNotificationPolicy(policy, origin, callingUid);
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to set notification policy", e);
@@ -7001,12 +7018,14 @@
return false;
}
- final boolean hasBitmap = n.extras.containsKey(Notification.EXTRA_PICTURE);
+ final boolean hasBitmap = n.extras.containsKey(Notification.EXTRA_PICTURE)
+ && n.extras.getParcelable(Notification.EXTRA_PICTURE) != null;
if (hasBitmap) {
return true;
}
- final boolean hasIcon = n.extras.containsKey(Notification.EXTRA_PICTURE_ICON);
+ final boolean hasIcon = n.extras.containsKey(Notification.EXTRA_PICTURE_ICON)
+ && n.extras.getParcelable(Notification.EXTRA_PICTURE_ICON) != null;
if (hasIcon) {
return true;
}
@@ -7022,9 +7041,10 @@
if (!isBigPictureWithBitmapOrIcon(r.getNotification())) {
return;
}
- // Remove Notification object's reference to picture bitmap or URI
- r.getNotification().extras.remove(Notification.EXTRA_PICTURE);
- r.getNotification().extras.remove(Notification.EXTRA_PICTURE_ICON);
+ // Remove Notification object's reference to picture bitmap or URI. Leave the extras set to
+ // null to avoid crashing apps that came to expect them to be present but null.
+ r.getNotification().extras.putParcelable(Notification.EXTRA_PICTURE, null);
+ r.getNotification().extras.putParcelable(Notification.EXTRA_PICTURE_ICON, null);
// Make Notification silent
r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index dc0cf4e..9f3104c 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -117,7 +117,6 @@
private final NotificationManagerService mDirectService;
private final INotificationManager mBinderService;
private final PackageManager mPm;
- private NotificationChannel mChannel;
public NotificationShellCmd(NotificationManagerService service) {
mDirectService = service;
@@ -183,7 +182,13 @@
interruptionFilter = INTERRUPTION_FILTER_ALL;
}
final int filter = interruptionFilter;
- mBinderService.setInterruptionFilter(callingPackage, filter);
+ if (android.app.Flags.modesApi()) {
+ mBinderService.setInterruptionFilter(callingPackage, filter,
+ /* fromUser= */ true);
+ } else {
+ mBinderService.setInterruptionFilter(callingPackage, filter,
+ /* fromUser= */ false);
+ }
}
break;
case "allow_dnd": {
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index 87158cd..df570a0 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -29,6 +29,7 @@
import android.os.Process;
import android.service.notification.DNDPolicyProto;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
import android.service.notification.ZenModeDiff;
import android.service.notification.ZenPolicy;
import android.util.ArrayMap;
@@ -58,7 +59,7 @@
// mode change.
ZenModeEventLogger.ZenStateChanges mChangeState = new ZenModeEventLogger.ZenStateChanges();
- private PackageManager mPm;
+ private final PackageManager mPm;
ZenModeEventLogger(PackageManager pm) {
mPm = pm;
@@ -97,11 +98,11 @@
* @param newInfo ZenModeInfo after this change takes effect
* @param callingUid the calling UID associated with the change; may be used to attribute the
* change to a particular package or determine if this is a user action
- * @param fromSystemOrSystemUi whether the calling UID is either system UID or system UI
+ * @param origin The origin of the Zen change.
*/
public final void maybeLogZenChange(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid,
- boolean fromSystemOrSystemUi) {
- mChangeState.init(prevInfo, newInfo, callingUid, fromSystemOrSystemUi);
+ @ConfigChangeOrigin int origin) {
+ mChangeState.init(prevInfo, newInfo, callingUid, origin);
if (mChangeState.shouldLogChanges()) {
maybeReassignCallingUid();
logChanges();
@@ -124,7 +125,7 @@
// We don't consider the manual rule in the old config because if a manual rule is turning
// off with a call from system, that could easily be a user action to explicitly turn it off
if (mChangeState.getChangedRuleType() == RULE_TYPE_MANUAL) {
- if (!mChangeState.mFromSystemOrSystemUi
+ if (!mChangeState.isFromSystemOrSystemUi()
|| mChangeState.getNewManualRuleEnabler() == null) {
return;
}
@@ -136,7 +137,7 @@
// - we've determined it's not a user action
// - our current best guess is that the calling uid is system/sysui
if (mChangeState.getChangedRuleType() == RULE_TYPE_AUTOMATIC) {
- if (mChangeState.getIsUserAction() || !mChangeState.mFromSystemOrSystemUi) {
+ if (mChangeState.getIsUserAction() || !mChangeState.isFromSystemOrSystemUi()) {
return;
}
@@ -221,10 +222,10 @@
ZenModeConfig mPrevConfig, mNewConfig;
NotificationManager.Policy mPrevPolicy, mNewPolicy;
int mCallingUid = Process.INVALID_UID;
- boolean mFromSystemOrSystemUi = false;
+ @ConfigChangeOrigin int mOrigin = ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
private void init(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid,
- boolean fromSystemOrSystemUi) {
+ @ConfigChangeOrigin int origin) {
// previous & new may be the same -- that would indicate that zen mode hasn't changed.
mPrevZenMode = prevInfo.mZenMode;
mNewZenMode = newInfo.mZenMode;
@@ -233,7 +234,7 @@
mPrevPolicy = prevInfo.mPolicy;
mNewPolicy = newInfo.mPolicy;
mCallingUid = callingUid;
- mFromSystemOrSystemUi = fromSystemOrSystemUi;
+ mOrigin = origin;
}
/**
@@ -389,12 +390,16 @@
/**
* Return our best guess as to whether the changes observed are due to a user action.
- * Note that this won't be 100% accurate as we can't necessarily distinguish between a
- * system uid call indicating "user interacted with Settings" vs "a system app changed
- * something automatically".
+ * Note that this (before {@code MODES_API}) won't be 100% accurate as we can't necessarily
+ * distinguish between a system uid call indicating "user interacted with Settings" vs "a
+ * system app changed something automatically".
*/
boolean getIsUserAction() {
- // Approach:
+ if (Flags.modesApi()) {
+ return mOrigin == ZenModeConfig.UPDATE_ORIGIN_USER;
+ }
+
+ // Approach for pre-MODES_API:
// - if manual rule turned on or off, the calling UID is system, and the new manual
// rule does not have an enabler set, guess that this is likely to be a user action.
// This may represent a system app turning on DND automatically, but we guess "user"
@@ -419,13 +424,13 @@
switch (getChangedRuleType()) {
case RULE_TYPE_MANUAL:
// TODO(b/278888961): Distinguish the automatically-turned-off state
- return mFromSystemOrSystemUi && (getNewManualRuleEnabler() == null);
+ return isFromSystemOrSystemUi() && (getNewManualRuleEnabler() == null);
case RULE_TYPE_AUTOMATIC:
for (ZenModeDiff.RuleDiff d : getChangedAutomaticRules().values()) {
if (d.wasAdded() || d.wasRemoved()) {
// If the change comes from system, a rule being added/removed indicates
// a likely user action. From an app, it's harder to know for sure.
- return mFromSystemOrSystemUi;
+ return isFromSystemOrSystemUi();
}
ZenModeDiff.FieldDiff enabled = d.getDiffForField(
ZenModeDiff.RuleDiff.FIELD_ENABLED);
@@ -455,6 +460,13 @@
return false;
}
+ boolean isFromSystemOrSystemUi() {
+ return mOrigin == ZenModeConfig.UPDATE_ORIGIN_INIT
+ || mOrigin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER
+ || mOrigin == ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ || mOrigin == ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP;
+ }
+
/**
* Get the package UID associated with this change, which is just the calling UID for the
* relevant method changes. This may get reset by ZenModeEventLogger, which has access to
@@ -612,7 +624,7 @@
copy.mPrevPolicy = mPrevPolicy.copy();
copy.mNewPolicy = mNewPolicy.copy();
copy.mCallingUid = mCallingUid;
- copy.mFromSystemOrSystemUi = mFromSystemOrSystemUi;
+ copy.mOrigin = mOrigin;
return copy;
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 3f8b595..0a46901 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -561,7 +561,7 @@
* {@link Global#ZEN_MODE_IMPORTANT_INTERRUPTIONS}.
*/
void applyGlobalPolicyAsImplicitZenRule(String callingPkg, int callingUid,
- NotificationManager.Policy policy) {
+ NotificationManager.Policy policy, @ConfigChangeOrigin int origin) {
if (!android.app.Flags.modesApi()) {
Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!");
return;
@@ -579,7 +579,7 @@
}
// TODO: b/308673679 - Keep user customization of this rule!
rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
- setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+ setConfigLocked(newConfig, /* triggeringComponent= */ null, origin,
"applyGlobalPolicyAsImplicitZenRule", callingUid);
}
}
@@ -1371,12 +1371,8 @@
if (logZenModeEvents) {
ZenModeEventLogger.ZenModeInfo newInfo = new ZenModeEventLogger.ZenModeInfo(
mZenMode, mConfig, mConsolidatedPolicy);
- boolean fromSystemOrSystemUi = origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- || origin == UPDATE_ORIGIN_INIT
- || origin == UPDATE_ORIGIN_INIT_USER
- || origin == UPDATE_ORIGIN_RESTORE_BACKUP;
mZenModeEventLogger.maybeLogZenChange(prevInfo, newInfo, callingUid,
- fromSystemOrSystemUi);
+ origin);
}
}
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index 9ce3cb3..f6e7ef3 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -41,13 +41,14 @@
import android.system.StructStat;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoParseException;
import com.android.internal.annotations.GuardedBy;
import com.android.server.BootReceiver;
import com.android.server.ServiceThread;
import com.android.server.os.TombstoneProtos.Cause;
import com.android.server.os.TombstoneProtos.Tombstone;
-import com.android.server.os.protobuf.CodedInputStream;
import libcore.io.IoUtils;
@@ -129,21 +130,18 @@
return;
}
+ String processName = "UNKNOWN";
final boolean isProtoFile = filename.endsWith(".pb");
- if (!isProtoFile) {
- return;
- }
+ File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
- Optional<ParsedTombstone> parsedTombstone = handleProtoTombstone(path, true);
+ Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
if (parsedTombstone.isPresent()) {
- BootReceiver.addTombstoneToDropBox(
- mContext, path, parsedTombstone.get().getTombstone(),
- parsedTombstone.get().getProcessName(), mTmpFileLock);
+ processName = parsedTombstone.get().getProcessName();
}
+ BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
}
- private Optional<ParsedTombstone> handleProtoTombstone(
- File path, boolean addToList) {
+ private Optional<TombstoneFile> handleProtoTombstone(File path, boolean addToList) {
final String filename = path.getName();
if (!filename.endsWith(".pb")) {
Slog.w(TAG, "unexpected tombstone name: " + path);
@@ -173,7 +171,7 @@
return Optional.empty();
}
- final Optional<ParsedTombstone> parsedTombstone = TombstoneFile.parse(pfd);
+ final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd);
if (!parsedTombstone.isPresent()) {
IoUtils.closeQuietly(pfd);
return Optional.empty();
@@ -186,7 +184,7 @@
previous.dispose();
}
- mTombstones.put(number, parsedTombstone.get().getTombstoneFile());
+ mTombstones.put(number, parsedTombstone.get());
}
}
@@ -334,27 +332,6 @@
}
}
- static class ParsedTombstone {
- TombstoneFile mTombstoneFile;
- Tombstone mTombstone;
- ParsedTombstone(TombstoneFile tombstoneFile, Tombstone tombstone) {
- mTombstoneFile = tombstoneFile;
- mTombstone = tombstone;
- }
-
- public String getProcessName() {
- return mTombstoneFile.getProcessName();
- }
-
- public TombstoneFile getTombstoneFile() {
- return mTombstoneFile;
- }
-
- public Tombstone getTombstone() {
- return mTombstone;
- }
- }
-
static class TombstoneFile {
final ParcelFileDescriptor mPfd;
@@ -437,21 +414,67 @@
}
}
- static Optional<ParsedTombstone> parse(ParcelFileDescriptor pfd) {
- Tombstone tombstoneProto;
- try (FileInputStream is = new FileInputStream(pfd.getFileDescriptor())) {
- final byte[] tombstoneBytes = is.readAllBytes();
+ static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
+ final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
+ final ProtoInputStream stream = new ProtoInputStream(is);
- tombstoneProto = Tombstone.parseFrom(
- CodedInputStream.newInstance(tombstoneBytes));
- } catch (IOException ex) {
+ int pid = 0;
+ int uid = 0;
+ String processName = null;
+ String crashReason = "";
+ String selinuxLabel = "";
+
+ try {
+ while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (stream.getFieldNumber()) {
+ case (int) Tombstone.PID:
+ pid = stream.readInt(Tombstone.PID);
+ break;
+
+ case (int) Tombstone.UID:
+ uid = stream.readInt(Tombstone.UID);
+ break;
+
+ case (int) Tombstone.COMMAND_LINE:
+ if (processName == null) {
+ processName = stream.readString(Tombstone.COMMAND_LINE);
+ }
+ break;
+
+ case (int) Tombstone.CAUSES:
+ if (!crashReason.equals("")) {
+ // Causes appear in decreasing order of likelihood. For now we only
+ // want the most likely crash reason here, so ignore all others.
+ break;
+ }
+ long token = stream.start(Tombstone.CAUSES);
+ cause:
+ while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (stream.getFieldNumber()) {
+ case (int) Cause.HUMAN_READABLE:
+ crashReason = stream.readString(Cause.HUMAN_READABLE);
+ break cause;
+
+ default:
+ break;
+ }
+ }
+ stream.end(token);
+ break;
+
+ case (int) Tombstone.SELINUX_LABEL:
+ selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
+ break;
+
+ default:
+ break;
+ }
+ }
+ } catch (IOException | ProtoParseException ex) {
Slog.e(TAG, "Failed to parse tombstone", ex);
return Optional.empty();
}
- int pid = tombstoneProto.getPid();
- int uid = tombstoneProto.getUid();
-
if (!UserHandle.isApp(uid)) {
Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring");
return Optional.empty();
@@ -468,7 +491,6 @@
final int userId = UserHandle.getUserId(uid);
final int appId = UserHandle.getAppId(uid);
- String selinuxLabel = tombstoneProto.getSelinuxLabel();
if (!selinuxLabel.startsWith("u:r:untrusted_app")) {
Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring");
return Optional.empty();
@@ -480,30 +502,11 @@
result.mAppId = appId;
result.mPid = pid;
result.mUid = uid;
- result.mProcessName = getCmdLineProcessName(tombstoneProto);
+ result.mProcessName = processName == null ? "" : processName;
result.mTimestampMs = timestampMs;
- result.mCrashReason = getCrashReason(tombstoneProto);
+ result.mCrashReason = crashReason;
- return Optional.of(new ParsedTombstone(result, tombstoneProto));
- }
-
- private static String getCmdLineProcessName(Tombstone tombstoneProto) {
- for (String cmdline : tombstoneProto.getCommandLineList()) {
- if (cmdline != null) {
- return cmdline;
- }
- }
- return "";
- }
-
- private static String getCrashReason(Tombstone tombstoneProto) {
- for (Cause cause : tombstoneProto.getCausesList()) {
- if (cause.getHumanReadable() != null
- && !cause.getHumanReadable().equals("")) {
- return cause.getHumanReadable();
- }
- }
- return "";
+ return Optional.of(result);
}
public IParcelFileDescriptorRetriever getPfdRetriever() {
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 82622d9..f3df424 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -1042,7 +1042,9 @@
existingSettings, forceQueryable, protectedBroadcasts);
SparseSetArray<Integer> queriesViaComponent = computer.execute();
synchronized (mQueriesViaComponentLock) {
- mQueriesViaComponent.copyFrom(queriesViaComponent);
+ mQueriesViaComponent = new WatchedSparseSetArray<>(queriesViaComponent);
+ mQueriesViaComponentSnapshot = new SnapshotCache.Auto<>(
+ mQueriesViaComponent, mQueriesViaComponent, "AppsFilter.mQueriesViaComponent");
}
mQueriesViaComponentRequireRecompute.set(false);
diff --git a/services/core/java/com/android/server/pm/CleanUpArgs.java b/services/core/java/com/android/server/pm/CleanUpArgs.java
new file mode 100644
index 0000000..9da76d6
--- /dev/null
+++ b/services/core/java/com/android/server/pm/CleanUpArgs.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.annotation.NonNull;
+
+import java.io.File;
+
+final class CleanUpArgs {
+ @NonNull
+ private final String mPackageName;
+ @NonNull
+ private final File mCodeFile;
+ @NonNull
+ private final String[] mInstructionSets;
+
+ /**
+ * Create args that describe an existing installed package. Typically used
+ * when cleaning up old installs.
+ */
+ CleanUpArgs(@NonNull String packageName, @NonNull String codePath,
+ @NonNull String[] instructionSets) {
+ mPackageName = packageName;
+ mCodeFile = new File(codePath);
+ mInstructionSets = instructionSets;
+ }
+
+ @NonNull
+ String getPackageName() {
+ return mPackageName;
+ }
+
+ @NonNull
+ File getCodeFile() {
+ return mCodeFile;
+ }
+
+ @NonNull
+ /** @see PackageSetting#getPath() */
+ String getCodePath() {
+ return mCodeFile.getAbsolutePath();
+ }
+
+ @NonNull
+ String[] getInstructionSets() {
+ return mInstructionSets;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 27f4e11..482807c 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -518,7 +518,9 @@
boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId)
throws PackageManager.NameNotFoundException;
- boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, @UserIdInt int userId);
+ /** Check if the package is suspending any package. */
+ boolean isSuspendingAnyPackages(@NonNull String suspendingPackage,
+ @UserIdInt int suspendingUserId, int targetUserId);
@NonNull
ParceledListSlice<IntentFilter> getAllIntentFilters(@NonNull String packageName);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 744c946..3cb2420 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -96,6 +96,7 @@
import android.content.pm.SigningDetails;
import android.content.pm.SigningInfo;
import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
import android.content.pm.VersionedPackage;
import android.os.Binder;
import android.os.Build;
@@ -5008,11 +5009,13 @@
@Override
public boolean isSuspendingAnyPackages(@NonNull String suspendingPackage,
- @UserIdInt int userId) {
+ @UserIdInt int suspendingUserId, int targetUserId) {
+ final UserPackage suspender = UserPackage.of(suspendingUserId, suspendingPackage);
for (final PackageStateInternal packageState : getPackageStates().values()) {
- final PackageUserStateInternal state = packageState.getUserStateOrDefault(userId);
+ final PackageUserStateInternal state =
+ packageState.getUserStateOrDefault(targetUserId);
if (state.getSuspendParams() != null
- && state.getSuspendParams().containsKey(suspendingPackage)) {
+ && state.getSuspendParams().containsKey(suspender)) {
return true;
}
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index aa7f0d3..b96b704 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -282,8 +282,8 @@
// other processes clean up before deleting resources.
synchronized (mPm.mInstallLock) {
if (info.mArgs != null) {
- mRemovePackageHelper.cleanUpResources(info.mArgs.mCodeFile,
- info.mArgs.mInstructionSets);
+ mRemovePackageHelper.cleanUpResources(info.mArgs.getPackageName(),
+ info.mArgs.getCodeFile(), info.mArgs.getInstructionSets());
}
boolean reEnableStub = false;
@@ -571,7 +571,7 @@
// Delete application code and resources only for parent packages
if (deleteCodeAndResources) {
- outInfo.mArgs = new InstallArgs(
+ outInfo.mArgs = new CleanUpArgs(ps.getName(),
ps.getPathString(), getAppDexInstructionSets(
ps.getPrimaryCpuAbiLegacy(), ps.getSecondaryCpuAbiLegacy()));
if (DEBUG_SD_INSTALL) Slog.i(TAG, "args=" + outInfo.mArgs);
diff --git a/services/core/java/com/android/server/pm/InstallArgs.java b/services/core/java/com/android/server/pm/InstallArgs.java
index cc6bb00..46f9732 100644
--- a/services/core/java/com/android/server/pm/InstallArgs.java
+++ b/services/core/java/com/android/server/pm/InstallArgs.java
@@ -16,14 +16,9 @@
package com.android.server.pm;
-import static android.app.AppOpsManager.MODE_DEFAULT;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.pm.DataLoaderType;
import android.content.pm.IPackageInstallObserver2;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
import android.content.pm.SigningDetails;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -102,22 +97,4 @@
mPackageSource = packageSource;
mApplicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
}
-
- /**
- * Create args that describe an existing installed package. Typically used
- * when cleaning up old installs, or used as a move source.
- */
- InstallArgs(String codePath, String[] instructionSets) {
- this(OriginInfo.fromNothing(), null, null, 0, 0, InstallSource.EMPTY, null, null,
- instructionSets, null, new ArrayMap<>(), null, MODE_DEFAULT, null, 0,
- SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN,
- PackageManager.INSTALL_SCENARIO_DEFAULT, false, DataLoaderType.NONE,
- PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, false);
- mCodeFile = (codePath != null) ? new File(codePath) : null;
- }
-
- /** @see PackageSettingBase#getPath() */
- String getCodePath() {
- return (mCodeFile != null) ? mCodeFile.getAbsolutePath() : null;
- }
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 0fa7aa5..65bfb2f 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2111,7 +2111,8 @@
// We didn't need to disable the .apk as a current system package,
// which means we are replacing another update that is already
// installed. We need to make sure to delete the older one's .apk.
- installRequest.getRemovedInfo().mArgs = new InstallArgs(
+ installRequest.getRemovedInfo().mArgs = new CleanUpArgs(
+ packageName,
oldPackage.getPath(),
getAppDexInstructionSets(
deletedPkgSetting.getPrimaryCpuAbi(),
@@ -2820,7 +2821,7 @@
request.setReturnMessage("Package was removed before install could complete.");
// Remove the update failed package's older resources safely now
- mRemovePackageHelper.cleanUpResources(request.getOldCodeFile(),
+ mRemovePackageHelper.cleanUpResources(packageName, request.getOldCodeFile(),
request.getOldInstructionSet());
mPm.notifyInstallObserver(request);
return;
@@ -2872,7 +2873,7 @@
getUnknownSourcesSettings());
// Remove the replaced package's older resources safely now
- InstallArgs args = request.getRemovedInfo() != null
+ CleanUpArgs args = request.getRemovedInfo() != null
? request.getRemovedInfo().mArgs : null;
if (args != null) {
if (!killApp) {
@@ -2883,7 +2884,8 @@
// propagated to all application threads.
mPm.scheduleDeferredNoKillPostDelete(args);
} else {
- mRemovePackageHelper.cleanUpResources(args.mCodeFile, args.mInstructionSets);
+ mRemovePackageHelper.cleanUpResources(packageName, args.getCodeFile(),
+ args.getInstructionSets());
}
} else {
// Force a gc to clear up things. Ask for a background one, it's fine to go on
@@ -4040,7 +4042,7 @@
+ "; " + pkgSetting.getPathString()
+ " --> " + parsedPackage.getPath());
- mRemovePackageHelper.cleanUpResources(
+ mRemovePackageHelper.cleanUpResources(pkgSetting.getPackageName(),
new File(pkgSetting.getPathString()),
getAppDexInstructionSets(pkgSetting.getPrimaryCpuAbiLegacy(),
pkgSetting.getSecondaryCpuAbiLegacy()));
@@ -4146,7 +4148,8 @@
+ parsedPackage.getLongVersionCode()
+ "; " + pkgSetting.getPathString() + " --> "
+ parsedPackage.getPath());
- mRemovePackageHelper.cleanUpResources(new File(pkgSetting.getPathString()),
+ mRemovePackageHelper.cleanUpResources(pkgSetting.getPackageName(),
+ new File(pkgSetting.getPathString()),
getAppDexInstructionSets(
pkgSetting.getPrimaryCpuAbiLegacy(), pkgSetting.getSecondaryCpuAbiLegacy()));
} else {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index ee780d9..ebaf516 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -294,13 +294,13 @@
@Nullable
public File getOldCodeFile() {
return (mRemovedInfo != null && mRemovedInfo.mArgs != null)
- ? mRemovedInfo.mArgs.mCodeFile : null;
+ ? mRemovedInfo.mArgs.getCodeFile() : null;
}
@Nullable
public String[] getOldInstructionSet() {
return (mRemovedInfo != null && mRemovedInfo.mArgs != null)
- ? mRemovedInfo.mArgs.mInstructionSets : null;
+ ? mRemovedInfo.mArgs.getInstructionSets() : null;
}
public UserHandle getUser() {
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 2864a8b..dcfc855d 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -132,6 +132,11 @@
private static final String EXTRA_INSTALLER_TITLE =
"com.android.content.pm.extra.UNARCHIVE_INSTALLER_TITLE";
+ private static final PorterDuffColorFilter OPACITY_LAYER_FILTER =
+ new PorterDuffColorFilter(
+ Color.argb(0.5f /* alpha */, 0f /* red */, 0f /* green */, 0f /* blue */),
+ PorterDuff.Mode.SRC_ATOP);
+
private final Context mContext;
private final PackageManagerService mPm;
@@ -746,11 +751,7 @@
return bitmap;
}
BitmapDrawable appIconDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
- PorterDuffColorFilter colorFilter =
- new PorterDuffColorFilter(
- Color.argb(0.32f /* alpha */, 0f /* red */, 0f /* green */, 0f /* blue */),
- PorterDuff.Mode.SRC_ATOP);
- appIconDrawable.setColorFilter(colorFilter);
+ appIconDrawable.setColorFilter(OPACITY_LAYER_FILTER);
appIconDrawable.setBounds(
0 /* left */,
0 /* top */,
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 4ecbd15..ee5875e 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -101,9 +101,10 @@
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "postInstall", msg.arg1);
} break;
case DEFERRED_NO_KILL_POST_DELETE: {
- InstallArgs args = (InstallArgs) msg.obj;
+ CleanUpArgs args = (CleanUpArgs) msg.obj;
if (args != null) {
- mPm.cleanUpResources(args.mCodeFile, args.mInstructionSets);
+ mPm.cleanUpResources(args.getPackageName(), args.getCodeFile(),
+ args.getInstructionSets());
}
} break;
case DEFERRED_NO_KILL_INSTALL_OBSERVER:
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index c737b45..8da1683 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -19,6 +19,8 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.RESTRICTION_NONE;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -37,6 +39,7 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.SuspendDialogInfo;
+import android.content.pm.UserPackage;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -250,7 +253,7 @@
getSuspendPackageHelper().removeSuspensionsBySuspendingPackage(snapshot(),
new String[]{packageName},
(suspendingPackage) -> !PackageManagerService.PLATFORM_PACKAGE_NAME.equals(
- suspendingPackage),
+ suspendingPackage.packageName),
userId);
}
@@ -269,7 +272,7 @@
@Override
@Deprecated
- public final String getSuspendingPackage(String suspendedPackage, int userId) {
+ public final UserPackage getSuspendingPackage(String suspendedPackage, int userId) {
return getSuspendPackageHelper().getSuspendingPackage(snapshot(), suspendedPackage, userId,
Binder.getCallingUid());
}
@@ -277,7 +280,7 @@
@Override
@Deprecated
public final SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage,
- String suspendingPackage, int userId) {
+ UserPackage suspendingPackage, int userId) {
return getSuspendPackageHelper().getSuspendedDialogInfo(snapshot(), suspendedPackage,
suspendingPackage, userId, Binder.getCallingUid());
}
@@ -683,14 +686,16 @@
@Override
@Deprecated
- public final void unsuspendForSuspendingPackage(final String packageName, int affectedUser) {
- mService.unsuspendForSuspendingPackage(snapshot(), packageName, affectedUser);
+ public final void unsuspendAdminSuspendedPackages(int affectedUser) {
+ final int suspendingUserId = affectedUser;
+ mService.unsuspendForSuspendingPackage(snapshot(), PLATFORM_PACKAGE_NAME, suspendingUserId);
}
@Override
@Deprecated
- public final boolean isSuspendingAnyPackages(String suspendingPackage, int userId) {
- return snapshot().isSuspendingAnyPackages(suspendingPackage, userId);
+ public final boolean isAdminSuspendingAnyPackages(int userId) {
+ final int suspendingUserId = userId;
+ return snapshot().isSuspendingAnyPackages(PLATFORM_PACKAGE_NAME, suspendingUserId, userId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 56365b6..1d5b8c3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -292,6 +292,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
+import java.util.function.Predicate;
/**
* Keep track of all those APKs everywhere.
@@ -1293,7 +1294,7 @@
mHandler.sendMessageDelayed(message, DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS);
}
- void scheduleDeferredNoKillPostDelete(InstallArgs args) {
+ void scheduleDeferredNoKillPostDelete(CleanUpArgs args) {
Message message = mHandler.obtainMessage(DEFERRED_NO_KILL_POST_DELETE, args);
// If the feature flag is on, retain the old files for a day. Otherwise, delete the old
// files after a few seconds.
@@ -3137,7 +3138,7 @@
}
private void enforceCanSetPackagesSuspendedAsUser(@NonNull Computer snapshot,
- boolean quarantined, String callingPackage, int callingUid, int userId,
+ boolean quarantined, UserPackage suspender, int callingUid, int targetUserId,
String callingMethod) {
if (callingUid == Process.ROOT_UID
// Need to compare app-id to allow system dialogs access on secondary users
@@ -3145,9 +3146,10 @@
return;
}
- final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId);
+ final String ownerPackage =
+ mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(targetUserId);
if (ownerPackage != null) {
- final int ownerUid = snapshot.getPackageUid(ownerPackage, 0, userId);
+ final int ownerUid = snapshot.getPackageUid(ownerPackage, 0, targetUserId);
if (ownerUid == callingUid) {
return;
}
@@ -3168,25 +3170,27 @@
callingMethod);
}
- final int packageUid = snapshot.getPackageUid(callingPackage, 0, userId);
+ final int packageUid = snapshot.getPackageUid(suspender.packageName, 0, targetUserId);
final boolean allowedPackageUid = packageUid == callingUid;
// TODO(b/139383163): remove special casing for shell and enforce INTERACT_ACROSS_USERS_FULL
final boolean allowedShell = callingUid == SHELL_UID
&& UserHandle.isSameApp(packageUid, callingUid);
if (!allowedShell && !allowedPackageUid) {
- throw new SecurityException("Calling package " + callingPackage + " in user "
- + userId + " does not belong to calling uid " + callingUid);
+ throw new SecurityException("Suspending package " + suspender.packageName
+ + " in user " + targetUserId + " does not belong to calling uid " + callingUid);
}
}
void unsuspendForSuspendingPackage(@NonNull Computer computer, String suspendingPackage,
- @UserIdInt int userId) {
+ @UserIdInt int suspendingUserId) {
// TODO: This can be replaced by a special parameter to iterate all packages, rather than
// this weird pre-collect of all packages.
final String[] allPackages = computer.getPackageStates().keySet().toArray(new String[0]);
+ final Predicate<UserPackage> suspenderPredicate =
+ UserPackage.of(suspendingUserId, suspendingPackage)::equals;
mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer,
- allPackages, suspendingPackage::equals, userId);
+ allPackages, suspenderPredicate, suspendingUserId);
}
void removeAllDistractingPackageRestrictions(@NonNull Computer snapshot, int userId) {
@@ -5259,8 +5263,9 @@
if (!snapshot.isPackageSuspendedForUser(packageName, userId)) {
return null;
}
- return mSuspendPackageHelper.getSuspendingPackage(snapshot, packageName, userId,
- callingUid);
+ final UserPackage suspender = mSuspendPackageHelper.getSuspendingPackage(
+ snapshot, packageName, userId, callingUid);
+ return suspender != null ? suspender.packageName : null;
} catch (PackageManager.NameNotFoundException e) {
return null;
}
@@ -6198,7 +6203,8 @@
@Override
public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
PersistableBundle appExtras, PersistableBundle launcherExtras,
- SuspendDialogInfo dialogInfo, int flags, String callingPackage, int userId) {
+ SuspendDialogInfo dialogInfo, int flags, String suspendingPackage,
+ int suspendingUserId, int targetUserId) {
final int callingUid = Binder.getCallingUid();
boolean quarantined = false;
if (Flags.quarantinedEnabled()) {
@@ -6207,14 +6213,15 @@
} else if (FeatureFlagUtils.isEnabled(mContext,
SETTINGS_TREAT_PAUSE_AS_QUARANTINE)) {
final String wellbeingPkg = mContext.getString(R.string.config_systemWellbeing);
- quarantined = callingPackage.equals(wellbeingPkg);
+ quarantined = suspendingPackage.equals(wellbeingPkg);
}
}
final Computer snapshot = snapshotComputer();
- enforceCanSetPackagesSuspendedAsUser(snapshot, quarantined, callingPackage, callingUid,
- userId, "setPackagesSuspendedAsUser");
+ final UserPackage suspender = UserPackage.of(targetUserId, suspendingPackage);
+ enforceCanSetPackagesSuspendedAsUser(snapshot, quarantined, suspender, callingUid,
+ targetUserId, "setPackagesSuspendedAsUser");
return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended,
- appExtras, launcherExtras, dialogInfo, callingPackage, userId, callingUid,
+ appExtras, launcherExtras, dialogInfo, suspender, targetUserId, callingUid,
quarantined);
}
@@ -6653,7 +6660,7 @@
final Computer computer = snapshotComputer();
final String[] allPackages = computer.getAllAvailablePackageNames();
mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer, allPackages,
- (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage),
+ (suspender) -> !PLATFORM_PACKAGE_NAME.equals(suspender.packageName),
userId);
}
@@ -6667,8 +6674,13 @@
@Override
public String[] setPackagesSuspendedByAdmin(
@UserIdInt int userId, @NonNull String[] packageNames, boolean suspended) {
- return mSuspendPackageHelper.setPackagesSuspendedByAdmin(
- snapshotComputer(), userId, packageNames, suspended);
+ final int suspendingUserId = userId;
+ final UserPackage suspender = UserPackage.of(
+ suspendingUserId, PackageManagerService.PLATFORM_PACKAGE_NAME);
+ return mSuspendPackageHelper.setPackagesSuspended(snapshotComputer(), packageNames,
+ suspended, null /* appExtras */, null /* launcherExtras */,
+ null /* dialogInfo */, suspender, userId, Process.SYSTEM_UID,
+ false /* quarantined */);
}
@Override
@@ -7974,8 +7986,9 @@
mRemovePackageHelper.removeCodePath(codePath);
}
- void cleanUpResources(@Nullable File codeFile, @Nullable String[] instructionSets) {
- mRemovePackageHelper.cleanUpResources(codeFile, instructionSets);
+ void cleanUpResources(@NonNull String packageName, @NonNull File codeFile,
+ @NonNull String[] instructionSets) {
+ mRemovePackageHelper.cleanUpResources(packageName, codeFile, instructionSets);
}
void cleanUpForMoveInstall(String volumeUuid, String packageName, String fromCodePath) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 322557b..243fb16 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2827,7 +2827,7 @@
mInterface.setPackagesSuspendedAsUser(packageNames.toArray(new String[] {}),
suspendedState, ((appExtras.size() > 0) ? appExtras : null),
((launcherExtras.size() > 0) ? launcherExtras : null),
- info, flags, callingPackage, translatedUserId);
+ info, flags, callingPackage, UserHandle.USER_SYSTEM, translatedUserId);
for (int i = 0; i < packageNames.size(); i++) {
final String packageName = packageNames.get(i);
pw.println("Package " + packageName + " new suspended state: "
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index 881b0b3..6f57538 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -43,7 +43,7 @@
// of package changes
SparseArray<int[]> mBroadcastAllowList;
// Clean up resources deleted packages.
- InstallArgs mArgs = null;
+ CleanUpArgs mArgs = null;
private static final int[] EMPTY_INT_ARRAY = new int[0];
public void populateBroadcastUsers(PackageSetting deletedPackageSetting) {
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 7d0a1f6..02ce159 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -32,6 +32,7 @@
import android.content.pm.SigningDetails;
import android.content.pm.SigningInfo;
import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
import android.content.pm.overlay.OverlayPaths;
import android.os.UserHandle;
import android.os.incremental.IncrementalManager;
@@ -71,6 +72,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -172,6 +174,8 @@
private File mPath;
@NonNull
private String mPathString;
+ @Nullable
+ private LinkedHashSet<File> mOldPaths;
private float mLoadingProgress;
private long mLoadingCompletedTime;
@@ -682,6 +686,7 @@
pkg = other.pkg;
mPath = other.mPath;
mPathString = other.mPathString;
+ mOldPaths = other.mOldPaths == null ? null : new LinkedHashSet<>(other.mOldPaths);
mPrimaryCpuAbi = other.mPrimaryCpuAbi;
mSecondaryCpuAbi = other.mSecondaryCpuAbi;
mCpuAbiOverride = other.mCpuAbiOverride;
@@ -952,7 +957,7 @@
void setUserState(int userId, long ceDataInode, long deDataInode, int enabled,
boolean installed, boolean stopped, boolean notLaunched, boolean hidden,
- int distractionFlags, ArrayMap<String, SuspendParams> suspendParams,
+ int distractionFlags, ArrayMap<UserPackage, SuspendParams> suspendParams,
boolean instantApp, boolean virtualPreload, String lastDisableAppCaller,
ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
int installReason, int uninstallReason,
@@ -1182,7 +1187,7 @@
if (state.isSuspended()) {
for (int j = 0; j < state.getSuspendParams().size(); j++) {
proto.write(PackageProto.UserInfoProto.SUSPENDING_PACKAGE,
- state.getSuspendParams().keyAt(j));
+ state.getSuspendParams().keyAt(j).packageName);
}
}
proto.write(PackageProto.UserInfoProto.IS_STOPPED, state.isStopped());
@@ -1237,6 +1242,27 @@
return this;
}
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public PackageSetting addOldPath(@NonNull File path) {
+ if (mOldPaths == null) {
+ mOldPaths = new LinkedHashSet<>();
+ }
+ mOldPaths.add(path);
+ onChanged();
+ return this;
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public PackageSetting removeOldPath(@Nullable File path) {
+ if (path == null || mOldPaths == null) {
+ return this;
+ }
+ if (mOldPaths.remove(path)) {
+ onChanged();
+ }
+ return this;
+ }
+
/**
* @param userId the specific user to change the label/icon for
* @see PackageUserStateImpl#overrideLabelAndIcon(ComponentName, String, Integer)
@@ -1667,6 +1693,11 @@
}
@DataClass.Generated.Member
+ public @Nullable LinkedHashSet<File> getOldPaths() {
+ return mOldPaths;
+ }
+
+ @DataClass.Generated.Member
public float getLoadingProgress() {
return mLoadingProgress;
}
@@ -1748,10 +1779,10 @@
}
@DataClass.Generated(
- time = 1700251133016L,
+ time = 1702666890740L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
- inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.internal.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\npublic com.android.server.pm.PackageSetting setScannedAsStoppedSystemApp(boolean)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\npublic @java.lang.Override boolean isScannedAsStoppedSystemApp()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int SCANNED_AS_STOPPED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+ inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable boolean[] usesSdkLibrariesOptional\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.internal.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate @android.annotation.Nullable java.util.LinkedHashSet<java.io.File> mOldPaths\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n void setArchiveState(com.android.server.pm.pkg.ArchiveState,int)\n boolean getInstalled(int)\n boolean isArchived(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\npublic com.android.server.pm.PackageSetting setScannedAsStoppedSystemApp(boolean)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting addOldPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting removeOldPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override boolean[] getUsesSdkLibrariesOptional()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesOptional(boolean[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\npublic @java.lang.Override boolean isScannedAsStoppedSystemApp()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int SCANNED_AS_STOPPED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index f42b43f..2854453 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -30,6 +30,7 @@
import static com.android.server.pm.PackageManagerService.TAG;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.PackageManager;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.PackageLite;
@@ -456,15 +457,25 @@
}
}
- void cleanUpResources(File codeFile, String[] instructionSets) {
+ void cleanUpResources(@Nullable String packageName, @Nullable File codeFile,
+ @Nullable String[] instructionSets) {
synchronized (mPm.mInstallLock) {
cleanUpResourcesLI(codeFile, instructionSets);
}
+ if (packageName == null) {
+ return;
+ }
+ synchronized (mPm.mLock) {
+ PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+ if (ps != null) {
+ ps.removeOldPath(codeFile);
+ }
+ }
}
// Need installer lock especially for dex file removal.
@GuardedBy("mPm.mInstallLock")
- private void cleanUpResourcesLI(File codeFile, String[] instructionSets) {
+ private void cleanUpResourcesLI(@Nullable File codeFile, @Nullable String[] instructionSets) {
// Try enumerating all code paths before deleting
List<String> allCodePaths = Collections.EMPTY_LIST;
if (codeFile != null && codeFile.exists()) {
@@ -482,7 +493,8 @@
}
@GuardedBy("mPm.mInstallLock")
- private void removeDexFilesLI(List<String> allCodePaths, String[] instructionSets) {
+ private void removeDexFilesLI(@NonNull List<String> allCodePaths,
+ @Nullable String[] instructionSets) {
if (!allCodePaths.isEmpty()) {
if (instructionSets == null) {
throw new IllegalStateException("instructionSet == null");
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 5c6d61e..2e67b2f 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -37,6 +37,7 @@
import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
import static com.android.server.pm.PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD;
import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
+import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
import static com.android.server.pm.PackageManagerService.SCAN_MOVE;
import static com.android.server.pm.PackageManagerService.SCAN_NEW_INSTALL;
@@ -227,6 +228,7 @@
// make a deep copy to avoid modifying any existing system state.
pkgSetting = new PackageSetting(pkgSetting);
pkgSetting.setPkg(parsedPackage);
+ final boolean isDontKill = (scanFlags & SCAN_DONT_KILL_APP) != 0;
// REMOVE SharedUserSetting from method; update in a separate call.
//
@@ -244,7 +246,8 @@
parsedPackage.getUsesSdkLibrariesOptional(),
usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
parsedPackage.getMimeGroups(), newDomainSetId,
- parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
+ parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash(),
+ isDontKill);
}
if (createNewPackage && originalPkgSetting != null) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 460bbb7..edae273 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -41,6 +41,7 @@
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
@@ -51,6 +52,7 @@
import android.content.pm.Signature;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.overlay.OverlayPaths;
import android.net.Uri;
@@ -1227,9 +1229,10 @@
@Nullable boolean[] usesSdkLibrariesOptional,
@Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
@Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId,
- int targetSdkVersion, byte[] restrictUpdatedHash)
+ int targetSdkVersion, byte[] restrictUpdatedHash, boolean isDontKill)
throws PackageManagerException {
final String pkgName = pkgSetting.getPackageName();
+ final File oldCodePath = pkgSetting.getPath();
if (sharedUser != null) {
if (!Objects.equals(existingSharedUserSetting, sharedUser)) {
PackageManagerService.reportSettingsProblem(Log.WARN,
@@ -1246,7 +1249,7 @@
pkgSetting.setSharedUserAppId(INVALID_UID);
}
- if (!pkgSetting.getPath().equals(codePath)) {
+ if (!oldCodePath.equals(codePath)) {
final boolean isSystem = pkgSetting.isSystem();
Slog.i(PackageManagerService.TAG,
"Update" + (isSystem ? " system" : "")
@@ -1274,6 +1277,11 @@
pkgSetting.setLegacyNativeLibraryPath(legacyNativeLibraryPath);
}
pkgSetting.setPath(codePath);
+ if (isDontKill && Flags.improveInstallDontKill()) {
+ // We retain old code paths for DONT_KILL installs. Keep a record of old paths until
+ // they are removed.
+ pkgSetting.addOldPath(oldCodePath);
+ }
}
pkgSetting.setPrimaryCpuAbi(primaryCpuAbi)
@@ -1956,7 +1964,7 @@
ArchiveState archiveState = null;
int packageDepth = parser.getDepth();
- ArrayMap<String, SuspendParams> suspendParamsMap = null;
+ ArrayMap<UserPackage, SuspendParams> suspendParamsMap = null;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > packageDepth)) {
@@ -1983,18 +1991,15 @@
parser);
break;
case TAG_SUSPEND_PARAMS:
- final String suspendingPackage = parser.getAttributeValue(null,
- ATTR_SUSPENDING_PACKAGE);
- if (suspendingPackage == null) {
- Slog.wtf(TAG, "No suspendingPackage found inside tag "
- + TAG_SUSPEND_PARAMS);
+ Map.Entry<UserPackage, SuspendParams> entry =
+ readSuspensionParamsLPr(userId, parser);
+ if (entry == null) {
continue;
}
if (suspendParamsMap == null) {
suspendParamsMap = new ArrayMap<>();
}
- suspendParamsMap.put(suspendingPackage,
- SuspendParams.restoreFromXml(parser));
+ suspendParamsMap.put(entry.getKey(), entry.getValue());
break;
case TAG_ARCHIVE_STATE:
archiveState = parseArchiveState(parser);
@@ -2016,7 +2021,8 @@
oldSuspendedLauncherExtras,
false /* quarantined */);
suspendParamsMap = new ArrayMap<>();
- suspendParamsMap.put(oldSuspendingPackage, suspendParams);
+ suspendParamsMap.put(
+ UserPackage.of(userId, oldSuspendingPackage), suspendParams);
}
if (blockUninstall) {
@@ -2058,6 +2064,20 @@
}
}
+ @Nullable
+ private static Map.Entry<UserPackage, SuspendParams> readSuspensionParamsLPr(
+ int userId, TypedXmlPullParser parser) throws IOException {
+ final String suspendingPackage = parser.getAttributeValue(null, ATTR_SUSPENDING_PACKAGE);
+ if (suspendingPackage == null) {
+ Slog.wtf(TAG, "No suspendingPackage found inside tag " + TAG_SUSPEND_PARAMS);
+ return null;
+ }
+ final int suspendingUserId = userId;
+ return Map.entry(
+ UserPackage.of(suspendingUserId, suspendingPackage),
+ SuspendParams.restoreFromXml(parser));
+ }
+
private static ArchiveState parseArchiveState(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
String installerTitle = parser.getAttributeValue(null,
@@ -2414,10 +2434,11 @@
}
if (ustate.isSuspended()) {
for (int i = 0; i < ustate.getSuspendParams().size(); i++) {
- final String suspendingPackage = ustate.getSuspendParams().keyAt(i);
+ final UserPackage suspendingPackage =
+ ustate.getSuspendParams().keyAt(i);
serializer.startTag(null, TAG_SUSPEND_PARAMS);
serializer.attribute(null, ATTR_SUSPENDING_PACKAGE,
- suspendingPackage);
+ suspendingPackage.packageName);
final SuspendParams params =
ustate.getSuspendParams().valueAt(i);
if (params != null) {
@@ -4955,6 +4976,11 @@
}
pw.print(prefix); pw.print(" pkg="); pw.println(pkg);
pw.print(prefix); pw.print(" codePath="); pw.println(ps.getPathString());
+ if (ps.getOldPaths() != null && ps.getOldPaths().size() > 0) {
+ for (File oldPath : ps.getOldPaths()) {
+ pw.print(prefix); pw.println(" oldCodePath=" + oldPath.getAbsolutePath());
+ }
+ }
if (permissionNames == null) {
pw.print(prefix); pw.print(" resourcePath="); pw.println(ps.getPathString());
pw.print(prefix); pw.print(" legacyNativeLibraryDir=");
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index c2a960a..4e70cc5 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -27,10 +27,10 @@
import android.app.AppOpsManager;
import android.content.Intent;
import android.content.pm.SuspendDialogInfo;
+import android.content.pm.UserPackage;
import android.os.Binder;
import android.os.Bundle;
import android.os.PersistableBundle;
-import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
@@ -88,8 +88,8 @@
* @param dialogInfo An optional {@link SuspendDialogInfo} object describing the dialog that
* should be shown to the user when they try to launch a suspended app.
* Ignored if {@code suspended} is false.
- * @param callingPackage The caller's package name.
- * @param userId The user where packages reside.
+ * @param suspendingPackage The caller's package name.
+ * @param targetUserId The user where packages reside.
* @param callingUid The caller's uid.
* @return The names of failed packages.
*/
@@ -97,14 +97,14 @@
String[] setPackagesSuspended(@NonNull Computer snapshot, @Nullable String[] packageNames,
boolean suspended, @Nullable PersistableBundle appExtras,
@Nullable PersistableBundle launcherExtras, @Nullable SuspendDialogInfo dialogInfo,
- @NonNull String callingPackage, @UserIdInt int userId, int callingUid,
+ @NonNull UserPackage suspendingPackage, @UserIdInt int targetUserId, int callingUid,
boolean quarantined) {
if (ArrayUtils.isEmpty(packageNames)) {
return packageNames;
}
- if (suspended && !quarantined && !isSuspendAllowedForUser(snapshot, userId,
- callingUid)) {
- Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
+ if (suspended && !quarantined
+ && !isSuspendAllowedForUser(snapshot, targetUserId, callingUid)) {
+ Slog.w(TAG, "Cannot suspend due to restrictions on user " + targetUserId);
return packageNames;
}
@@ -119,19 +119,21 @@
final IntArray changedUids = new IntArray(packageNames.length);
final boolean[] canSuspend = suspended
- ? canSuspendPackageForUser(snapshot, packageNames, userId, callingUid)
+ ? canSuspendPackageForUser(snapshot, packageNames, targetUserId, callingUid)
: null;
for (int i = 0; i < packageNames.length; i++) {
final String packageName = packageNames[i];
- if (callingPackage.equals(packageName)) {
- Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
+ if (suspendingPackage.packageName.equals(packageName)
+ && suspendingPackage.userId == targetUserId) {
+ Slog.w(TAG, "Suspending package: " + suspendingPackage + " trying to "
+ (suspended ? "" : "un") + "suspend itself. Ignoring");
unmodifiablePackages.add(packageName);
continue;
}
final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
- if (packageState == null || !packageState.getUserStateOrDefault(userId).isInstalled()
- || snapshot.shouldFilterApplication(packageState, callingUid, userId)) {
+ if (packageState == null
+ || !packageState.getUserStateOrDefault(targetUserId).isInstalled()
+ || snapshot.shouldFilterApplication(packageState, callingUid, targetUserId)) {
Slog.w(TAG, "Could not find package setting for package: " + packageName
+ ". Skipping suspending/un-suspending.");
unmodifiablePackages.add(packageName);
@@ -142,34 +144,34 @@
continue;
}
- final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
- packageState.getUserStateOrDefault(userId).getSuspendParams();
+ final WatchedArrayMap<UserPackage, SuspendParams> suspendParamsMap =
+ packageState.getUserStateOrDefault(targetUserId).getSuspendParams();
final SuspendParams oldSuspendParams = suspendParamsMap == null
- ? null : suspendParamsMap.get(callingPackage);
+ ? null : suspendParamsMap.get(suspendingPackage);
boolean changed = !Objects.equals(oldSuspendParams, newSuspendParams);
if (suspended && !changed) {
// Carried over API behavior, must notify change even if no change
notifyPackagesList.add(packageName);
notifyUids.add(
- UserHandle.getUid(userId, packageState.getAppId()));
+ UserHandle.getUid(targetUserId, packageState.getAppId()));
continue;
}
- // If only the callingPackage is suspending this package,
+ // If only the suspendingPackage is suspending this package,
// it will be unsuspended when this change is committed
boolean packageUnsuspended = !suspended
&& CollectionUtils.size(suspendParamsMap) == 1
- && suspendParamsMap.containsKey(callingPackage);
+ && suspendParamsMap.containsKey(suspendingPackage);
if (suspended || packageUnsuspended) {
// Always notify of a suspend call + notify when fully unsuspended
notifyPackagesList.add(packageName);
- notifyUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ notifyUids.add(UserHandle.getUid(targetUserId, packageState.getAppId()));
}
if (changed) {
changedPackagesList.add(packageName);
- changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ changedUids.add(UserHandle.getUid(targetUserId, packageState.getAppId()));
} else {
Slog.w(TAG, "No change is needed for package: " + packageName
+ ". Skipping suspending/un-suspending.");
@@ -181,11 +183,11 @@
for (int index = 0; index < size; index++) {
final String packageName = changedPackagesList.valueAt(index);
final PackageUserStateWrite userState = mutator.forPackage(packageName)
- .userState(userId);
+ .userState(targetUserId);
if (suspended) {
- userState.putSuspendParams(callingPackage, newSuspendParams);
+ userState.putSuspendParams(suspendingPackage, newSuspendParams);
} else {
- userState.removeSuspension(callingPackage);
+ userState.removeSuspension(suspendingPackage);
}
}
});
@@ -197,17 +199,17 @@
mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
suspended ? Intent.ACTION_PACKAGES_SUSPENDED
: Intent.ACTION_PACKAGES_UNSUSPENDED,
- changedPackages, notifyUids.toArray(), quarantined, userId);
+ changedPackages, notifyUids.toArray(), quarantined, targetUserId);
mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, changedPackages,
- suspended, userId);
- mPm.scheduleWritePackageRestrictions(userId);
+ suspended, targetUserId);
+ mPm.scheduleWritePackageRestrictions(targetUserId);
}
// Send the suspension changed broadcast to ensure suspension state is not stale.
if (!changedPackagesList.isEmpty()) {
mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
changedPackagesList.toArray(new String[0]), changedUids.toArray(), quarantined,
- userId);
+ targetUserId);
}
return unmodifiablePackages.toArray(new String[0]);
}
@@ -216,19 +218,19 @@
* Returns the names in the {@code packageNames} which can not be suspended by the caller.
*
* @param packageNames The names of packages to check.
- * @param userId The user where packages reside.
+ * @param targetUserId The user where packages reside.
* @param callingUid The caller's uid.
* @return The names of packages which are Unsuspendable.
*/
@NonNull
String[] getUnsuspendablePackagesForUser(@NonNull Computer snapshot,
- @NonNull String[] packageNames, @UserIdInt int userId, int callingUid) {
- if (!isSuspendAllowedForUser(snapshot, userId, callingUid)) {
- Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
+ @NonNull String[] packageNames, @UserIdInt int targetUserId, int callingUid) {
+ if (!isSuspendAllowedForUser(snapshot, targetUserId, callingUid)) {
+ Slog.w(TAG, "Cannot suspend due to restrictions on user " + targetUserId);
return packageNames;
}
final ArraySet<String> unactionablePackages = new ArraySet<>();
- final boolean[] canSuspend = canSuspendPackageForUser(snapshot, packageNames, userId,
+ final boolean[] canSuspend = canSuspendPackageForUser(snapshot, packageNames, targetUserId,
callingUid);
for (int i = 0; i < packageNames.length; i++) {
if (!canSuspend[i]) {
@@ -237,7 +239,7 @@
}
final PackageStateInternal packageState =
snapshot.getPackageStateForInstalledAndFiltered(
- packageNames[i], callingUid, userId);
+ packageNames[i], callingUid, targetUserId);
if (packageState == null) {
Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]);
unactionablePackages.add(packageNames[i]);
@@ -285,30 +287,31 @@
* @param packagesToChange The packages on which the suspension are to be removed.
* @param suspendingPackagePredicate A predicate identifying the suspending packages whose
* suspensions will be removed.
- * @param userId The user for which the changes are taking place.
+ * @param targetUserId The user for which the changes are taking place.
*/
void removeSuspensionsBySuspendingPackage(@NonNull Computer snapshot,
@NonNull String[] packagesToChange,
- @NonNull Predicate<String> suspendingPackagePredicate, int userId) {
+ @NonNull Predicate<UserPackage> suspendingPackagePredicate, int targetUserId) {
final List<String> unsuspendedPackages = new ArrayList<>();
final IntArray unsuspendedUids = new IntArray();
- final ArrayMap<String, ArraySet<String>> pkgToSuspendingPkgsToCommit = new ArrayMap<>();
+ final ArrayMap<String, ArraySet<UserPackage>> pkgToSuspendingPkgsToCommit =
+ new ArrayMap<>();
for (String packageName : packagesToChange) {
final PackageStateInternal packageState =
snapshot.getPackageStateInternal(packageName);
final PackageUserStateInternal packageUserState = packageState == null
- ? null : packageState.getUserStateOrDefault(userId);
+ ? null : packageState.getUserStateOrDefault(targetUserId);
if (packageUserState == null || !packageUserState.isSuspended()) {
continue;
}
- WatchedArrayMap<String, SuspendParams> suspendParamsMap =
+ WatchedArrayMap<UserPackage, SuspendParams> suspendParamsMap =
packageUserState.getSuspendParams();
int countRemoved = 0;
for (int index = 0; index < suspendParamsMap.size(); index++) {
- String suspendingPackage = suspendParamsMap.keyAt(index);
+ UserPackage suspendingPackage = suspendParamsMap.keyAt(index);
if (suspendingPackagePredicate.test(suspendingPackage)) {
- ArraySet<String> suspendingPkgsToCommit =
+ ArraySet<UserPackage> suspendingPkgsToCommit =
pkgToSuspendingPkgsToCommit.get(packageName);
if (suspendingPkgsToCommit == null) {
suspendingPkgsToCommit = new ArraySet<>();
@@ -322,31 +325,33 @@
// Everything would be removed and package unsuspended
if (countRemoved == suspendParamsMap.size()) {
unsuspendedPackages.add(packageState.getPackageName());
- unsuspendedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ unsuspendedUids.add(UserHandle.getUid(targetUserId, packageState.getAppId()));
}
}
mPm.commitPackageStateMutation(null, mutator -> {
for (int mapIndex = 0; mapIndex < pkgToSuspendingPkgsToCommit.size(); mapIndex++) {
String packageName = pkgToSuspendingPkgsToCommit.keyAt(mapIndex);
- ArraySet<String> packagesToRemove = pkgToSuspendingPkgsToCommit.valueAt(mapIndex);
- PackageUserStateWrite userState = mutator.forPackage(packageName).userState(userId);
+ ArraySet<UserPackage> packagesToRemove =
+ pkgToSuspendingPkgsToCommit.valueAt(mapIndex);
+ PackageUserStateWrite userState =
+ mutator.forPackage(packageName).userState(targetUserId);
for (int setIndex = 0; setIndex < packagesToRemove.size(); setIndex++) {
userState.removeSuspension(packagesToRemove.valueAt(setIndex));
}
}
});
- mPm.scheduleWritePackageRestrictions(userId);
+ mPm.scheduleWritePackageRestrictions(targetUserId);
final Computer newSnapshot = mPm.snapshotComputer();
if (!unsuspendedPackages.isEmpty()) {
final String[] packageArray = unsuspendedPackages.toArray(
new String[unsuspendedPackages.size()]);
mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, packageArray,
- false, userId);
+ false, targetUserId);
mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
Intent.ACTION_PACKAGES_UNSUSPENDED,
- packageArray, unsuspendedUids.toArray(), false, userId);
+ packageArray, unsuspendedUids.toArray(), false, targetUserId);
}
}
@@ -404,7 +409,7 @@
* @return The name of suspending package.
*/
@Nullable
- String getSuspendingPackage(@NonNull Computer snapshot, @NonNull String suspendedPackage,
+ UserPackage getSuspendingPackage(@NonNull Computer snapshot, @NonNull String suspendedPackage,
int userId, int callingUid) {
final PackageStateInternal packageState = snapshot.getPackageStateInternal(
suspendedPackage, callingUid);
@@ -417,13 +422,13 @@
return null;
}
- String suspendingPackage = null;
- String suspendedBySystem = null;
- String qasPackage = null;
+ UserPackage suspendingPackage = null;
+ UserPackage suspendedBySystem = null;
+ UserPackage qasPackage = null;
for (int i = 0; i < userState.getSuspendParams().size(); i++) {
suspendingPackage = userState.getSuspendParams().keyAt(i);
var suspendParams = userState.getSuspendParams().valueAt(i);
- if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
+ if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage.packageName)) {
suspendedBySystem = suspendingPackage;
}
if (suspendParams.isQuarantined() && qasPackage == null) {
@@ -451,7 +456,7 @@
*/
@Nullable
SuspendDialogInfo getSuspendedDialogInfo(@NonNull Computer snapshot,
- @NonNull String suspendedPackage, @NonNull String suspendingPackage, int userId,
+ @NonNull String suspendedPackage, @NonNull UserPackage suspendingPackage, int userId,
int callingUid) {
final PackageStateInternal packageState = snapshot.getPackageStateInternal(
suspendedPackage, callingUid);
@@ -464,7 +469,7 @@
return null;
}
- final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
+ final WatchedArrayMap<UserPackage, SuspendParams> suspendParamsMap =
userState.getSuspendParams();
if (suspendParamsMap == null) {
return null;
@@ -493,34 +498,36 @@
* be suspended or not.
*
* @param packageNames The package names to check suspendability for.
- * @param userId The user to check in
+ * @param targetUserId The user to check in
* @param callingUid The caller's uid.
* @return An array containing results of the checks
*/
@NonNull
boolean[] canSuspendPackageForUser(@NonNull Computer snapshot, @NonNull String[] packageNames,
- int userId, int callingUid) {
+ int targetUserId, int callingUid) {
final boolean[] canSuspend = new boolean[packageNames.length];
- final boolean isCallerOwner = isCallerDeviceOrProfileOwner(snapshot, userId, callingUid);
+ final boolean isCallerOwner =
+ isCallerDeviceOrProfileOwner(snapshot, targetUserId, callingUid);
final long token = Binder.clearCallingIdentity();
try {
final DefaultAppProvider defaultAppProvider = mInjector.getDefaultAppProvider();
- final String activeLauncherPackageName = defaultAppProvider.getDefaultHome(userId);
- final String dialerPackageName = defaultAppProvider.getDefaultDialer(userId);
+ final String activeLauncherPackageName =
+ defaultAppProvider.getDefaultHome(targetUserId);
+ final String dialerPackageName = defaultAppProvider.getDefaultDialer(targetUserId);
final String requiredInstallerPackage =
- getKnownPackageName(snapshot, KnownPackages.PACKAGE_INSTALLER, userId);
+ getKnownPackageName(snapshot, KnownPackages.PACKAGE_INSTALLER, targetUserId);
final String requiredUninstallerPackage =
- getKnownPackageName(snapshot, KnownPackages.PACKAGE_UNINSTALLER, userId);
+ getKnownPackageName(snapshot, KnownPackages.PACKAGE_UNINSTALLER, targetUserId);
final String requiredVerifierPackage =
- getKnownPackageName(snapshot, KnownPackages.PACKAGE_VERIFIER, userId);
+ getKnownPackageName(snapshot, KnownPackages.PACKAGE_VERIFIER, targetUserId);
final String requiredPermissionControllerPackage =
getKnownPackageName(snapshot, KnownPackages.PACKAGE_PERMISSION_CONTROLLER,
- userId);
+ targetUserId);
for (int i = 0; i < packageNames.length; i++) {
canSuspend[i] = false;
final String packageName = packageNames[i];
- if (mPm.isPackageDeviceAdmin(packageName, userId)) {
+ if (mPm.isPackageDeviceAdmin(packageName, targetUserId)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": has an active device admin");
continue;
@@ -555,12 +562,12 @@
+ "\": required for permissions management");
continue;
}
- if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
+ if (mProtectedPackages.isPackageStateProtected(targetUserId, packageName)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": protected package");
continue;
}
- if (!isCallerOwner && snapshot.getBlockUninstall(userId, packageName)) {
+ if (!isCallerOwner && snapshot.getBlockUninstall(targetUserId, packageName)) {
Slog.w(TAG, "Cannot suspend package \"" + packageName
+ "\": blocked by admin");
continue;
@@ -572,7 +579,7 @@
PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
AndroidPackage pkg = packageState == null ? null : packageState.getPkg();
if (pkg != null) {
- final int uid = UserHandle.getUid(userId, packageState.getAppId());
+ final int uid = UserHandle.getUid(targetUserId, packageState.getAppId());
// Cannot suspend SDK libs as they are controlled by SDK manager.
if (pkg.isSdkLibrary()) {
Slog.w(TAG, "Cannot suspend package: " + packageName
@@ -614,20 +621,6 @@
== AppOpsManager.MODE_ALLOWED;
}
- /**
- * Suspends packages on behalf of an admin.
- *
- * @return array of packages that are unsuspendable, either because admin is not allowed to
- * suspend them (e.g. current dialer) or there was other problem (e.g. package not found).
- */
- public String[] setPackagesSuspendedByAdmin(
- Computer snapshot, int userId, String[] packageNames, boolean suspend) {
- return setPackagesSuspended(snapshot, packageNames, suspend,
- null /* appExtras */, null /* launcherExtras */, null /* dialogInfo */,
- PackageManagerService.PLATFORM_PACKAGE_NAME, userId, Process.SYSTEM_UID,
- false /* quarantined */);
- }
-
private String getKnownPackageName(@NonNull Computer snapshot,
@KnownPackages.KnownPackage int knownPackage, int userId) {
final String[] knownPackages =
@@ -635,14 +628,15 @@
return knownPackages.length > 0 ? knownPackages[0] : null;
}
- private boolean isCallerDeviceOrProfileOwner(@NonNull Computer snapshot, int userId,
+ private boolean isCallerDeviceOrProfileOwner(@NonNull Computer snapshot, int targetUserId,
int callingUid) {
if (callingUid == SYSTEM_UID) {
return true;
}
- final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId);
+ final String ownerPackage =
+ mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(targetUserId);
if (ownerPackage != null) {
- return callingUid == snapshot.getPackageUidInternal(ownerPackage, 0, userId,
+ return callingUid == snapshot.getPackageUidInternal(ownerPackage, 0, targetUserId,
callingUid);
}
return false;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c48eccf..2305d6c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -7237,7 +7237,8 @@
@Override
public boolean isUserInitialized(@UserIdInt int userId) {
- return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
+ final UserInfo userInfo = getUserInfo(userId);
+ return userInfo != null && (userInfo.flags & UserInfo.FLAG_INITIALIZED) != 0;
}
@Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 671e031..3afba39 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -3291,7 +3291,7 @@
final PermissionAllowlist permissionAllowlist =
SystemConfig.getInstance().getPermissionAllowlist();
final String packageName = packageState.getPackageName();
- if (packageState.isVendor()) {
+ if (packageState.isVendor() || packageState.isOdm()) {
return permissionAllowlist.getVendorPrivilegedAppAllowlistState(packageName,
permissionName);
} else if (packageState.isProduct()) {
@@ -3386,7 +3386,7 @@
// the permission's protectionLevel does not have the extra 'vendorPrivileged'
// flag.
if (allowed && isPrivilegedPermission && !bp.isVendorPrivileged()
- && pkgSetting.isVendor()) {
+ && (pkgSetting.isVendor() || pkgSetting.isOdm())) {
Slog.w(TAG, "Permission " + permissionName
+ " cannot be granted to privileged vendor apk " + pkg.getPackageName()
+ " because it isn't a 'vendorPrivileged' permission.");
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
index 2f4ad2d8..15b693c 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
import android.content.pm.overlay.OverlayPaths;
import android.util.ArraySet;
import android.util.Pair;
@@ -173,7 +174,7 @@
@Nullable
@Override
- public WatchedArrayMap<String, SuspendParams> getSuspendParams() {
+ public WatchedArrayMap<UserPackage, SuspendParams> getSuspendParams() {
return null;
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index c5ef525..7a5a14d 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
import android.content.pm.overlay.OverlayPaths;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -121,7 +122,7 @@
* Suspending package to suspend params
*/
@Nullable
- private WatchedArrayMap<String, SuspendParams> mSuspendParams;
+ private WatchedArrayMap<UserPackage, SuspendParams> mSuspendParams;
@Nullable
private WatchedArrayMap<ComponentName, Pair<String, Integer>> mComponentLabelIconOverrideMap;
@@ -369,7 +370,10 @@
return !CollectionUtils.isEmpty(mSuspendParams);
}
- public PackageUserStateImpl putSuspendParams(@NonNull String suspendingPackage,
+ /**
+ * Adds or updates suspension params by the given package.
+ */
+ public PackageUserStateImpl putSuspendParams(@NonNull UserPackage suspendingPackage,
@Nullable SuspendParams suspendParams) {
if (mSuspendParams == null) {
mSuspendParams = new WatchedArrayMap<>();
@@ -384,7 +388,10 @@
return this;
}
- public PackageUserStateImpl removeSuspension(@NonNull String suspendingPackage) {
+ /**
+ * Removes suspension by the given package.
+ */
+ public PackageUserStateImpl removeSuspension(@NonNull UserPackage suspendingPackage) {
if (mSuspendParams != null) {
mSuspendParams.remove(suspendingPackage);
onChanged();
@@ -565,7 +572,7 @@
* Suspending package to suspend params
*/
public @NonNull PackageUserStateImpl setSuspendParams(
- @NonNull ArrayMap<String, SuspendParams> value) {
+ @NonNull ArrayMap<UserPackage, SuspendParams> value) {
if (value == null) {
return this;
}
@@ -778,7 +785,7 @@
* Suspending package to suspend params
*/
@DataClass.Generated.Member
- public @Nullable WatchedArrayMap<String,SuspendParams> getSuspendParams() {
+ public @Nullable WatchedArrayMap<UserPackage,SuspendParams> getSuspendParams() {
return mSuspendParams;
}
@@ -830,7 +837,7 @@
* Suspending package to suspend params
*/
@DataClass.Generated.Member
- public @NonNull PackageUserStateImpl setSuspendParams(@NonNull WatchedArrayMap<String,SuspendParams> value) {
+ public @NonNull PackageUserStateImpl setSuspendParams(@NonNull WatchedArrayMap<UserPackage,SuspendParams> value) {
mSuspendParams = value;
return this;
}
@@ -909,10 +916,10 @@
}
@DataClass.Generated(
- time = 1701470095849L,
+ time = 1701864813354L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
- inputSignatures = "private int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate long mDeDataInode\nprivate int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isQuarantined()\npublic @java.lang.Override boolean dataExists()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final int INSTALLED\nprivate static final int STOPPED\nprivate static final int NOT_LAUNCHED\nprivate static final int HIDDEN\nprivate static final int INSTANT_APP\nprivate static final int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+ inputSignatures = "private int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate long mDeDataInode\nprivate int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.pm.UserPackage,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(android.content.pm.UserPackage,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(android.content.pm.UserPackage)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<android.content.pm.UserPackage,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isQuarantined()\npublic @java.lang.Override boolean dataExists()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final int INSTALLED\nprivate static final int STOPPED\nprivate static final int NOT_LAUNCHED\nprivate static final int HIDDEN\nprivate static final int INSTANT_APP\nprivate static final int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
index 46cc830..f8d745c 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
+import android.content.pm.UserPackage;
import android.content.pm.pkg.FrameworkPackageUserState;
import android.util.Pair;
@@ -38,7 +39,7 @@
// TODO: Make non-null with emptyMap()
@Nullable
- WatchedArrayMap<String, SuspendParams> getSuspendParams();
+ WatchedArrayMap<UserPackage, SuspendParams> getSuspendParams();
@Nullable
WatchedArraySet<String> getDisabledComponentsNoCopy();
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 8430cf7..253eb40 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -21,6 +21,7 @@
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
import android.content.pm.overlay.OverlayPaths;
import android.util.ArraySet;
@@ -349,7 +350,7 @@
@NonNull
@Override
- public PackageUserStateWrite putSuspendParams(@NonNull String suspendingPackage,
+ public PackageUserStateWrite putSuspendParams(@NonNull UserPackage suspendingPackage,
@Nullable SuspendParams suspendParams) {
if (mUserState != null) {
mUserState.putSuspendParams(suspendingPackage, suspendParams);
@@ -359,7 +360,7 @@
@NonNull
@Override
- public PackageUserStateWrite removeSuspension(@NonNull String suspendingPackage) {
+ public PackageUserStateWrite removeSuspension(@NonNull UserPackage suspendingPackage) {
if (mUserState != null) {
mUserState.removeSuspension(suspendingPackage);
}
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
index 0c6c672..f6b2104 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
import android.content.pm.overlay.OverlayPaths;
import com.android.server.pm.pkg.PackageUserStateImpl;
@@ -38,11 +39,11 @@
@PackageManager.DistractionRestriction int restrictionFlags);
@NonNull
- PackageUserStateWrite putSuspendParams(@NonNull String suspendingPackage,
+ PackageUserStateWrite putSuspendParams(@NonNull UserPackage suspendingPackage,
@Nullable SuspendParams suspendParams);
@NonNull
- PackageUserStateWrite removeSuspension(@NonNull String suspendingPackage);
+ PackageUserStateWrite removeSuspension(@NonNull UserPackage suspendingPackage);
@NonNull
PackageUserStateWrite setHidden(boolean hidden);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 938ed23..e8b54d58 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -6865,13 +6865,13 @@
static class ButtonOverridePermissionChecker {
boolean canAppOverrideSystemKey(Context context, int uid) {
return PermissionChecker.checkPermissionForDataDelivery(
- context,
- OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW,
- PID_UNKNOWN,
- uid,
- null,
- null,
- null)
+ context,
+ OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW,
+ PID_UNKNOWN,
+ uid,
+ null,
+ null,
+ null)
== PERMISSION_GRANTED;
}
}
diff --git a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
index b8850af..ff426cc 100644
--- a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
+++ b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java
@@ -47,6 +47,17 @@
}
/**
+ * Create a new WatchedSparseSetArray from an existing SparseSetArray without copying.
+ * <p>
+ * Use with caution: Callers must ensure that no reference to {@code sparseSetArray} exists
+ * anywhere else in the system. If such a reference does exist, then changes to the storage via
+ * that reference will not be noticed by the new WatchedSpareSetArray.
+ */
+ public WatchedSparseSetArray(@NonNull SparseSetArray<T> sparseSetArray) {
+ mStorage = sparseSetArray;
+ }
+
+ /**
* Return the underlying storage. This breaks the wrapper but is necessary when
* passing the array to distant methods.
*/
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f3922f9..1ce87a7 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -565,7 +565,6 @@
boolean idle; // has the activity gone idle?
boolean hasBeenLaunched;// has this activity ever been launched?
boolean immersive; // immersive mode (don't interrupt if possible)
- boolean forceNewConfig; // force re-create with new config next time
boolean supportsEnterPipOnTaskSwitch; // This flag is set by the system to indicate that the
// activity can enter picture in picture while pausing (only when switching to another task)
// The PiP params used when deferring the entering of picture-in-picture.
@@ -9600,7 +9599,7 @@
// configurations because there are cases (like moving a task to the root pinned task) where
// the combine configurations are equal, but would otherwise differ in the override config
mTmpConfig.setTo(mLastReportedConfiguration.getMergedConfiguration());
- if (getConfiguration().equals(mTmpConfig) && !forceNewConfig && !displayChanged) {
+ if (getConfiguration().equals(mTmpConfig) && !displayChanged) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration & display "
+ "unchanged in %s", this);
return true;
@@ -9627,7 +9626,7 @@
return true;
}
- if (changes == 0 && !forceNewConfig) {
+ if (changes == 0) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration no differences in %s",
this);
// There are no significant differences, so we won't relaunch but should still deliver
@@ -9649,7 +9648,6 @@
// pick that up next time it starts.
if (!attachedToProcess()) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration doesn't matter not running %s", this);
- forceNewConfig = false;
return true;
}
@@ -9659,11 +9657,10 @@
Integer.toHexString(changes), Integer.toHexString(info.getRealConfigChanged()),
mLastReportedConfiguration);
- if (shouldRelaunchLocked(changes, mTmpConfig) || forceNewConfig) {
+ if (shouldRelaunchLocked(changes, mTmpConfig)) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
configChangeFlags |= changes;
startFreezingScreenLocked(globalChanges);
- forceNewConfig = false;
// Do not preserve window if it is freezing screen because the original window won't be
// able to update drawn state that causes freeze timeout.
preserveWindow &= isResizeOnlyChange(changes) && !mFreezingScreen;
@@ -9883,7 +9880,6 @@
try {
ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
(andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
- forceNewConfig = false;
final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(token,
pendingResults, pendingNewIntents, configChangeFlags,
new MergedConfiguration(getProcessGlobalConfiguration(),
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 86be6ba..7af494c 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -33,6 +33,7 @@
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
+import com.android.window.flags.Flags;
import java.io.File;
import java.util.ArrayList;
@@ -121,7 +122,8 @@
// TODO remove when enabled
static boolean isSnapshotEnabled() {
- return SystemProperties.getInt("persist.wm.debug.activity_screenshot", 0) != 0;
+ return SystemProperties.getInt("persist.wm.debug.activity_screenshot", 0) != 0
+ || Flags.activitySnapshotByDefault();
}
static PersistInfoProvider createPersistInfoProvider(
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index f9d344b..e7621ff 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -48,6 +48,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -223,7 +224,7 @@
// before issuing the work challenge.
return true;
}
- if (interceptLockedManagedProfileIfNeeded()) {
+ if (interceptLockedProfileIfNeeded()) {
return true;
}
if (interceptHomeIfNeeded()) {
@@ -335,19 +336,19 @@
return false;
}
final String suspendedPackage = mAInfo.applicationInfo.packageName;
- final String suspendingPackage = pmi.getSuspendingPackage(suspendedPackage, mUserId);
- if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
+ final UserPackage suspender = pmi.getSuspendingPackage(suspendedPackage, mUserId);
+ if (suspender != null && PLATFORM_PACKAGE_NAME.equals(suspender.packageName)) {
return interceptSuspendedByAdminPackage();
}
final SuspendDialogInfo dialogInfo = pmi.getSuspendedDialogInfo(suspendedPackage,
- suspendingPackage, mUserId);
+ suspender, mUserId);
final Bundle crossProfileOptions = hasCrossProfileAnimation()
? ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle()
: null;
final IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
FLAG_IMMUTABLE);
mIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(suspendedPackage,
- suspendingPackage, dialogInfo, crossProfileOptions, target, mUserId);
+ suspender, dialogInfo, crossProfileOptions, target, mUserId);
mCallingPid = mRealCallingPid;
mCallingUid = mRealCallingUid;
mResolvedType = null;
@@ -377,7 +378,7 @@
return true;
}
- private boolean interceptLockedManagedProfileIfNeeded() {
+ private boolean interceptLockedProfileIfNeeded() {
final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId);
if (interceptingIntent == null) {
return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 630b9e1..cb2adbc 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -579,30 +579,11 @@
computeResolveFilterUid(callingUid, realCallingUid, filterCallingUid),
realCallingPid);
if (resolveInfo == null) {
- final UserInfo userInfo = supervisor.getUserInfo(userId);
- if (userInfo != null && userInfo.isManagedProfile()) {
- // Special case for managed profiles, if attempting to launch non-cryto aware
- // app in a locked managed profile from an unlocked parent allow it to resolve
- // as user will be sent via confirm credentials to unlock the profile.
- final UserManager userManager = UserManager.get(supervisor.mService.mContext);
- boolean profileLockedAndParentUnlockingOrUnlocked = false;
- final long token = Binder.clearCallingIdentity();
- try {
- final UserInfo parent = userManager.getProfileParent(userId);
- profileLockedAndParentUnlockingOrUnlocked = (parent != null)
- && userManager.isUserUnlockingOrUnlocked(parent.id)
- && !userManager.isUserUnlockingOrUnlocked(userId);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- if (profileLockedAndParentUnlockingOrUnlocked) {
- resolveInfo = supervisor.resolveIntent(intent, resolvedType, userId,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- computeResolveFilterUid(callingUid, realCallingUid,
- filterCallingUid), realCallingPid);
- }
- }
+ // Special case for profiles: If attempting to launch non-crypto aware app in a
+ // locked profile or launch an app in a profile that is stopped by quiet mode from
+ // an unlocked parent, allow it to resolve as user will be sent via confirm
+ // credentials to unlock the profile.
+ resolveInfo = resolveIntentForLockedOrStoppedProfiles(supervisor);
}
// Collect information about the target of the Intent.
@@ -616,6 +597,36 @@
UserHandle.getUserId(activityInfo.applicationInfo.uid));
}
}
+
+ /**
+ * Resolve intent for locked or stopped profiles if the parent profile is unlocking or
+ * unlocked.
+ */
+ ResolveInfo resolveIntentForLockedOrStoppedProfiles(
+ ActivityTaskSupervisor supervisor) {
+ final UserInfo userInfo = supervisor.getUserInfo(userId);
+ if (userInfo != null && userInfo.isProfile()) {
+ final UserManager userManager = UserManager.get(supervisor.mService.mContext);
+ boolean profileLockedAndParentUnlockingOrUnlocked = false;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final UserInfo parent = userManager.getProfileParent(userId);
+ profileLockedAndParentUnlockingOrUnlocked = (parent != null)
+ && userManager.isUserUnlockingOrUnlocked(parent.id)
+ && !userManager.isUserUnlockingOrUnlocked(userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ if (profileLockedAndParentUnlockingOrUnlocked) {
+ return supervisor.resolveIntent(intent, resolvedType, userId,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ computeResolveFilterUid(callingUid, realCallingUid,
+ filterCallingUid), realCallingPid);
+ }
+ }
+ return null;
+ }
}
ActivityStarter(ActivityStartController controller, ActivityTaskManagerService service,
@@ -2767,10 +2778,7 @@
}
}
- // If the target task is not in the front, then we need to bring it to the front...
- // except... well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
- // the same behavior as if a new instance was being started, which means not bringing it
- // to the front if the caller is not itself in the front.
+ // If the target task is not in the front, then we need to bring it to the front.
final boolean differentTopTask;
if (mTargetRootTask.getDisplayArea() == mPreferredTaskDisplayArea) {
final Task focusRootTask = mTargetRootTask.mDisplayContent.getFocusedRootTask();
@@ -2787,49 +2795,47 @@
if (differentTopTask && !avoidMoveToFront()) {
mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
- if (mSourceRecord == null || inTopNonFinishingTask(mSourceRecord)) {
- // We really do want to push this one into the user's face, right now.
- if (mLaunchTaskBehind && mSourceRecord != null) {
- intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
- }
-
- if (intentActivity.isDescendantOf(mTargetRootTask)) {
- // TODO(b/151572268): Figure out a better way to move tasks in above 2-levels
- // tasks hierarchies.
- if (mTargetRootTask != intentTask
- && mTargetRootTask != intentTask.getParent().asTask()) {
- intentTask.getParent().positionChildAt(POSITION_TOP, intentTask,
- false /* includingParents */);
- intentTask = intentTask.getParent().asTaskFragment().getTask();
- }
- // If the activity is visible in multi-windowing mode, it may already be on
- // the top (visible to user but not the global top), then the result code
- // should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
- final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
- && intentActivity.inMultiWindowMode()
- && intentActivity == mTargetRootTask.topRunningActivity()
- && !intentActivity.mTransitionController.isTransientHide(
- mTargetRootTask);
- // We only want to move to the front, if we aren't going to launch on a
- // different root task. If we launch on a different root task, we will put the
- // task on top there.
- // Defer resuming the top activity while moving task to top, since the
- // current task-top activity may not be the activity that should be resumed.
- mTargetRootTask.moveTaskToFront(intentTask, mNoAnimation, mOptions,
- mStartActivity.appTimeTracker, DEFER_RESUME,
- "bringingFoundTaskToFront");
- mMovedToFront = !wasTopOfVisibleRootTask;
- } else if (intentActivity.getWindowingMode() != WINDOWING_MODE_PINNED) {
- // Leaves reparenting pinned task operations to task organizer to make sure it
- // dismisses pinned task properly.
- // TODO(b/199997762): Consider leaving all reparent operation of organized tasks
- // to task organizer.
- intentTask.reparent(mTargetRootTask, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
- ANIMATE, DEFER_RESUME, "reparentToTargetRootTask");
- mMovedToFront = true;
- }
- mOptions = null;
+ // We really do want to push this one into the user's face, right now.
+ if (mLaunchTaskBehind && mSourceRecord != null) {
+ intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
}
+
+ if (intentActivity.isDescendantOf(mTargetRootTask)) {
+ // TODO(b/151572268): Figure out a better way to move tasks in above 2-levels
+ // tasks hierarchies.
+ if (mTargetRootTask != intentTask
+ && mTargetRootTask != intentTask.getParent().asTask()) {
+ intentTask.getParent().positionChildAt(POSITION_TOP, intentTask,
+ false /* includingParents */);
+ intentTask = intentTask.getParent().asTaskFragment().getTask();
+ }
+ // If the activity is visible in multi-windowing mode, it may already be on
+ // the top (visible to user but not the global top), then the result code
+ // should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
+ final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
+ && intentActivity.inMultiWindowMode()
+ && intentActivity == mTargetRootTask.topRunningActivity()
+ && !intentActivity.mTransitionController.isTransientHide(
+ mTargetRootTask);
+ // We only want to move to the front, if we aren't going to launch on a
+ // different root task. If we launch on a different root task, we will put the
+ // task on top there.
+ // Defer resuming the top activity while moving task to top, since the
+ // current task-top activity may not be the activity that should be resumed.
+ mTargetRootTask.moveTaskToFront(intentTask, mNoAnimation, mOptions,
+ mStartActivity.appTimeTracker, DEFER_RESUME,
+ "bringingFoundTaskToFront");
+ mMovedToFront = !wasTopOfVisibleRootTask;
+ } else if (intentActivity.getWindowingMode() != WINDOWING_MODE_PINNED) {
+ // Leaves reparenting pinned task operations to task organizer to make sure it
+ // dismisses pinned task properly.
+ // TODO(b/199997762): Consider leaving all reparent operation of organized tasks
+ // to task organizer.
+ intentTask.reparent(mTargetRootTask, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
+ ANIMATE, DEFER_RESUME, "reparentToTargetRootTask");
+ mMovedToFront = true;
+ }
+ mOptions = null;
}
if (differentTopTask) {
logPIOnlyCreatorAllowsBAL();
@@ -2850,20 +2856,6 @@
mRootWindowContainer.getDefaultTaskDisplayArea(), mTargetRootTask);
}
- private boolean inTopNonFinishingTask(ActivityRecord r) {
- if (r == null || r.getTask() == null) {
- return false;
- }
-
- final Task rTask = r.getTask();
- final Task parent = rTask.getCreatedByOrganizerTask() != null
- ? rTask.getCreatedByOrganizerTask() : r.getRootTask();
- final ActivityRecord topNonFinishingActivity = parent != null
- ? parent.getTopNonFinishingActivity() : null;
-
- return topNonFinishingActivity != null && topNonFinishingActivity.getTask() == rTask;
- }
-
private void resumeTargetRootTaskIfNeeded() {
if (mDoResume) {
final ActivityRecord next = mTargetRootTask.topRunningActivity(
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 38682ca..dbae29b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -62,6 +62,7 @@
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
import static android.provider.Settings.Global.HIDE_ERROR_DIALOGS;
import static android.provider.Settings.System.FONT_SCALE;
+import static android.service.controls.flags.Flags.homePanelDream;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -1501,14 +1502,19 @@
a.exported = true;
a.name = DreamActivity.class.getName();
a.enabled = true;
- a.launchMode = ActivityInfo.LAUNCH_SINGLE_INSTANCE;
a.persistableMode = ActivityInfo.PERSIST_NEVER;
a.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
- a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
a.configChanges = 0xffffffff;
+ if (homePanelDream()) {
+ a.launchMode = ActivityInfo.LAUNCH_SINGLE_TASK;
+ } else {
+ a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ a.launchMode = ActivityInfo.LAUNCH_SINGLE_INSTANCE;
+ }
+
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchActivityType(ACTIVITY_TYPE_DREAM);
@@ -4777,6 +4783,10 @@
if (DEBUG_ALL && !mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) {
Slog.i(TAG, "continueWindowLayout reason=" + mLayoutReasons);
}
+
+ // ClientTransactions is queued during #deferWindowLayout() for performance.
+ // Notify to continue.
+ mLifecycleManager.onLayoutContinued();
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e59601c..10efb94 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -906,7 +906,6 @@
}
mService.getPackageManagerInternalLocked().notifyPackageUse(
r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
- r.forceNewConfig = false;
mService.getAppWarningsLocked().onStartActivity(r);
// Because we could be starting an Activity in the system process this may not go
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 6b6388b..2e47677 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -105,7 +105,7 @@
final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
clientTransaction.addTransactionItem(transactionItem);
- onClientTransactionItemScheduledLocked(clientTransaction);
+ onClientTransactionItemScheduled(clientTransaction);
} else {
// TODO(b/260873529): cleanup after launch.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
@@ -134,7 +134,7 @@
clientTransaction.addTransactionItem(transactionItem);
clientTransaction.addTransactionItem(lifecycleItem);
- onClientTransactionItemScheduledLocked(clientTransaction);
+ onClientTransactionItemScheduled(clientTransaction);
} else {
// TODO(b/260873529): cleanup after launch.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
@@ -146,6 +146,9 @@
/** Executes all the pending transactions. */
void dispatchPendingTransactions() {
+ if (!Flags.bundleClientTransactionFlag()) {
+ return;
+ }
final int size = mPendingTransactions.size();
for (int i = 0; i < size; i++) {
final ClientTransaction transaction = mPendingTransactions.valueAt(i);
@@ -158,6 +161,20 @@
mPendingTransactions.clear();
}
+ /**
+ * Called to when {@link WindowSurfacePlacer#continueLayout}.
+ * Dispatches all pending transactions unless there is an ongoing/scheduled layout, in which
+ * case the pending transactions will be dispatched in
+ * {@link RootWindowContainer#performSurfacePlacementNoTrace}.
+ */
+ void onLayoutContinued() {
+ if (shouldDispatchPendingTransactionsImmediately()) {
+ // Dispatch the pending transactions immediately if there is no ongoing/scheduled layout
+ dispatchPendingTransactions();
+ }
+ }
+
+ /** Must only be called with WM lock. */
@NonNull
private ClientTransaction getOrCreatePendingTransaction(@NonNull IApplicationThread client) {
final IBinder clientBinder = client.asBinder();
@@ -173,20 +190,28 @@
}
/** Must only be called with WM lock. */
- private void onClientTransactionItemScheduledLocked(
+ private void onClientTransactionItemScheduled(
@NonNull ClientTransaction clientTransaction) throws RemoteException {
- // TODO(b/260873529): make sure WindowSurfacePlacer#requestTraversal is called before
- // ClientTransaction scheduled when needed.
-
- if (mWms != null && (mWms.mWindowPlacerLocked.isInLayout()
- || mWms.mWindowPlacerLocked.isTraversalScheduled())) {
- // The pending transactions will be dispatched when
- // RootWindowContainer#performSurfacePlacementNoTrace.
- return;
+ if (shouldDispatchPendingTransactionsImmediately()) {
+ // Dispatch the pending transaction immediately.
+ mPendingTransactions.remove(clientTransaction.getClient().asBinder());
+ scheduleTransaction(clientTransaction);
}
+ }
- // Dispatch the pending transaction immediately.
- mPendingTransactions.remove(clientTransaction.getClient().asBinder());
- scheduleTransaction(clientTransaction);
+ /** Must only be called with WM lock. */
+ private boolean shouldDispatchPendingTransactionsImmediately() {
+ if (mWms == null) {
+ return true;
+ }
+ // Do not dispatch when
+ // 1. Layout deferred.
+ // 2. Layout requested.
+ // 3. Layout in process.
+ // The pending transactions will be dispatched during layout in
+ // RootWindowContainer#performSurfacePlacementNoTrace.
+ return !mWms.mWindowPlacerLocked.isLayoutDeferred()
+ && !mWms.mWindowPlacerLocked.isTraversalScheduled()
+ && !mWms.mWindowPlacerLocked.isInLayout();
}
}
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index 73bcc8d..1a8927e 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -19,7 +19,6 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_SYSTEM_FIRST;
import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_SYSTEM_LAST;
@@ -47,6 +46,7 @@
import android.util.DisplayMetrics;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.Xml;
import com.android.internal.protolog.common.ProtoLog;
@@ -60,6 +60,7 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@@ -347,6 +348,7 @@
private GameManagerInternal mGameManager;
private final AtomicFile mFile;
private final HashMap<String, Integer> mPackages = new HashMap<>();
+ private final SparseBooleanArray mLegacyScreenCompatPackages = new SparseBooleanArray();
private final CompatHandler mHandler;
private final SparseArray<CompatScaleProvider> mProviders = new SparseArray<>();
@@ -427,6 +429,7 @@
mPackages.remove(packageName);
scheduleWrite();
}
+ mLegacyScreenCompatPackages.delete(packageName.hashCode());
}
public void handlePackageAddedLocked(String packageName, boolean updated) {
@@ -458,6 +461,17 @@
mHandler.sendMessageDelayed(msg, 10000);
}
+ /**
+ * Returns {@code true} if the windows belonging to the package should be scaled with
+ * {@link DisplayContent#mCompatibleScreenScale}.
+ */
+ boolean useLegacyScreenCompatMode(String packageName) {
+ if (mLegacyScreenCompatPackages.size() == 0) {
+ return false;
+ }
+ return mLegacyScreenCompatPackages.get(packageName.hashCode());
+ }
+
public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
final boolean forceCompat = getPackageCompatModeEnabledLocked(ai);
final CompatScale compatScale = getCompatScaleFromProvider(ai.packageName, ai.uid);
@@ -466,8 +480,18 @@
: getCompatScale(ai.packageName, ai.uid, /* checkProvider= */ false);
final float densityScale = compatScale != null ? compatScale.mDensityScaleFactor : appScale;
final Configuration config = mService.getGlobalConfiguration();
- return new CompatibilityInfo(ai, config.screenLayout, config.smallestScreenWidthDp,
- forceCompat, appScale, densityScale);
+ final CompatibilityInfo info = new CompatibilityInfo(ai, config.screenLayout,
+ config.smallestScreenWidthDp, forceCompat, appScale, densityScale);
+ // Ignore invalid info which may be a placeholder of isolated process.
+ if (ai.flags != 0 && ai.sourceDir != null) {
+ if (!info.supportsScreen() && !"android".equals(ai.packageName)) {
+ Slog.i(TAG, "Use legacy screen compat mode: " + ai.packageName);
+ mLegacyScreenCompatPackages.put(ai.packageName.hashCode(), true);
+ } else if (mLegacyScreenCompatPackages.size() > 0) {
+ mLegacyScreenCompatPackages.delete(ai.packageName.hashCode());
+ }
+ }
+ return info;
}
float getCompatScale(String packageName, int uid) {
@@ -718,14 +742,23 @@
scheduleWrite();
- final Task rootTask = mService.getTopDisplayFocusedRootTask();
- ActivityRecord starting = rootTask.restartPackage(packageName);
-
+ final ArrayList<WindowProcessController> restartedApps = new ArrayList<>();
+ mService.mRootWindowContainer.forAllWindows(w -> {
+ final ActivityRecord ar = w.mActivityRecord;
+ if (ar != null) {
+ if (ar.packageName.equals(packageName) && !restartedApps.contains(ar.app)) {
+ ar.restartProcessIfVisible();
+ restartedApps.add(ar.app);
+ }
+ } else if (w.getProcess().mInfo.packageName.equals(packageName)) {
+ w.updateGlobalScale();
+ }
+ }, true /* traverseTopToBottom */);
// Tell all processes that loaded this package about the change.
SparseArray<WindowProcessController> pidMap = mService.mProcessMap.getPidMap();
for (int i = pidMap.size() - 1; i >= 0; i--) {
final WindowProcessController app = pidMap.valueAt(i);
- if (!app.containsPackage(packageName)) {
+ if (!app.containsPackage(packageName) || restartedApps.contains(app)) {
continue;
}
try {
@@ -737,14 +770,6 @@
} catch (Exception e) {
}
}
-
- if (starting != null) {
- starting.ensureActivityConfiguration(0 /* globalChanges */,
- false /* preserveWindow */);
- // And we need to make sure at this point that all other activities
- // are made visible with the correct configuration.
- rootTask.ensureActivitiesVisible(starting, 0, !PRESERVE_WINDOWS);
- }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ae10ce3..f8dc9c7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -512,7 +512,11 @@
*/
private final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics();
- /** The desired scaling factor for compatible apps. */
+ /**
+ * The desired scaling factor for compatible apps. It limits the size of the window to be
+ * original size ([320x480] x density). Used to scale window for applications running under
+ * legacy compatibility mode.
+ */
float mCompatibleScreenScale;
/** @see #getCurrentOverrideConfigurationChanges */
@@ -4794,7 +4798,8 @@
assignRelativeLayerForIme(getSyncTransaction(), true /* forceUpdate */);
scheduleAnimation();
- mWmService.mH.post(() -> InputMethodManagerInternal.get().onImeParentChanged());
+ mWmService.mH.post(
+ () -> InputMethodManagerInternal.get().onImeParentChanged(getDisplayId()));
} else if (mImeControlTarget != null && mImeControlTarget == mImeLayeringTarget) {
// Even if the IME surface parent is not changed, the layer target belonging to the
// parent may have changes. Then attempt to reassign if the IME control target is
@@ -7090,7 +7095,7 @@
}
@Override
- public void notifyInsetsControlChanged() {
+ public void notifyInsetsControlChanged(int displayId) {
final InsetsStateController stateController = getInsetsStateController();
try {
mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(),
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index 8ecbc17..b74eb56 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -29,8 +29,10 @@
/**
* Notifies the control target that the insets control has changed.
+ *
+ * @param displayId the display hosting the window of this target
*/
- default void notifyInsetsControlChanged() {
+ default void notifyInsetsControlChanged(int displayId) {
};
/**
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 7815679..3c556bf 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -728,7 +728,7 @@
}
@Override
- public void notifyInsetsControlChanged() {
+ public void notifyInsetsControlChanged(int displayId) {
mHandler.post(this);
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index c4d0129..6b9fcf4 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -72,7 +72,7 @@
};
private final InsetsControlTarget mEmptyImeControlTarget = new InsetsControlTarget() {
@Override
- public void notifyInsetsControlChanged() {
+ public void notifyInsetsControlChanged(int displayId) {
InsetsSourceControl[] controls = getControlsForDispatch(this);
if (controls == null) {
return;
@@ -80,7 +80,7 @@
for (InsetsSourceControl control : controls) {
if (control.getType() == WindowInsets.Type.ime()) {
mDisplayContent.mWmService.mH.post(() ->
- InputMethodManagerInternal.get().removeImeSurface());
+ InputMethodManagerInternal.get().removeImeSurface(displayId));
}
}
}
@@ -370,9 +370,10 @@
provider.onSurfaceTransactionApplied();
}
final ArraySet<InsetsControlTarget> newControlTargets = new ArraySet<>();
+ int displayId = mDisplayContent.getDisplayId();
for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) {
final InsetsControlTarget controlTarget = mPendingControlChanged.valueAt(i);
- controlTarget.notifyInsetsControlChanged();
+ controlTarget.notifyInsetsControlChanged(displayId);
if (mControlTargetProvidersMap.containsKey(controlTarget)) {
// We only collect targets who get controls, not lose controls.
newControlTargets.add(controlTarget);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 5518de7..9305396 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -20,6 +20,7 @@
import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
@@ -170,6 +171,8 @@
// Corresponds to OVERRIDE_ANY_ORIENTATION
private final boolean mIsOverrideAnyOrientationEnabled;
+ // Corresponds to OVERRIDE_ANY_ORIENTATION_TO_USER
+ private final boolean mIsOverrideToUserOrientationEnabled;
// Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT
private final boolean mIsOverrideToPortraitOrientationEnabled;
// Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR
@@ -254,9 +257,8 @@
// Counter for ActivityRecord#setRequestedOrientation
private int mSetOrientationRequestCounter = 0;
- // The min aspect ratio override set by user. Stores the last selected aspect ratio after
- // {@link #shouldApplyUserFullscreenOverride} or {@link #shouldApplyUserMinAspectRatioOverride}
- // have been invoked.
+ // TODO(b/315140179): Make mUserAspectRatio final
+ // The min aspect ratio override set by user
@PackageManager.UserMinAspectRatio
private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
@@ -355,6 +357,8 @@
PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
+ mIsOverrideToUserOrientationEnabled =
+ isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER);
mIsOverrideToPortraitOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT);
mIsOverrideToReverseLandscapeOrientationEnabled =
@@ -663,9 +667,11 @@
@ScreenOrientation
int overrideOrientationIfNeeded(@ScreenOrientation int candidate) {
+ final DisplayContent displayContent = mActivityRecord.mDisplayContent;
+ final boolean isIgnoreOrientationRequestEnabled = displayContent != null
+ && displayContent.getIgnoreOrientationRequest();
if (shouldApplyUserFullscreenOverride()
- && mActivityRecord.mDisplayContent != null
- && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
+ && isIgnoreOrientationRequestEnabled) {
Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
+ mActivityRecord + " is overridden to "
+ screenOrientationToString(SCREEN_ORIENTATION_USER)
@@ -690,7 +696,6 @@
return candidate;
}
- DisplayContent displayContent = mActivityRecord.mDisplayContent;
if (mIsOverrideOrientationOnlyForCameraEnabled && displayContent != null
&& (displayContent.mDisplayRotationCompatPolicy == null
|| !displayContent.mDisplayRotationCompatPolicy
@@ -698,6 +703,17 @@
return candidate;
}
+ // mUserAspectRatio is always initialized first in shouldApplyUserFullscreenOverride(),
+ // which will always come first before this check as user override > device
+ // manufacturer override.
+ if (mUserAspectRatio == PackageManager.USER_MIN_ASPECT_RATIO_UNSET
+ && mIsOverrideToUserOrientationEnabled && isIgnoreOrientationRequestEnabled) {
+ Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
+ + mActivityRecord + " is overridden to "
+ + screenOrientationToString(SCREEN_ORIENTATION_USER));
+ return SCREEN_ORIENTATION_USER;
+ }
+
if (mIsOverrideToReverseLandscapeOrientationEnabled
&& (isFixedOrientationLandscape(candidate) || mIsOverrideAnyOrientationEnabled)) {
Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 57939bc..7995028 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -89,6 +89,7 @@
import com.android.window.flags.Flags;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
@@ -106,7 +107,7 @@
final WindowProcessController mProcess;
private final String mStringName;
SurfaceSession mSurfaceSession;
- private int mNumWindow = 0;
+ private final ArrayList<WindowState> mAddedWindows = new ArrayList<>();
// Set of visible application overlay window surfaces connected to this session.
private final ArraySet<WindowSurfaceController> mAppOverlaySurfaces = new ArraySet<>();
// Set of visible alert window surfaces connected to this session.
@@ -192,8 +193,7 @@
try {
mCallback.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
- // The caller has died, so we can just forget about this.
- // Hmmm, should we call killSessionLocked()??
+ mClientDead = true;
}
}
@@ -211,12 +211,27 @@
}
}
+ boolean isClientDead() {
+ return mClientDead;
+ }
+
@Override
public void binderDied() {
synchronized (mService.mGlobalLock) {
mCallback.asBinder().unlinkToDeath(this, 0);
mClientDead = true;
- killSessionLocked();
+ try {
+ for (int i = mAddedWindows.size() - 1; i >= 0; i--) {
+ final WindowState w = mAddedWindows.get(i);
+ Slog.i(TAG_WM, "WIN DEATH: " + w);
+ if (w.mActivityRecord != null && w.mActivityRecord.findMainWindow() == w) {
+ mService.mSnapshotController.onAppDied(w.mActivityRecord);
+ }
+ w.removeIfPossible();
+ }
+ } finally {
+ killSessionLocked();
+ }
}
}
@@ -741,7 +756,7 @@
}
}
- void windowAddedLocked() {
+ void onWindowAdded(WindowState w) {
if (mPackageName == null) {
mPackageName = mProcess.mInfo.packageName;
mRelayoutTag = "relayoutWindow: " + mPackageName;
@@ -757,12 +772,14 @@
mService.dispatchNewAnimatorScaleLocked(this);
}
}
- mNumWindow++;
+ mAddedWindows.add(w);
}
- void windowRemovedLocked() {
- mNumWindow--;
- killSessionLocked();
+ void onWindowRemoved(WindowState w) {
+ mAddedWindows.remove(w);
+ if (mAddedWindows.isEmpty()) {
+ killSessionLocked();
+ }
}
@@ -829,7 +846,7 @@
}
private void killSessionLocked() {
- if (mNumWindow > 0 || !mClientDead) {
+ if (!mClientDead) {
return;
}
@@ -838,10 +855,6 @@
return;
}
- if (DEBUG) {
- Slog.v(TAG_WM, "Last window removed from " + this
- + ", destroying " + mSurfaceSession);
- }
ProtoLog.i(WM_SHOW_TRANSACTIONS, " KILL SURFACE SESSION %s", mSurfaceSession);
try {
mSurfaceSession.kill();
@@ -850,6 +863,7 @@
+ " in session " + this + ": " + e.toString());
}
mSurfaceSession = null;
+ mAddedWindows.clear();
mAlertWindowSurfaces.clear();
mAppOverlaySurfaces.clear();
setHasOverlayUi(false);
@@ -869,7 +883,7 @@
}
void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("mNumWindow="); pw.print(mNumWindow);
+ pw.print(prefix); pw.print("numWindow="); pw.print(mAddedWindows.size());
pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow);
pw.print(" mAppOverlaySurfaces="); pw.print(mAppOverlaySurfaces);
pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 671acfc..dbfcc22 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -34,7 +34,6 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
-import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
@@ -5917,22 +5916,6 @@
return activities;
}
- ActivityRecord restartPackage(String packageName) {
- ActivityRecord starting = topRunningActivity();
-
- // All activities that came from the package must be
- // restarted as if there was a config change.
- forAllActivities(r -> {
- if (!r.info.packageName.equals(packageName)) return;
- r.forceNewConfig = true;
- if (starting != null && r == starting && r.isVisibleRequested()) {
- r.startFreezingScreenLocked(CONFIG_SCREEN_LAYOUT);
- }
- });
-
- return starting;
- }
-
Task reuseOrCreateTask(ActivityInfo info, Intent intent, boolean toTop) {
return reuseOrCreateTask(info, intent, null /*voiceSession*/, null /*voiceInteractor*/,
toTop, null /*activity*/, null /*source*/, null /*options*/);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2125c63..c1310a6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1445,6 +1445,11 @@
if (!mDisplayReady) {
throw new IllegalStateException("Display has not been initialialized");
}
+ if (session.isClientDead()) {
+ ProtoLog.w(WM_ERROR, "Attempted to add window with a client %s "
+ + "that is dead. Aborting.", session);
+ return WindowManagerGlobal.ADD_APP_EXITING;
+ }
final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
@@ -1629,19 +1634,6 @@
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], attrs, viewVisibility, session.mUid, userId,
session.mCanAddInternalSystemWindow);
- if (win.mDeathRecipient == null) {
- // Client has apparently died, so there is no reason to
- // continue.
- ProtoLog.w(WM_ERROR, "Adding window client %s"
- + " that is dead, aborting.", client.asBinder());
- return WindowManagerGlobal.ADD_APP_EXITING;
- }
-
- if (win.getDisplayContent() == null) {
- ProtoLog.w(WM_ERROR, "Adding window to Display that has been removed.");
- return WindowManagerGlobal.ADD_INVALID_DISPLAY;
- }
-
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
@@ -1725,7 +1717,7 @@
displayContent.mTapExcludedWindows.add(win);
}
- win.attach();
+ win.mSession.onWindowAdded(win);
mWindowMap.put(client.asBinder(), win);
win.initAppOpsState();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4e9d23c..9c21e4c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -52,7 +52,6 @@
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NOT_MAGNIFIABLE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY;
@@ -310,7 +309,6 @@
// mAttrs.flags is tested in animation without being locked. If the bits tested are ever
// modified they will need to be locked.
final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams();
- final DeathRecipient mDeathRecipient;
private boolean mIsChildWindow;
final int mBaseLayer;
final int mSubLayer;
@@ -1108,7 +1106,6 @@
mViewVisibility = viewVisibility;
mPolicy = mWmService.mPolicy;
mContext = mWmService.mContext;
- DeathRecipient deathRecipient = new DeathRecipient();
mPowerManagerWrapper = powerManagerWrapper;
mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(
@@ -1128,22 +1125,6 @@
Slog.v(TAG, "Window " + this + " client=" + c.asBinder()
+ " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
}
- try {
- c.asBinder().linkToDeath(deathRecipient, 0);
- } catch (RemoteException e) {
- mDeathRecipient = null;
- mIsChildWindow = false;
- mLayoutAttached = false;
- mIsImWindow = false;
- mIsWallpaper = false;
- mIsFloatingLayer = false;
- mBaseLayer = 0;
- mSubLayer = 0;
- mWinAnimator = null;
- mOverrideScale = 1f;
- return;
- }
- mDeathRecipient = deathRecipient;
if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
// The multiplier here is to reserve space for multiple
@@ -1238,11 +1219,6 @@
return TouchOcclusionMode.BLOCK_UNTRUSTED;
}
- void attach() {
- if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
- mSession.windowAddedLocked();
- }
-
void updateGlobalScale() {
if (hasCompatScale()) {
mCompatScale = (mOverrideScale == 1f || mToken.hasSizeCompatBounds())
@@ -1270,13 +1246,14 @@
* @see ActivityRecord#hasSizeCompatBounds()
*/
boolean hasCompatScale() {
- if ((mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) {
- return true;
- }
if (mAttrs.type == TYPE_APPLICATION_STARTING) {
// Exclude starting window because it is not displayed by the application.
return false;
}
+ if (mWmService.mAtmService.mCompatModePackages.useLegacyScreenCompatMode(
+ mSession.mProcess.mInfo.packageName)) {
+ return true;
+ }
return mActivityRecord != null && mActivityRecord.hasSizeCompatBounds()
|| mOverrideScale != 1f;
}
@@ -2398,14 +2375,7 @@
disposeInputChannel();
mOnBackInvokedCallbackInfo = null;
- mSession.windowRemovedLocked();
- try {
- mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
- } catch (RuntimeException e) {
- // Ignore if it has already been removed (usually because
- // we are doing this as part of processing a death note.)
- }
-
+ mSession.onWindowRemoved(this);
mWmService.postWindowRemoveCleanupLocked(this);
mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
@@ -2935,31 +2905,6 @@
}
}
- private class DeathRecipient implements IBinder.DeathRecipient {
- @Override
- public void binderDied() {
- try {
- synchronized (mWmService.mGlobalLock) {
- final WindowState win = mWmService
- .windowForClientLocked(mSession, mClient, false);
- Slog.i(TAG, "WIN DEATH: " + win);
- if (win != null) {
- if (win.mActivityRecord != null
- && win.mActivityRecord.findMainWindow() == win) {
- mWmService.mSnapshotController.onAppDied(win.mActivityRecord);
- }
- win.removeIfPossible();
- } else if (mHasSurface) {
- Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
- WindowState.this.removeIfPossible();
- }
- }
- } catch (IllegalArgumentException ex) {
- // This will happen if the window has already been removed.
- }
- }
- }
-
/** Returns {@code true} if this window desires key events. */
boolean canReceiveKeys() {
return canReceiveKeys(false /* fromUserTouch */);
@@ -3830,7 +3775,7 @@
}
@Override
- public void notifyInsetsControlChanged() {
+ public void notifyInsetsControlChanged(int displayId) {
ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsControlChanged for %s ", this);
if (mRemoved) {
return;
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index c34e701..b19f3d8 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -37,7 +37,6 @@
"com_android_server_adb_AdbDebuggingManager.cpp",
"com_android_server_am_BatteryStatsService.cpp",
"com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
- "com_android_server_BootReceiver.cpp",
"com_android_server_ConsumerIrService.cpp",
"com_android_server_companion_virtual_InputController.cpp",
"com_android_server_devicepolicy_CryptoTestHelper.cpp",
@@ -94,16 +93,6 @@
header_libs: [
"bionic_libc_platform_headers",
],
-
- static_libs: [
- "libunwindstack",
- ],
-
- whole_static_libs: [
- "libdebuggerd_tombstone_proto_to_text",
- ],
-
- runtime_libs: ["libdexfile"],
}
cc_defaults {
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 0d762dfb..061fe0f 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -33,4 +33,3 @@
# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java
per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS
-per-file com_android_server_BootReceiver.cpp = file:/STABILITY_OWNERS
diff --git a/services/core/jni/com_android_server_BootReceiver.cpp b/services/core/jni/com_android_server_BootReceiver.cpp
deleted file mode 100644
index 3892d28..0000000
--- a/services/core/jni/com_android_server_BootReceiver.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <libdebuggerd/tombstone.h>
-#include <nativehelper/JNIHelp.h>
-
-#include <sstream>
-
-#include "jni.h"
-#include "tombstone.pb.h"
-
-namespace android {
-
-static void writeToString(std::stringstream& ss, const std::string& line, bool should_log) {
- ss << line << std::endl;
-}
-
-static jstring com_android_server_BootReceiver_getTombstoneText(JNIEnv* env, jobject,
- jbyteArray tombstoneBytes) {
- Tombstone tombstone;
- tombstone.ParseFromArray(env->GetByteArrayElements(tombstoneBytes, 0),
- env->GetArrayLength(tombstoneBytes));
-
- std::stringstream tombstoneString;
-
- tombstone_proto_to_text(tombstone,
- std::bind(&writeToString, std::ref(tombstoneString),
- std::placeholders::_1, std::placeholders::_2));
-
- return env->NewStringUTF(tombstoneString.str().c_str());
-}
-
-static const JNINativeMethod sMethods[] = {
- /* name, signature, funcPtr */
- {"getTombstoneText", "([B)Ljava/lang/String;",
- (jstring*)com_android_server_BootReceiver_getTombstoneText},
-};
-
-int register_com_android_server_BootReceiver(JNIEnv* env) {
- return jniRegisterNativeMethods(env, "com/android/server/BootReceiver", sMethods,
- NELEM(sMethods));
-}
-
-} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index deae82c..11734da 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -65,7 +65,6 @@
int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
int register_android_server_companion_virtual_InputController(JNIEnv* env);
int register_android_server_app_GameManagerService(JNIEnv* env);
-int register_com_android_server_BootReceiver(JNIEnv* env);
int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
int register_com_android_server_display_DisplayControl(JNIEnv* env);
int register_com_android_server_SystemClockTime(JNIEnv* env);
@@ -127,7 +126,6 @@
register_android_server_sensor_SensorService(vm, env);
register_android_server_companion_virtual_InputController(env);
register_android_server_app_GameManagerService(env);
- register_com_android_server_BootReceiver(env);
register_com_android_server_wm_TaskFpsCallbackController(env);
register_com_android_server_display_DisplayControl(env);
register_com_android_server_SystemClockTime(env);
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index c625b1e..adbd3c9 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -595,7 +595,7 @@
<!-- Sets the brightness mapping of the desired screen brightness to the corresponding
lux for the current display -->
<xs:element name="luxToBrightnessMapping" type="luxToBrightnessMapping"
- minOccurs="0" maxOccurs="1">
+ minOccurs="0" maxOccurs="unbounded">
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
@@ -619,12 +619,20 @@
This is used in place of config_autoBrightnessLevels and config_autoBrightnessLcdBacklightValues
defined in the config XML resource.
+
+ On devices that allow users to choose from a set of predefined options in display
+ auto-brightness settings, multiple mappings for different modes and settings can be defined.
+
+ If no mode is specified, the mapping will be used for the default mode.
+ If no setting is specified, the mapping will be used for the normal brightness setting.
-->
<xs:complexType name="luxToBrightnessMapping">
<xs:element name="map" type="nonNegativeFloatToFloatMap">
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
+ <xs:element name="mode" type="xs:string" minOccurs="0"/>
+ <xs:element name="setting" type="xs:string" minOccurs="0"/>
</xs:complexType>
<!-- Represents a point in the display brightness mapping, representing the lux level from the
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 8c8c123..98c95ed 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -8,13 +8,12 @@
method public final java.math.BigInteger getDarkeningLightDebounceIdleMillis();
method public final java.math.BigInteger getDarkeningLightDebounceMillis();
method public boolean getEnabled();
- method public final com.android.server.display.config.LuxToBrightnessMapping getLuxToBrightnessMapping();
+ method public final java.util.List<com.android.server.display.config.LuxToBrightnessMapping> getLuxToBrightnessMapping();
method public final void setBrighteningLightDebounceIdleMillis(java.math.BigInteger);
method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceIdleMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
method public void setEnabled(boolean);
- method public final void setLuxToBrightnessMapping(com.android.server.display.config.LuxToBrightnessMapping);
}
public class BlockingZoneConfig {
@@ -220,7 +219,11 @@
public class LuxToBrightnessMapping {
ctor public LuxToBrightnessMapping();
method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap();
+ method public String getMode();
+ method public String getSetting();
method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+ method public void setMode(String);
+ method public void setSetting(String);
}
public class NitsMap {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e0a2f30..a490013 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2469,7 +2469,7 @@
private void migratePersonalAppSuspensionLocked(
int doUserId, int poUserId, ActiveAdmin poAdmin) {
final PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
- if (!pmi.isSuspendingAnyPackages(PLATFORM_PACKAGE_NAME, doUserId)) {
+ if (!pmi.isAdminSuspendingAnyPackages(doUserId)) {
Slogf.i(LOG_TAG, "DO is not suspending any apps.");
return;
}
@@ -2480,7 +2480,7 @@
poAdmin.mSuspendPersonalApps = true;
} else {
Slogf.i(LOG_TAG, "PO isn't targeting R+, unsuspending personal apps.");
- pmi.unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, doUserId);
+ pmi.unsuspendAdminSuspendedPackages(doUserId);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 6570ce1..506dbe8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -16,8 +16,6 @@
package com.android.server.devicepolicy;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppGlobals;
@@ -287,7 +285,7 @@
suspendPersonalAppsInPackageManager(context, userId);
} else {
LocalServices.getService(PackageManagerInternal.class)
- .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, userId);
+ .unsuspendAdminSuspendedPackages(userId);
}
});
return true;
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
index eae159f..8ff1a7d 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -1,7 +1,7 @@
<manifest version="1.0" type="framework">
<hal format="aidl">
<name>android.frameworks.location.altitude</name>
- <version>1</version>
+ <version>2</version>
<fqname>IAltitudeService/default</fqname>
</hal>
<hal format="aidl">
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index f69f628..022268d 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -1262,7 +1262,7 @@
val apexModuleName = packageState.apexModuleName
val packageName = packageState.packageName
return when {
- packageState.isVendor ->
+ packageState.isVendor || packageState.isOdm ->
permissionAllowlist.getVendorPrivilegedAppAllowlistState(
packageName,
permissionName
@@ -1471,12 +1471,15 @@
// In any case, don't grant a privileged permission to privileged vendor apps,
// if the permission's protectionLevel does not have the extra vendorPrivileged
// flag.
- if (packageState.isVendor && !permission.isVendorPrivileged) {
+ if (
+ (packageState.isVendor || packageState.isOdm) &&
+ !permission.isVendorPrivileged
+ ) {
Slog.w(
LOG_TAG,
"Permission $permissionName cannot be granted to privileged" +
- " vendor app $packageName because it isn't a vendorPrivileged" +
- " permission"
+ " vendor (or odm) app $packageName because it isn't a" +
+ " vendorPrivileged permission"
)
return false
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 3d0b389..6fff012 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -36,6 +36,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -50,6 +51,7 @@
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
import android.os.BaseBundle;
import android.os.Message;
import android.os.PersistableBundle;
@@ -57,6 +59,9 @@
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -122,6 +127,8 @@
private static final String PACKAGE_NAME_3 = "com.android.app3";
private static final int TEST_RESOURCE_ID = 2131231283;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock
RuntimePermissionsPersistence mRuntimePermissionsPersistence;
@Mock
@@ -425,7 +432,7 @@
PackageUserStateInternal packageUserState1 = ps1.readUserState(0);
assertThat(packageUserState1.isSuspended(), is(true));
assertThat(packageUserState1.getSuspendParams().size(), is(1));
- assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android"));
+ assertThat(packageUserState1.getSuspendParams().keyAt(0), is(UserPackage.of(0, "android")));
assertThat(packageUserState1.getSuspendParams().valueAt(0).getAppExtras(), is(nullValue()));
assertThat(packageUserState1.getSuspendParams().valueAt(0).getDialogInfo(),
is(nullValue()));
@@ -437,7 +444,7 @@
packageUserState1 = ps1.readUserState(0);
assertThat(packageUserState1.isSuspended(), is(true));
assertThat(packageUserState1.getSuspendParams().size(), is(1));
- assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android"));
+ assertThat(packageUserState1.getSuspendParams().keyAt(0), is(UserPackage.of(0, "android")));
assertThat(packageUserState1.getSuspendParams().valueAt(0).getAppExtras(), is(nullValue()));
assertThat(packageUserState1.getSuspendParams().valueAt(0).getDialogInfo(),
is(nullValue()));
@@ -472,7 +479,8 @@
watcher.verifyNoChangeReported("readUserState");
assertThat(packageUserState1.isSuspended(), is(true));
assertThat(packageUserState1.getSuspendParams().size(), is(1));
- assertThat(packageUserState1.getSuspendParams().keyAt(0), is(PACKAGE_NAME_3));
+ assertThat(packageUserState1.getSuspendParams().keyAt(0),
+ is(UserPackage.of(0, PACKAGE_NAME_3)));
final SuspendParams params = packageUserState1.getSuspendParams().valueAt(0);
watcher.verifyNoChangeReported("fetch user state");
assertThat(params, is(notNullValue()));
@@ -523,19 +531,24 @@
.setNeutralButtonAction(BUTTON_ACTION_UNSUSPEND)
.build();
- ps1.modifyUserState(0).putSuspendParams("suspendingPackage1",
+ UserPackage suspender1 = UserPackage.of(0, "suspendingPackage1");
+ UserPackage suspender2 = UserPackage.of(0, "suspendingPackage2");
+ UserPackage suspender3 = UserPackage.of(0, "suspendingPackage3");
+ UserPackage irrelevantSuspender = UserPackage.of(0, "irrelevant");
+
+ ps1.modifyUserState(0).putSuspendParams(suspender1,
new SuspendParams(dialogInfo1, appExtras1, launcherExtras1));
- ps1.modifyUserState(0).putSuspendParams("suspendingPackage2",
+ ps1.modifyUserState(0).putSuspendParams(suspender2,
new SuspendParams(dialogInfo2, appExtras2, launcherExtras2));
settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
watcher.verifyChangeReported("put package 1");
- ps2.modifyUserState(0).putSuspendParams("suspendingPackage3",
+ ps2.modifyUserState(0).putSuspendParams(suspender3,
new SuspendParams(null, appExtras1, null));
settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
watcher.verifyChangeReported("put package 2");
- ps3.modifyUserState(0).removeSuspension("irrelevant");
+ ps3.modifyUserState(0).removeSuspension(irrelevantSuspender);
settingsUnderTest.mPackages.put(PACKAGE_NAME_3, ps3);
watcher.verifyChangeReported("put package 3");
@@ -560,7 +573,7 @@
assertThat(readPus1.getSuspendParams().size(), is(2));
watcher.verifyNoChangeReported("read package param");
- assertThat(readPus1.getSuspendParams().keyAt(0), is("suspendingPackage1"));
+ assertThat(readPus1.getSuspendParams().keyAt(0), is(suspender1));
final SuspendParams params11 = readPus1.getSuspendParams().valueAt(0);
watcher.verifyNoChangeReported("read package param");
assertThat(params11, is(notNullValue()));
@@ -570,7 +583,7 @@
is(true));
watcher.verifyNoChangeReported("read package param");
- assertThat(readPus1.getSuspendParams().keyAt(1), is("suspendingPackage2"));
+ assertThat(readPus1.getSuspendParams().keyAt(1), is(suspender2));
final SuspendParams params12 = readPus1.getSuspendParams().valueAt(1);
assertThat(params12, is(notNullValue()));
assertThat(params12.getDialogInfo(), is(dialogInfo2));
@@ -583,7 +596,7 @@
.readUserState(0);
assertThat(readPus2.isSuspended(), is(true));
assertThat(readPus2.getSuspendParams().size(), is(1));
- assertThat(readPus2.getSuspendParams().keyAt(0), is("suspendingPackage3"));
+ assertThat(readPus2.getSuspendParams().keyAt(0), is(suspender3));
final SuspendParams params21 = readPus2.getSuspendParams().valueAt(0);
assertThat(params21, is(notNullValue()));
assertThat(params21.getDialogInfo(), is(nullValue()));
@@ -1087,6 +1100,8 @@
new File(InstrumentationRegistry.getContext().getFilesDir(), "com.android.bar-1");
private static final File UPDATED_CODE_PATH =
new File(InstrumentationRegistry.getContext().getFilesDir(), "com.android.bar-2");
+ private static final File UPDATED_CODE_PATH2 =
+ new File(InstrumentationRegistry.getContext().getFilesDir(), "com.android.bar-3");
private static final long INITIAL_VERSION_CODE = 10023L;
private static final long UPDATED_VERSION_CODE = 10025L;
@@ -1134,7 +1149,8 @@
.setNeutralButtonText(0x11220003)
.setNeutralButtonAction(BUTTON_ACTION_MORE_DETAILS)
.build();
- origPkgSetting01.modifyUserState(0).putSuspendParams("suspendingPackage1",
+ origPkgSetting01.modifyUserState(0).putSuspendParams(
+ UserPackage.of(0, "suspendingPackage1"),
new SuspendParams(dialogInfo1, appExtras1, launcherExtras1));
origPkgSetting01.setPkg(mockAndroidPackage(origPkgSetting01));
final PackageSetting testPkgSetting01 = new PackageSetting(
@@ -1182,13 +1198,16 @@
null /*mimeGroups*/,
UUID.randomUUID(),
34 /*targetSdkVersion*/,
- null /*restrictUpdateHash*/);
+ null /*restrictUpdateHash*/,
+ false /*isDontKill*/);
assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
assertThat(testPkgSetting01.getSecondaryCpuAbiLegacy(), is("armeabi"));
assertThat(testPkgSetting01.getFlags(), is(0));
assertThat(testPkgSetting01.getPrivateFlags(), is(0));
+ assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
+ assertNull(testPkgSetting01.getOldPaths());
final PackageUserState userState = testPkgSetting01.readUserState(0);
verifyUserState(userState, false /*notLaunched*/,
false /*stopped*/, false /*installed*/);
@@ -1223,7 +1242,8 @@
null /*mimeGroups*/,
UUID.randomUUID(),
34 /*targetSdkVersion*/,
- null /*restrictUpdateHash*/);
+ null /*restrictUpdateHash*/,
+ false /*isDontKill*/);
assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
@@ -1237,6 +1257,74 @@
false /*stopped*/, true /*installed*/);
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_IMPROVE_INSTALL_DONT_KILL)
+ public void testUpdatePackageSettings02WithOldPaths() throws PackageManagerException {
+ final PackageSetting testPkgSetting01 =
+ createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
+ testPkgSetting01.setInstalled(false /*installed*/, 0 /*userId*/);
+ assertThat(testPkgSetting01.getFlags(), is(0));
+ assertThat(testPkgSetting01.getPrivateFlags(), is(0));
+ final PackageSetting oldPkgSetting01 = new PackageSetting(testPkgSetting01);
+ Settings.updatePackageSetting(
+ testPkgSetting01,
+ null /*disabledPkg*/,
+ null /*existingSharedUserSetting*/,
+ null /*sharedUser*/,
+ UPDATED_CODE_PATH /*codePath*/,
+ null /*legacyNativeLibraryPath*/,
+ "arm64-v8a" /*primaryCpuAbi*/,
+ "armeabi" /*secondaryCpuAbi*/,
+ ApplicationInfo.FLAG_SYSTEM /*pkgFlags*/,
+ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED /*pkgPrivateFlags*/,
+ UserManagerService.getInstance(),
+ null /*usesSdkLibraries*/,
+ null /*usesSdkLibrariesVersions*/,
+ null /*usesSdkLibrariesOptional*/,
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/,
+ null /*mimeGroups*/,
+ UUID.randomUUID(),
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/,
+ true /*isDontKill*/);
+ assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
+ assertThat(testPkgSetting01.getOldPaths().size(), is(1));
+ assertTrue(testPkgSetting01.getOldPaths().contains(INITIAL_CODE_PATH));
+ Settings.updatePackageSetting(
+ testPkgSetting01,
+ null /*disabledPkg*/,
+ null /*existingSharedUserSetting*/,
+ null /*sharedUser*/,
+ UPDATED_CODE_PATH2 /*codePath*/,
+ null /*legacyNativeLibraryPath*/,
+ "arm64-v8a" /*primaryCpuAbi*/,
+ "armeabi" /*secondaryCpuAbi*/,
+ ApplicationInfo.FLAG_SYSTEM /*pkgFlags*/,
+ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED /*pkgPrivateFlags*/,
+ UserManagerService.getInstance(),
+ null /*usesSdkLibraries*/,
+ null /*usesSdkLibrariesVersions*/,
+ null /*usesSdkLibrariesOptional*/,
+ null /*usesStaticLibraries*/,
+ null /*usesStaticLibrariesVersions*/,
+ null /*mimeGroups*/,
+ UUID.randomUUID(),
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/,
+ true /*isDontKill*/);
+ assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH2));
+ assertThat(testPkgSetting01.getOldPaths().size(), is(2));
+ int index = 0;
+ for (File path : testPkgSetting01.getOldPaths()) {
+ switch (index) {
+ case 0 -> assertThat(path, is(INITIAL_CODE_PATH));
+ case 1 -> assertThat(path, is(UPDATED_CODE_PATH));
+ }
+ index++;
+ }
+ }
+
/** Update package; changing shared user throws exception */
@Test
public void testUpdatePackageSetting03() {
@@ -1266,7 +1354,8 @@
null /*mimeGroups*/,
UUID.randomUUID(),
34 /*targetSdkVersion*/,
- null /*restrictUpdateHash*/);
+ null /*restrictUpdateHash*/,
+ false /*isDontKill*/);
fail("Expected a PackageManagerException");
} catch (PackageManagerException expected) {
}
@@ -1672,6 +1761,63 @@
assertSame(sus1, settings.getSharedUserSettingLPr(ps1));
}
+ @Test
+ public void testAddRemoveOldPaths() {
+ final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
+ ps1.addOldPath(INITIAL_CODE_PATH);
+ ps1.addOldPath(UPDATED_CODE_PATH);
+ ps1.addOldPath(UPDATED_CODE_PATH2);
+ assertThat(ps1.getOldPaths().size(), is(3));
+ int index = 0;
+ for (File path : ps1.getOldPaths()) {
+ switch (index) {
+ case 0 -> assertThat(path, is(INITIAL_CODE_PATH));
+ case 1 -> assertThat(path, is(UPDATED_CODE_PATH));
+ case 2 -> assertThat(path, is(UPDATED_CODE_PATH2));
+ }
+ index++;
+ }
+
+ ps1.removeOldPath(UPDATED_CODE_PATH);
+ assertThat(ps1.getOldPaths().size(), is(2));
+ index = 0;
+ for (File path : ps1.getOldPaths()) {
+ switch (index) {
+ case 0 -> assertThat(path, is(INITIAL_CODE_PATH));
+ case 1 -> assertThat(path, is(UPDATED_CODE_PATH2));
+ }
+ index++;
+ }
+ ps1.removeOldPath(UPDATED_CODE_PATH2);
+ // assert not throws when deleting non-existing element
+ ps1.removeOldPath(UPDATED_CODE_PATH2);
+ assertThat(ps1.getOldPaths().size(), is(1));
+ assertTrue(ps1.getOldPaths().contains(INITIAL_CODE_PATH));
+ ps1.removeOldPath(INITIAL_CODE_PATH);
+ // assert not throws when deleting from an empty list
+ ps1.removeOldPath(INITIAL_CODE_PATH);
+ assertThat(ps1.getOldPaths().size(), is(0));
+ }
+
+ @Test
+ public void testOldPathsNotPreservedAfterReboot() {
+ Settings settings = makeSettings();
+ final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
+ ps1.setAppId(Process.FIRST_APPLICATION_UID);
+ ps1.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+ .setUid(ps1.getAppId())
+ .hideAsFinal());
+ ps1.addOldPath(INITIAL_CODE_PATH);
+ ps1.addOldPath(UPDATED_CODE_PATH);
+ settings.mPackages.put(PACKAGE_NAME_1, ps1);
+
+ settings.writeLPr(computer, /*sync=*/true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+ assertNull(settings.getPackageLPr(PACKAGE_NAME_1).getOldPaths());
+ }
+
private void verifyUserState(PackageUserState userState,
boolean notLaunched, boolean stopped, boolean installed) {
assertThat(userState.getEnabledState(), is(0));
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
index c0c7032..9780440 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
@@ -27,6 +27,7 @@
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.SuspendDialogInfo;
+import android.content.pm.UserPackage;
import android.content.pm.overlay.OverlayPaths;
import android.os.PersistableBundle;
import android.platform.test.annotations.Presubmit;
@@ -89,7 +90,7 @@
assertThat(testUserState.equals(oldUserState), is(false));
oldUserState = new PackageUserStateImpl();
- oldUserState.putSuspendParams("suspendingPackage",
+ oldUserState.putSuspendParams(UserPackage.of(0, "suspendingPackage"),
new SuspendParams(null, new PersistableBundle(), null));
assertThat(testUserState.equals(oldUserState), is(false));
@@ -220,6 +221,8 @@
final PersistableBundle launcherExtras2 = createPersistableBundle(null, 0, "name",
"launcherExtras2", null, 0);
+ final int suspendingUser1 = 0;
+ final int suspendingUser2 = 10;
final String suspendingPackage1 = "package1";
final String suspendingPackage2 = "package2";
@@ -230,12 +233,12 @@
.setMessage("dialogMessage2")
.build();
- final ArrayMap<String, SuspendParams> paramsMap1 = new ArrayMap<>();
- paramsMap1.put(suspendingPackage1, createSuspendParams(dialogInfo1, appExtras1,
- launcherExtras1));
- final ArrayMap<String, SuspendParams> paramsMap2 = new ArrayMap<>();
- paramsMap2.put(suspendingPackage2, createSuspendParams(dialogInfo2,
- appExtras2, launcherExtras2));
+ final ArrayMap<UserPackage, SuspendParams> paramsMap1 = new ArrayMap<>();
+ paramsMap1.put(UserPackage.of(suspendingUser1, suspendingPackage1),
+ createSuspendParams(dialogInfo1, appExtras1, launcherExtras1));
+ final ArrayMap<UserPackage, SuspendParams> paramsMap2 = new ArrayMap<>();
+ paramsMap2.put(UserPackage.of(suspendingUser2, suspendingPackage2),
+ createSuspendParams(dialogInfo2, appExtras2, launcherExtras2));
final PackageUserStateImpl testUserState1 = new PackageUserStateImpl();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index 189d9bb..c5a1ba1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -107,20 +107,6 @@
468.5f,
};
- private static final int[] DISPLAY_LEVELS_INT = {
- 9,
- 30,
- 45,
- 62,
- 78,
- 96,
- 119,
- 146,
- 178,
- 221,
- 255
- };
-
private static final float[] DISPLAY_LEVELS = {
0.03f,
0.11f,
@@ -172,62 +158,23 @@
DisplayWhiteBalanceController mMockDwbc;
@Test
- public void testSimpleStrategyMappingAtControlPoints_IntConfig() {
- Resources res = createResources(DISPLAY_LEVELS_INT);
- DisplayDeviceConfig ddc = createDdc();
+ public void testSimpleStrategyMappingAtControlPoints() {
+ Resources res = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
+ AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
assertNotNull("BrightnessMappingStrategy should not be null", simple);
for (int i = 0; i < LUX_LEVELS.length; i++) {
- final float expectedLevel = MathUtils.map(PowerManager.BRIGHTNESS_OFF + 1,
- PowerManager.BRIGHTNESS_ON, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_INT[i]);
- assertEquals(expectedLevel,
- simple.getBrightness(LUX_LEVELS[i]), 0.0001f /*tolerance*/);
+ assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]), TOLERANCE);
}
}
@Test
- public void testSimpleStrategyMappingBetweenControlPoints_IntConfig() {
- Resources res = createResources(DISPLAY_LEVELS_INT);
- DisplayDeviceConfig ddc = createDdc();
+ public void testSimpleStrategyMappingBetweenControlPoints() {
+ Resources res = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
- assertNotNull("BrightnessMappingStrategy should not be null", simple);
- for (int i = 1; i < LUX_LEVELS.length; i++) {
- final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
- final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
- assertTrue("Desired brightness should be between adjacent control points.",
- backlight > DISPLAY_LEVELS_INT[i - 1]
- && backlight < DISPLAY_LEVELS_INT[i]);
- }
- }
-
- @Test
- public void testSimpleStrategyMappingAtControlPoints_FloatConfig() {
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY, EMPTY_FLOAT_ARRAY, LUX_LEVELS,
- EMPTY_FLOAT_ARRAY, DISPLAY_LEVELS);
- BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
- assertNotNull("BrightnessMappingStrategy should not be null", simple);
- for (int i = 0; i < LUX_LEVELS.length; i++) {
- assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]),
- /* tolerance= */ 0.0001f);
- }
- }
-
- @Test
- public void testSimpleStrategyMappingBetweenControlPoints_FloatConfig() {
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY, EMPTY_FLOAT_ARRAY, LUX_LEVELS,
- EMPTY_FLOAT_ARRAY, DISPLAY_LEVELS);
- BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
+ AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
assertNotNull("BrightnessMappingStrategy should not be null", simple);
for (int i = 1; i < LUX_LEVELS.length; i++) {
final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
@@ -239,8 +186,8 @@
@Test
public void testSimpleStrategyIgnoresNewConfiguration() {
- Resources res = createResources(DISPLAY_LEVELS_INT);
- DisplayDeviceConfig ddc = createDdc();
+ Resources res = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
@@ -255,25 +202,23 @@
@Test
public void testSimpleStrategyIgnoresNullConfiguration() {
- Resources res = createResources(DISPLAY_LEVELS_INT);
- DisplayDeviceConfig ddc = createDdc();
+ Resources res = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
strategy.setBrightnessConfiguration(null);
- final int n = DISPLAY_LEVELS_INT.length;
- final float expectedBrightness =
- (float) DISPLAY_LEVELS_INT[n - 1] / PowerManager.BRIGHTNESS_ON;
+ final int n = DISPLAY_LEVELS.length;
+ final float expectedBrightness = DISPLAY_LEVELS[n - 1];
assertEquals(expectedBrightness,
strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/);
}
@Test
public void testPhysicalStrategyMappingAtControlPoints() {
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT,
- LUX_LEVELS, DISPLAY_LEVELS_NITS);
+ Resources res = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+ .build();
BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
assertNotNull("BrightnessMappingStrategy should not be null", physical);
@@ -290,9 +235,9 @@
@Test
public void testPhysicalStrategyMappingBetweenControlPoints() {
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
- LUX_LEVELS, DISPLAY_LEVELS_NITS);
+ Resources res = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+ .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
assertNotNull("BrightnessMappingStrategy should not be null", physical);
@@ -309,9 +254,9 @@
@Test
public void testPhysicalStrategyUsesNewConfigurations() {
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
+ Resources res = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+ .build();
BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
@@ -336,9 +281,9 @@
@Test
public void testPhysicalStrategyRecalculateSplines() {
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
+ Resources res = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+ .build();
BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length];
@@ -381,9 +326,10 @@
@Test
public void testDefaultStrategyIsPhysical() {
- Resources res = createResources(DISPLAY_LEVELS_INT);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
+ Resources res = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS)
+ .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+ .build();
BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
@@ -396,17 +342,17 @@
float tmp = lux[idx];
lux[idx] = lux[idx + 1];
lux[idx + 1] = tmp;
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
+ Resources res = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
+ .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
assertNull(strategy);
// And make sure we get the same result even if it's monotone but not increasing.
lux[idx] = lux[idx + 1];
- ddc = createDdc(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux,
- DISPLAY_LEVELS_NITS);
+ ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
+ .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
mMockDwbc);
assertNull(strategy);
@@ -419,25 +365,25 @@
// Make sure it's strictly increasing so that the only failure is the differing array
// lengths
lux[lux.length - 1] = lux[lux.length - 2] + 1;
- Resources res = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
+ Resources res = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
+ .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
assertNull(strategy);
- res = createResources(DISPLAY_LEVELS_INT);
+ ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
+ .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
+ .setAutoBrightnessLevels(DISPLAY_LEVELS).build();
strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
mMockDwbc);
assertNull(strategy);
// Extra backlight level
- final int[] backlight = Arrays.copyOf(
- DISPLAY_LEVELS_INT, DISPLAY_LEVELS_INT.length + 1);
+ final float[] backlight = Arrays.copyOf(DISPLAY_LEVELS, DISPLAY_LEVELS.length + 1);
backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
- res = createResources(backlight);
- ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, EMPTY_FLOAT_ARRAY);
+ res = createResources();
+ ddc = new DdcBuilder().setAutoBrightnessLevels(backlight).build();
strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
mMockDwbc);
assertNull(strategy);
@@ -445,9 +391,9 @@
// Extra nits level
final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length + 1);
nits[nits.length - 1] = nits[nits.length - 2] + 1;
- res = createResources(EMPTY_INT_ARRAY);
- ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, nits);
+ res = createResources();
+ ddc = new DdcBuilder().setAutoBrightnessLevelsNits(nits)
+ .setAutoBrightnessLevels(EMPTY_FLOAT_ARRAY).build();
strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
mMockDwbc);
assertNull(strategy);
@@ -455,40 +401,32 @@
@Test
public void testPhysicalStrategyRequiresNitsMapping() {
- Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
- DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY /*nitsRange*/);
+ Resources res = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setNitsRange(EMPTY_FLOAT_ARRAY).build();
BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
assertNull(physical);
-
- res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
- physical = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
- assertNull(physical);
-
- res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
- physical = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
- assertNull(physical);
}
@Test
public void testStrategiesAdaptToUserDataPoint() {
- Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
- LUX_LEVELS, DISPLAY_LEVELS_NITS);
+ Resources res = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+ .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc));
- ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
- res = createResources(DISPLAY_LEVELS_INT);
+ ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+ .setAutoBrightnessLevels(DISPLAY_LEVELS).build();
+ res = createResources();
assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc));
}
@Test
public void testIdleModeConfigLoadsCorrectly() {
- Resources res = createResourcesIdle(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
+ Resources res = createResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+ DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+ .build();
// Create an idle mode bms
// This will fail if it tries to fetch the wrong configuration.
@@ -562,17 +500,11 @@
assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/);
}
- private Resources createResources(int[] brightnessLevelsBacklight) {
- return createResources(brightnessLevelsBacklight, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
+ private Resources createResources() {
+ return createResources(EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
}
- private Resources createResourcesIdle(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
- return createResources(EMPTY_INT_ARRAY,
- luxLevelsIdle, brightnessLevelsNitsIdle);
- }
-
- private Resources createResources(int[] brightnessLevelsBacklight, int[] luxLevelsIdle,
- float[] brightnessLevelsNitsIdle) {
+ private Resources createResources(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
Resources mockResources = mock(Resources.class);
if (luxLevelsIdle.length > 0) {
@@ -583,10 +515,6 @@
.thenReturn(luxLevelsIdleResource);
}
- when(mockResources.getIntArray(
- com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
- .thenReturn(brightnessLevelsBacklight);
-
TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle);
when(mockResources.obtainTypedArray(
com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle))
@@ -604,41 +532,6 @@
return mockResources;
}
- private DisplayDeviceConfig createDdc() {
- return createDdc(DISPLAY_RANGE_NITS);
- }
-
- private DisplayDeviceConfig createDdc(float[] nitsArray) {
- return createDdc(nitsArray, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT);
- }
-
- private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray) {
- DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
- when(mockDdc.getNits()).thenReturn(nitsArray);
- when(mockDdc.getBrightness()).thenReturn(backlightArray);
- when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS);
- when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
- when(mockDdc.getAutoBrightnessBrighteningLevels()).thenReturn(EMPTY_FLOAT_ARRAY);
- return mockDdc;
- }
-
- private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
- float[] luxLevelsFloat, float[] brightnessLevelsNits) {
- return createDdc(nitsArray, backlightArray, luxLevelsFloat, brightnessLevelsNits,
- EMPTY_FLOAT_ARRAY);
- }
-
- private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
- float[] luxLevelsFloat, float[] brightnessLevelsNits, float[] brightnessLevels) {
- DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
- when(mockDdc.getNits()).thenReturn(nitsArray);
- when(mockDdc.getBrightness()).thenReturn(backlightArray);
- when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevelsFloat);
- when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits);
- when(mockDdc.getAutoBrightnessBrighteningLevels()).thenReturn(brightnessLevels);
- return mockDdc;
- }
-
private TypedArray createFloatTypedArray(float[] vals) {
TypedArray mockArray = mock(TypedArray.class);
when(mockArray.length()).thenAnswer(invocation -> {
@@ -677,10 +570,9 @@
final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
- Resources resources = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
- GAMMA_CORRECTION_NITS);
+ Resources resources = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
+ .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
// Let's start with a validity check:
@@ -708,10 +600,9 @@
final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
- Resources resources = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
- GAMMA_CORRECTION_NITS);
+ Resources resources = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
+ .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
// Validity check:
@@ -736,10 +627,9 @@
public void testGammaCorrectionExtremeChangeAtCenter() {
// Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we
// just make sure the adjustment reflects the change.
- Resources resources = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
- GAMMA_CORRECTION_NITS);
+ Resources resources = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
+ .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), /* delta= */ 0.0001f);
@@ -760,10 +650,9 @@
final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0);
final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4);
- Resources resources = createResources(EMPTY_INT_ARRAY);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
- DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
- GAMMA_CORRECTION_NITS);
+ Resources resources = createResources();
+ DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
+ .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
// Validity, as per tradition:
@@ -790,11 +679,54 @@
@Test
public void testGetMode() {
- Resources res = createResourcesIdle(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
- DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
+ Resources res = createResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+ DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
+ .build();
BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
AUTO_BRIGHTNESS_MODE_IDLE,
mMockDwbc);
assertEquals(AUTO_BRIGHTNESS_MODE_IDLE, strategy.getMode());
}
+
+ private static class DdcBuilder {
+ private DisplayDeviceConfig mDdc;
+
+ DdcBuilder() {
+ mDdc = mock(DisplayDeviceConfig.class);
+ when(mDdc.getNits()).thenReturn(DISPLAY_RANGE_NITS);
+ when(mDdc.getBrightness()).thenReturn(DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT);
+ when(mDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS);
+ when(mDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
+ when(mDdc.getAutoBrightnessBrighteningLevels()).thenReturn(EMPTY_FLOAT_ARRAY);
+ }
+
+ DdcBuilder setNitsRange(float[] nitsArray) {
+ when(mDdc.getNits()).thenReturn(nitsArray);
+ return this;
+ }
+
+ DdcBuilder setBrightnessRange(float[] brightnessArray) {
+ when(mDdc.getBrightness()).thenReturn(brightnessArray);
+ return this;
+ }
+
+ DdcBuilder setAutoBrightnessLevelsLux(float[] luxLevels) {
+ when(mDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevels);
+ return this;
+ }
+
+ DdcBuilder setAutoBrightnessLevelsNits(float[] brightnessLevelsNits) {
+ when(mDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits);
+ return this;
+ }
+
+ DdcBuilder setAutoBrightnessLevels(float[] brightnessLevels) {
+ when(mDdc.getAutoBrightnessBrighteningLevels()).thenReturn(brightnessLevels);
+ return this;
+ }
+
+ DisplayDeviceConfig build() {
+ return mDdc;
+ }
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 31d7e88..a4c15b5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -17,6 +17,7 @@
package com.android.server.display;
+import static com.android.internal.display.BrightnessSynchronizer.brightnessIntToFloat;
import static com.android.server.display.config.SensorData.SupportedMode;
import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
@@ -47,7 +48,6 @@
import androidx.test.filters.SmallTest;
import com.android.internal.R;
-import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.config.HdrBrightnessData;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -609,6 +609,9 @@
float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA);
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), new
+ float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
+ brightnessIntToFloat(150)}, SMALL_DELTA);
// Test thresholds
assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
@@ -674,7 +677,7 @@
assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type);
assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name);
- assertEquals(BrightnessSynchronizer.brightnessIntToFloat(35),
+ assertEquals(brightnessIntToFloat(35),
mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
}
@@ -737,6 +740,27 @@
mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA);
assertArrayEquals(new float[]{0.2f, 0.3f},
mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA);
+
+ assertArrayEquals(new float[]{0.0f, 90},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("default", "dim"),
+ ZERO_DELTA);
+ assertArrayEquals(new float[]{0.3f, 0.4f},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("default", "dim"),
+ SMALL_DELTA);
+
+ assertArrayEquals(new float[]{0.0f, 95},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("doze", "normal"),
+ ZERO_DELTA);
+ assertArrayEquals(new float[]{0.35f, 0.45f},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("doze", "normal"),
+ SMALL_DELTA);
+
+ assertArrayEquals(new float[]{0.0f, 100},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("doze", "bright"),
+ ZERO_DELTA);
+ assertArrayEquals(new float[]{0.4f, 0.5f},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("doze", "bright"),
+ SMALL_DELTA);
}
@Test
@@ -746,7 +770,9 @@
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
getValidProxSensor(), /* includeIdleMode= */ false));
- assertNull(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels());
+ assertArrayEquals(new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
+ brightnessIntToFloat(150)},
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA);
assertArrayEquals(new float[]{0, 110, 500},
mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA);
assertArrayEquals(new float[]{2, 200, 600},
@@ -1138,6 +1164,46 @@
+ "</point>\n"
+ "</map>\n"
+ "</luxToBrightnessMapping>\n"
+ + "<luxToBrightnessMapping>\n"
+ + "<setting>dim</setting>\n"
+ + "<map>\n"
+ + "<point>\n"
+ + "<first>0</first>\n"
+ + "<second>0.3</second>\n"
+ + "</point>\n"
+ + "<point>\n"
+ + "<first>90</first>\n"
+ + "<second>0.4</second>\n"
+ + "</point>\n"
+ + "</map>\n"
+ + "</luxToBrightnessMapping>\n"
+ + "<luxToBrightnessMapping>\n"
+ + "<mode>doze</mode>\n"
+ + "<map>\n"
+ + "<point>\n"
+ + "<first>0</first>\n"
+ + "<second>0.35</second>\n"
+ + "</point>\n"
+ + "<point>\n"
+ + "<first>95</first>\n"
+ + "<second>0.45</second>\n"
+ + "</point>\n"
+ + "</map>\n"
+ + "</luxToBrightnessMapping>\n"
+ + "<luxToBrightnessMapping>\n"
+ + "<mode>doze</mode>\n"
+ + "<setting>bright</setting>\n"
+ + "<map>\n"
+ + "<point>\n"
+ + "<first>0</first>\n"
+ + "<second>0.4</second>\n"
+ + "</point>\n"
+ + "<point>\n"
+ + "<first>100</first>\n"
+ + "<second>0.5</second>\n"
+ + "</point>\n"
+ + "</map>\n"
+ + "</luxToBrightnessMapping>\n"
+ "</autoBrightness>\n"
+ getPowerThrottlingConfig()
+ "<highBrightnessMode enabled=\"true\">\n"
@@ -1435,6 +1501,10 @@
when(mResources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLevels))
.thenReturn(screenBrightnessLevelLux);
+ int[] screenBrightnessLevels = new int[]{50, 100, 150};
+ when(mResources.getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
+ .thenReturn(screenBrightnessLevels);
// Thresholds
// Config.xml requires the levels arrays to be of length N and the thresholds arrays to be
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
index dea838d..fbb14c3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -88,4 +89,12 @@
verify(mDisplayPowerController).setBrightnessFromOffload(brightness);
}
+
+ @Test
+ public void testBlockScreenOn() {
+ Runnable unblocker = () -> {};
+ mSession.blockScreenOn(unblocker);
+
+ verify(mDisplayOffloader).onBlockingScreenOn(eq(unblocker));
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index f36854b..00f9892 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -206,6 +206,9 @@
when(mMockedResources.getIntArray(
com.android.internal.R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
.thenReturn(new int[]{});
+ when(mMockedResources.getIntArray(
+ com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
+ .thenReturn(new int[]{});
doReturn(true).when(mFlags).isDisplayOffloadEnabled();
initDisplayOffloadSession();
}
@@ -1235,6 +1238,9 @@
@Override
public void stopOffload() {}
+
+ @Override
+ public void onBlockingScreenOn(Runnable unblocker) {}
});
mDisplayOffloadSession = new DisplayOffloadSessionImpl(mDisplayOffloader,
diff --git a/services/tests/media/OWNERS b/services/tests/media/OWNERS
new file mode 100644
index 0000000..160767a6
--- /dev/null
+++ b/services/tests/media/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137631
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/services/tests/media/mediarouterservicetest/Android.bp b/services/tests/media/mediarouterservicetest/Android.bp
new file mode 100644
index 0000000..aed3af6
--- /dev/null
+++ b/services/tests/media/mediarouterservicetest/Android.bp
@@ -0,0 +1,39 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "MediaRouterServiceTests",
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "compatibility-device-util-axt",
+ "junit",
+ "platform-test-annotations",
+ "services.core",
+ "truth",
+ ],
+
+ platform_apis: true,
+
+ test_suites: [
+ // "device-tests",
+ "general-tests",
+ ],
+
+ certificate: "platform",
+ dxflags: ["--multi-dex"],
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/media/mediarouterservicetest/AndroidManifest.xml b/services/tests/media/mediarouterservicetest/AndroidManifest.xml
new file mode 100644
index 0000000..fe65f86
--- /dev/null
+++ b/services/tests/media/mediarouterservicetest/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.media.tests">
+
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
+
+ <application android:testOnly="true" android:debuggable="true">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.media.tests"
+ android:label="Frameworks Services Tests"/>
+</manifest>
diff --git a/services/tests/media/mediarouterservicetest/AndroidTest.xml b/services/tests/media/mediarouterservicetest/AndroidTest.xml
new file mode 100644
index 0000000..b065681
--- /dev/null
+++ b/services/tests/media/mediarouterservicetest/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<configuration description="Runs MediaRouter Service tests.">
+ <option name="test-tag" value="MediaRouterServiceTests" />
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="MediaRouterServiceTests.apk"/>
+ <option name="install-arg" value="-t" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.server.media.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
new file mode 100644
index 0000000..6f9b6fa
--- /dev/null
+++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2023 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.media;
+
+import static com.android.server.media.AudioRoutingUtils.ATTRIBUTES_MEDIA;
+import static com.android.server.media.AudioRoutingUtils.getMediaAudioProductStrategy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioDevicePort;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.MediaRoute2Info;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.os.Looper;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class AudioPoliciesDeviceRouteControllerTest {
+
+ private static final String FAKE_ROUTE_NAME = "fake name";
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_SPEAKER, "name_builtin", /* address= */ null);
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_WIRED_HEADSET, "name_wired_hs", /* address= */ null);
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "name_a2dp", /* address= */ "12:34:45");
+
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_BUILTIN_EARPIECE =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_EARPIECE, /* name= */ null, /* address= */ null);
+
+ private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_NO_NAME =
+ createAudioDeviceInfo(
+ AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET,
+ /* name= */ null,
+ /* address= */ null);
+
+ private AudioDeviceInfo mSelectedAudioDeviceInfo;
+ private Set<AudioDeviceInfo> mAvailableAudioDeviceInfos;
+ @Mock private AudioManager mMockAudioManager;
+ @Mock private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+ private AudioPoliciesDeviceRouteController mControllerUnderTest;
+ private AudioDeviceCallback mAudioDeviceCallback;
+ private AudioProductStrategy mMediaAudioProductStrategy;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ Resources mockResources = Mockito.mock(Resources.class);
+ when(mockResources.getText(anyInt())).thenReturn(FAKE_ROUTE_NAME);
+ Context realContext = InstrumentationRegistry.getInstrumentation().getContext();
+ Context mockContext = Mockito.mock(Context.class);
+ when(mockContext.getResources()).thenReturn(mockResources);
+ // The bluetooth stack needs the application info, but we cannot use a spy because the
+ // concrete class is package private, so we just return the application info through the
+ // mock.
+ when(mockContext.getApplicationInfo()).thenReturn(realContext.getApplicationInfo());
+
+ // Setup the initial state so that the route controller is created in a sensible state.
+ mSelectedAudioDeviceInfo = FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER;
+ mAvailableAudioDeviceInfos = Set.of(FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER);
+ updateMockAudioManagerState();
+ mMediaAudioProductStrategy = getMediaAudioProductStrategy();
+
+ BluetoothAdapter btAdapter =
+ realContext.getSystemService(BluetoothManager.class).getAdapter();
+ mControllerUnderTest =
+ new AudioPoliciesDeviceRouteController(
+ mockContext,
+ mMockAudioManager,
+ Looper.getMainLooper(),
+ mMediaAudioProductStrategy,
+ btAdapter,
+ mOnDeviceRouteChangedListener);
+ mControllerUnderTest.start(UserHandle.CURRENT_OR_SELF);
+
+ ArgumentCaptor<AudioDeviceCallback> deviceCallbackCaptor =
+ ArgumentCaptor.forClass(AudioDeviceCallback.class);
+ verify(mMockAudioManager)
+ .registerAudioDeviceCallback(deviceCallbackCaptor.capture(), any());
+ mAudioDeviceCallback = deviceCallbackCaptor.getValue();
+
+ // We clear any invocations during setup.
+ clearInvocations(mOnDeviceRouteChangedListener);
+ }
+
+ @Test
+ public void getSelectedRoute_afterDevicesConnect_returnsRightSelectedRoute() {
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP);
+ verify(mOnDeviceRouteChangedListener).onDeviceRouteChanged();
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ null, // Selected device doesn't change.
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+ }
+
+ @Test
+ public void getSelectedRoute_afterDeviceRemovals_returnsExpectedRoutes() {
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
+ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ verify(mOnDeviceRouteChangedListener).onDeviceRouteChanged();
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP);
+ verify(mOnDeviceRouteChangedListener, times(2)).onDeviceRouteChanged();
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+
+ removeAvailableAudioDeviceInfos(
+ /* newSelectedDevice= */ null,
+ /* devicesToRemove...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+
+ removeAvailableAudioDeviceInfos(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER,
+ /* devicesToRemove...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ }
+
+ @Test
+ public void onAudioDevicesAdded_clearsAudioRoutingPoliciesCorrectly() {
+ clearInvocations(mMockAudioManager);
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ null, // Selected device doesn't change.
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_BUILTIN_EARPIECE);
+ verifyNoMoreInteractions(mMockAudioManager);
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP);
+ verify(mMockAudioManager).removePreferredDeviceForStrategy(mMediaAudioProductStrategy);
+ }
+
+ @Test
+ public void getAvailableDevices_ignoresInvalidMediaOutputs() {
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ null, // Selected device doesn't change.
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_BUILTIN_EARPIECE);
+ verifyNoMoreInteractions(mOnDeviceRouteChangedListener);
+ assertThat(
+ mControllerUnderTest.getAvailableRoutes().stream()
+ .map(MediaRoute2Info::getType)
+ .toList())
+ .containsExactly(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ assertThat(mControllerUnderTest.getSelectedRoute().getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ }
+
+ @Test
+ public void transferTo_setsTheExpectedRoutingPolicy() {
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
+ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+ MediaRoute2Info builtInSpeakerRoute =
+ getAvailableRouteWithType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ mControllerUnderTest.transferTo(builtInSpeakerRoute.getId());
+ verify(mMockAudioManager)
+ .setPreferredDeviceForStrategy(
+ mMediaAudioProductStrategy,
+ createAudioDeviceAttribute(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER));
+
+ MediaRoute2Info wiredHeadsetRoute =
+ getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET);
+ mControllerUnderTest.transferTo(wiredHeadsetRoute.getId());
+ verify(mMockAudioManager)
+ .setPreferredDeviceForStrategy(
+ mMediaAudioProductStrategy,
+ createAudioDeviceAttribute(AudioDeviceInfo.TYPE_WIRED_HEADSET));
+ }
+
+ @Test
+ public void updateVolume_propagatesCorrectlyToRouteInfo() {
+ when(mMockAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)).thenReturn(2);
+ when(mMockAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)).thenReturn(3);
+ when(mMockAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC)).thenReturn(1);
+ when(mMockAudioManager.isVolumeFixed()).thenReturn(false);
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
+
+ MediaRoute2Info selectedRoute = mControllerUnderTest.getSelectedRoute();
+ assertThat(selectedRoute.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADSET);
+ assertThat(selectedRoute.getVolume()).isEqualTo(2);
+ assertThat(selectedRoute.getVolumeMax()).isEqualTo(3);
+ assertThat(selectedRoute.getVolumeHandling())
+ .isEqualTo(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE);
+
+ MediaRoute2Info onlyTransferrableRoute =
+ mControllerUnderTest.getAvailableRoutes().stream()
+ .filter(it -> !it.equals(selectedRoute))
+ .findAny()
+ .orElseThrow();
+ assertThat(onlyTransferrableRoute.getType())
+ .isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ assertThat(onlyTransferrableRoute.getVolume()).isEqualTo(0);
+ assertThat(onlyTransferrableRoute.getVolumeMax()).isEqualTo(0);
+ assertThat(onlyTransferrableRoute.getVolume()).isEqualTo(0);
+ assertThat(onlyTransferrableRoute.getVolumeHandling())
+ .isEqualTo(MediaRoute2Info.PLAYBACK_VOLUME_FIXED);
+
+ when(mMockAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)).thenReturn(0);
+ when(mMockAudioManager.isVolumeFixed()).thenReturn(true);
+ mControllerUnderTest.updateVolume(0);
+ MediaRoute2Info newSelectedRoute = mControllerUnderTest.getSelectedRoute();
+ assertThat(newSelectedRoute.getVolume()).isEqualTo(0);
+ assertThat(newSelectedRoute.getVolumeHandling())
+ .isEqualTo(MediaRoute2Info.PLAYBACK_VOLUME_FIXED);
+ }
+
+ @Test
+ public void getAvailableRoutes_whenNoProductNameIsProvided_usesTypeToPopulateName() {
+ assertThat(mControllerUnderTest.getSelectedRoute().getName().toString())
+ .isEqualTo(FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER.getProductName().toString());
+
+ addAvailableAudioDeviceInfo(
+ /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_NO_NAME,
+ /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_NO_NAME);
+
+ MediaRoute2Info selectedRoute = mControllerUnderTest.getSelectedRoute();
+ assertThat(selectedRoute.getName().toString()).isEqualTo(FAKE_ROUTE_NAME);
+ }
+
+ // Internal methods.
+
+ @NonNull
+ private MediaRoute2Info getAvailableRouteWithType(int type) {
+ return mControllerUnderTest.getAvailableRoutes().stream()
+ .filter(it -> it.getType() == type)
+ .findFirst()
+ .orElseThrow();
+ }
+
+ private void addAvailableAudioDeviceInfo(
+ @Nullable AudioDeviceInfo newSelectedDevice, AudioDeviceInfo... newAvailableDevices) {
+ Set<AudioDeviceInfo> newAvailableDeviceInfos = new HashSet<>(mAvailableAudioDeviceInfos);
+ newAvailableDeviceInfos.addAll(List.of(newAvailableDevices));
+ mAvailableAudioDeviceInfos = newAvailableDeviceInfos;
+ if (newSelectedDevice != null) {
+ mSelectedAudioDeviceInfo = newSelectedDevice;
+ }
+ updateMockAudioManagerState();
+ mAudioDeviceCallback.onAudioDevicesAdded(newAvailableDevices);
+ }
+
+ private void removeAvailableAudioDeviceInfos(
+ @Nullable AudioDeviceInfo newSelectedDevice, AudioDeviceInfo... devicesToRemove) {
+ Set<AudioDeviceInfo> newAvailableDeviceInfos = new HashSet<>(mAvailableAudioDeviceInfos);
+ List.of(devicesToRemove).forEach(newAvailableDeviceInfos::remove);
+ mAvailableAudioDeviceInfos = newAvailableDeviceInfos;
+ if (newSelectedDevice != null) {
+ mSelectedAudioDeviceInfo = newSelectedDevice;
+ }
+ updateMockAudioManagerState();
+ mAudioDeviceCallback.onAudioDevicesRemoved(devicesToRemove);
+ }
+
+ private void updateMockAudioManagerState() {
+ when(mMockAudioManager.getDevicesForAttributes(ATTRIBUTES_MEDIA))
+ .thenReturn(
+ List.of(createAudioDeviceAttribute(mSelectedAudioDeviceInfo.getType())));
+ when(mMockAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
+ .thenReturn(mAvailableAudioDeviceInfos.toArray(new AudioDeviceInfo[0]));
+ }
+
+ private static AudioDeviceAttributes createAudioDeviceAttribute(int type) {
+ // Address is unused.
+ return new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT, type, /* address= */ "");
+ }
+
+ private static AudioDeviceInfo createAudioDeviceInfo(
+ int type, @NonNull String name, @NonNull String address) {
+ return new AudioDeviceInfo(AudioDevicePort.createForTesting(type, name, address));
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 3b83e3c..fc2e5b0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -40,6 +40,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
@@ -109,6 +110,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.function.Supplier;
@RunWith(AndroidJUnit4.class)
@@ -126,6 +128,8 @@
private MockitoSession mMockingSession;
private String mPackageName;
+ private Map<String, Integer> mPackageCategories;
+ private Map<String, Integer> mPackageUids;
private TestLooper mTestLooper;
@Mock
private PackageManager mMockPackageManager;
@@ -229,34 +233,50 @@
.strictness(Strictness.WARN)
.startMocking();
mMockContext = new MockContext(InstrumentationRegistry.getContext());
+ mPackageCategories = new HashMap<>();
+ mPackageUids = new HashMap<>();
mPackageName = mMockContext.getPackageName();
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
+ LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE);
+ mSetFlagsRule.disableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
+ }
+
+ private void mockAppCategory(String packageName, int packageUid,
+ @ApplicationInfo.Category int category)
+ throws Exception {
+ reset(mMockPackageManager);
+ mPackageCategories.put(packageName, category);
+ mPackageUids.put(packageName, packageUid);
+ final List<PackageInfo> packageInfos = new ArrayList<>();
+ for (Map.Entry<String, Integer> entry : mPackageCategories.entrySet()) {
+
+ packageName = entry.getKey();
+ packageUid = mPackageUids.get(packageName);
+ category = entry.getValue();
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = packageName;
+ applicationInfo.category = category;
+ when(mMockPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+
+ final PackageInfo pi = new PackageInfo();
+ pi.packageName = packageName;
+ pi.applicationInfo = applicationInfo;
+ packageInfos.add(pi);
+
+ when(mMockPackageManager.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(
+ packageUid);
+ when(mMockPackageManager.getPackagesForUid(packageUid)).thenReturn(
+ new String[]{packageName});
+ }
+ when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
+ .thenReturn(packageInfos);
final Resources resources =
InstrumentationRegistry.getInstrumentation().getContext().getResources();
when(mMockPackageManager.getResourcesForApplication(anyString()))
.thenReturn(resources);
- when(mMockPackageManager.getPackageUidAsUser(mPackageName, USER_ID_1)).thenReturn(
- DEFAULT_PACKAGE_UID);
- LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
-
- mSetFlagsRule.enableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE);
- }
-
- private void mockAppCategory(String packageName, @ApplicationInfo.Category int category)
- throws Exception {
- reset(mMockPackageManager);
- final ApplicationInfo gameApplicationInfo = new ApplicationInfo();
- gameApplicationInfo.category = category;
- gameApplicationInfo.packageName = packageName;
- final PackageInfo pi = new PackageInfo();
- pi.packageName = packageName;
- pi.applicationInfo = gameApplicationInfo;
- final List<PackageInfo> packages = new ArrayList<>();
- packages.add(pi);
- when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
- .thenReturn(packages);
- when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
- .thenReturn(gameApplicationInfo);
}
@After
@@ -508,7 +528,7 @@
@Test
public void testGetGameMode_nonGame() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
mockModifyGameModeGranted();
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
@@ -780,7 +800,7 @@
@Test
public void testDeviceConfig_nonGame() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
mockDeviceConfigAll();
mockModifyGameModeGranted();
checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1));
@@ -1588,7 +1608,7 @@
@Test
public void testSetGameState_nonGame() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
mockDeviceConfigNone();
mockModifyGameModeGranted();
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
@@ -1611,14 +1631,14 @@
gameManagerService.addGameStateListener(mockListener);
verify(binder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
GameState gameState = new GameState(true, GameState.MODE_NONE);
gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
mTestLooper.dispatchAll();
verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
gameState = new GameState(true, GameState.MODE_NONE);
gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
@@ -1654,7 +1674,7 @@
gameManagerService.addGameStateListener(mockListener);
gameManagerService.removeGameStateListener(mockListener);
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
GameState gameState = new GameState(false, GameState.MODE_CONTENT);
gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
@@ -1670,13 +1690,17 @@
return output;
}
- private void mockInterventionListForMultipleUsers() {
+ private void mockInterventionListForMultipleUsers() throws Exception {
final String[] packageNames = new String[]{"com.android.app0",
"com.android.app1", "com.android.app2"};
+ int i = 1;
+ for (String p : packageNames) {
+ mockAppCategory(p, DEFAULT_PACKAGE_UID + i++, ApplicationInfo.CATEGORY_GAME);
+ }
final ApplicationInfo[] applicationInfos = new ApplicationInfo[3];
final PackageInfo[] pis = new PackageInfo[3];
- for (int i = 0; i < 3; ++i) {
+ for (i = 0; i < 3; ++i) {
applicationInfos[i] = new ApplicationInfo();
applicationInfos[i].category = ApplicationInfo.CATEGORY_GAME;
applicationInfos[i].packageName = packageNames[i];
@@ -1717,7 +1741,6 @@
new Injector());
startUser(gameManagerService, USER_ID_1);
startUser(gameManagerService, USER_ID_2);
-
gameManagerService.setGameModeConfigOverride("com.android.app0", USER_ID_2,
GameManager.GAME_MODE_PERFORMANCE, "120", "0.6");
gameManagerService.setGameModeConfigOverride("com.android.app2", USER_ID_2,
@@ -1953,7 +1976,7 @@
@Test
public void testUpdateCustomGameModeConfiguration_nonGame() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_IMAGE);
mockModifyGameModeGranted();
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
gameManagerService.updateCustomGameModeConfiguration(mPackageName,
@@ -2013,13 +2036,14 @@
}
@Test
- public void testWritingSettingFile_onShutdown() throws InterruptedException {
+ public void testWritingSettingFile_onShutdown() throws Exception {
mockModifyGameModeGranted();
mockDeviceConfigAll();
GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.onBootCompleted();
startUser(gameManagerService, USER_ID_1);
Thread.sleep(500);
+ mockAppCategory("com.android.app1", DEFAULT_PACKAGE_UID + 1, ApplicationInfo.CATEGORY_GAME);
gameManagerService.setGameModeConfigOverride("com.android.app1", USER_ID_1,
GameManager.GAME_MODE_BATTERY, "60", "0.5");
gameManagerService.setGameMode("com.android.app1", USER_ID_1,
@@ -2259,7 +2283,7 @@
when(DeviceConfig.getProperty(anyString(), anyString()))
.thenReturn(configString);
mockModifyGameModeGranted();
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_IMAGE);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
@@ -2277,9 +2301,7 @@
mockModifyGameModeGranted();
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
String someGamePkg = "some.game";
- mockAppCategory(someGamePkg, ApplicationInfo.CATEGORY_GAME);
- when(mMockPackageManager.getPackageUidAsUser(someGamePkg, USER_ID_1)).thenReturn(
- DEFAULT_PACKAGE_UID + 1);
+ mockAppCategory(someGamePkg, DEFAULT_PACKAGE_UID + 1, ApplicationInfo.CATEGORY_GAME);
gameManagerService.setGameMode(someGamePkg, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
assertEquals(GameManager.GAME_MODE_PERFORMANCE,
gameManagerService.getGameMode(someGamePkg, USER_ID_1));
@@ -2307,24 +2329,11 @@
}
@Test
- public void testGamePowerMode_gamePackage() throws Exception {
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
- gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
- verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
- }
-
- @Test
public void testGamePowerMode_twoGames() throws Exception {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages1 = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
String someGamePkg = "some.game";
- String[] packages2 = {someGamePkg};
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
HashMap<Integer, Boolean> powerState = new HashMap<>();
doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
.when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
@@ -2333,6 +2342,7 @@
assertTrue(powerState.get(Mode.GAME));
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ assertFalse(powerState.get(Mode.GAME));
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
assertTrue(powerState.get(Mode.GAME));
@@ -2344,12 +2354,9 @@
@Test
public void testGamePowerMode_twoGamesOverlap() throws Exception {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages1 = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
String someGamePkg = "some.game";
- String[] packages2 = {someGamePkg};
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
@@ -2363,49 +2370,162 @@
}
@Test
- public void testGamePowerMode_released() throws Exception {
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
- gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
- gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
- verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
- }
-
- @Test
public void testGamePowerMode_noPackage() throws Exception {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
String[] packages = {};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
- verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
+ DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
}
@Test
- public void testGamePowerMode_notAGamePackage() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ public void testGamePowerMode_gameAndNotGameApps_flagOn() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages = {"someapp"};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+
+ String nonGamePkg1 = "not.game1";
+ int nonGameUid1 = DEFAULT_PACKAGE_UID + 1;
+ mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE);
+
+ String nonGamePkg2 = "not.game2";
+ int nonGameUid2 = DEFAULT_PACKAGE_UID + 2;
+ mockAppCategory(nonGamePkg2, nonGameUid2, ApplicationInfo.CATEGORY_IMAGE);
+
+ String gamePkg1 = "game1";
+ int gameUid1 = DEFAULT_PACKAGE_UID + 3;
+ mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME);
+
+ String gamePkg2 = "game2";
+ int gameUid2 = DEFAULT_PACKAGE_UID + 4;
+ mockAppCategory(gamePkg2, gameUid2, ApplicationInfo.CATEGORY_GAME);
+
+ // non-game1 top and background with no-op
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
- verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // game1 top to enable game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game1 in foreground to disable game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game2 in foreground with no-op
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid2, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // game 2 in foreground with no-op
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid2, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game 1 in background with no-op
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game2 in background to resume game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid2, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // game 1 in background with no-op
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // game 2 in background to stop game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid2, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
}
@Test
- public void testGamePowerMode_notAGamePackageNotReleased() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ public void testGamePowerMode_gameAndNotGameApps_flagOff() throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages = {"someapp"};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+
+ String nonGamePkg1 = "not.game1";
+ int nonGameUid1 = DEFAULT_PACKAGE_UID + 1;
+ mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE);
+
+ String gamePkg1 = "game1";
+ int gameUid1 = DEFAULT_PACKAGE_UID + 3;
+ mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME);
+
+ // non-game1 top and background with no-op
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
- verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, false);
+ nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // game1 top to enable game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game1 in foreground to not interfere
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game 1 in background to not interfere
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // move non-game1 to foreground again
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+
+ // with non-game1 on top, game 1 in background to still disable game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // with non-game1 on top, game 1 in foreground to still enable game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
}
@Test
@@ -2432,8 +2552,6 @@
gameManagerService.onBootCompleted();
// Set up a game in the foreground.
- String[] packages = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
@@ -2448,10 +2566,8 @@
// Adding another game to the foreground.
String anotherGamePkg = "another.game";
- String[] packages2 = {anotherGamePkg};
- mockAppCategory(anotherGamePkg, ApplicationInfo.CATEGORY_GAME);
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ mockAppCategory(anotherGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
@@ -2484,8 +2600,6 @@
}));
// Set up a game in the foreground.
- String[] packages = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
@@ -2503,10 +2617,8 @@
// Toggle game default frame rate off.
String anotherGamePkg = "another.game";
- String[] packages2 = {anotherGamePkg};
- mockAppCategory(anotherGamePkg, ApplicationInfo.CATEGORY_GAME);
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ mockAppCategory(anotherGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index ae53e70..7444403 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -19,6 +19,7 @@
import android.app.AppOpsManager
import android.content.Intent
import android.content.pm.SuspendDialogInfo
+import android.content.pm.UserPackage
import android.os.Binder
import android.os.PersistableBundle
import com.android.server.testutils.any
@@ -41,12 +42,18 @@
.thenReturn(AppOpsManager.MODE_DEFAULT)
}
+ companion object {
+ val doUserPackage = UserPackage.of(TEST_USER_ID, DEVICE_OWNER_PACKAGE)
+ val platformUserPackage = UserPackage.of(TEST_USER_ID, PLATFORM_PACKAGE_NAME)
+ val testUserPackage1 = UserPackage.of(TEST_USER_ID, TEST_PACKAGE_1)
+ }
+
@Test
fun setPackagesSuspended() {
val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
targetPackages, true /* suspended */, null /* appExtras */,
- null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+ null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
testHandler.flush()
@@ -63,14 +70,14 @@
fun setPackagesSuspended_emptyPackageName() {
var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
null /* packageNames */, true /* suspended */, null /* appExtras */,
- null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+ null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
assertThat(failedNames).isNull()
failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
arrayOfNulls(0), true /* suspended */, null /* appExtras */,
- null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+ null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
assertThat(failedNames).isEmpty()
@@ -80,7 +87,8 @@
fun setPackagesSuspended_callerIsNotAllowed() {
val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */,
- null /* launcherExtras */, null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID,
+ null /* launcherExtras */, null /* dialogInfo */,
+ testUserPackage1, TEST_USER_ID,
Binder.getCallingUid(), false /* quarantined */)
assertThat(failedNames).asList().hasSize(1)
@@ -91,7 +99,7 @@
fun setPackagesSuspended_callerSuspendItself() {
val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
arrayOf(DEVICE_OWNER_PACKAGE), true /* suspended */, null /* appExtras */,
- null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+ null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
assertThat(failedNames).asList().hasSize(1)
@@ -102,7 +110,7 @@
fun setPackagesSuspended_nonexistentPackage() {
val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
arrayOf(NONEXISTENT_PACKAGE), true /* suspended */, null /* appExtras */,
- null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+ null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
assertThat(failedNames).asList().hasSize(1)
@@ -115,7 +123,7 @@
INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE)
val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
knownPackages, true /* suspended */, null /* appExtras */,
- null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+ null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
TEST_USER_ID, deviceOwnerUid, false /* quarantined */)!!
assertThat(failedNames.size).isEqualTo(knownPackages.size)
@@ -129,14 +137,14 @@
val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
targetPackages, true /* suspended */, null /* appExtras */,
- null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+ null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
testHandler.flush()
Mockito.clearInvocations(broadcastHelper)
assertThat(failedNames).isEmpty()
failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
targetPackages, false /* suspended */, null /* appExtras */,
- null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+ null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
testHandler.flush()
@@ -184,7 +192,7 @@
appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_1)
var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
arrayOf(TEST_PACKAGE_1), true /* suspended */, appExtras, null /* launcherExtras */,
- null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+ null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
false /* quarantined */)
testHandler.flush()
assertThat(failedNames).isEmpty()
@@ -202,22 +210,22 @@
val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
targetPackages, true /* suspended */, appExtras, null /* launcherExtras */,
- null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+ null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
false /* quarantined */)
testHandler.flush()
Mockito.clearInvocations(broadcastHelper)
assertThat(failedNames).isEmpty()
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
- TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+ TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
- TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+ TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNotNull()
assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNotNull()
suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
- targetPackages, { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE },
+ targetPackages, { suspender -> suspender.packageName == DEVICE_OWNER_PACKAGE },
TEST_USER_ID)
testHandler.flush()
@@ -243,7 +251,7 @@
launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2)
var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */, launcherExtras,
- null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+ null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
false /* quarantined */)
testHandler.flush()
assertThat(failedNames).isEmpty()
@@ -258,7 +266,7 @@
fun isPackageSuspended() {
var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
arrayOf(TEST_PACKAGE_1), true /* suspended */, null /* appExtras */,
- null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+ null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
testHandler.flush()
assertThat(failedNames).isEmpty()
@@ -273,13 +281,13 @@
launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2)
var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */, launcherExtras,
- null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+ null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
false /* quarantined */)
testHandler.flush()
assertThat(failedNames).isEmpty()
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
- TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+ TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
}
@Test
@@ -290,57 +298,57 @@
// Suspend.
var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
- null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+ null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
false /* quarantined */)
assertThat(failedNames).isEmpty()
testHandler.flush()
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
- TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+ TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
// Suspend by system.
failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
- null /* dialogInfo */, PLATFORM_PACKAGE_NAME, TEST_USER_ID, deviceOwnerUid,
+ null /* dialogInfo */, platformUserPackage, TEST_USER_ID, deviceOwnerUid,
false /* quarantined */)
assertThat(failedNames).isEmpty()
testHandler.flush()
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
- TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(PLATFORM_PACKAGE_NAME)
+ TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(platformUserPackage)
// QAS by package1.
failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
- null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid,
+ null /* dialogInfo */, testUserPackage1, TEST_USER_ID, deviceOwnerUid,
true /* quarantined */)
assertThat(failedNames).isEmpty()
testHandler.flush()
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
- TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(TEST_PACKAGE_1)
+ TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(testUserPackage1)
// Un-QAS by package1.
suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
- targetPackages, { suspendingPackage -> suspendingPackage == TEST_PACKAGE_1 },
+ targetPackages, { suspendingPackage -> suspendingPackage == testUserPackage1 },
TEST_USER_ID)
testHandler.flush()
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
- TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(PLATFORM_PACKAGE_NAME)
+ TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(platformUserPackage)
// Un-suspend by system.
suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
- targetPackages, { suspendingPackage -> suspendingPackage == PLATFORM_PACKAGE_NAME },
+ targetPackages, { suspender -> suspender.packageName == PLATFORM_PACKAGE_NAME },
TEST_USER_ID)
testHandler.flush()
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
- TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+ TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
// Unsuspend.
suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
- targetPackages, { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE },
+ targetPackages, { suspendingPackage -> suspendingPackage == doUserPackage },
TEST_USER_ID)
testHandler.flush()
@@ -354,13 +362,13 @@
.setTitle(TEST_PACKAGE_1).build()
var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
arrayOf(TEST_PACKAGE_1), true /* suspended */, null /* appExtras */,
- null /* launcherExtras */, dialogInfo, DEVICE_OWNER_PACKAGE, TEST_USER_ID,
+ null /* launcherExtras */, dialogInfo, doUserPackage, TEST_USER_ID,
deviceOwnerUid, false /* quarantined */)
testHandler.flush()
assertThat(failedNames).isEmpty()
val result = suspendPackageHelper.getSuspendedDialogInfo(pms.snapshotComputer(),
- TEST_PACKAGE_1, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)!!
+ TEST_PACKAGE_1, doUserPackage, TEST_USER_ID, deviceOwnerUid)!!
assertThat(result.title).isEqualTo(TEST_PACKAGE_1)
}
diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
deleted file mode 100644
index 523c5c0..0000000
--- a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.test.AndroidTestCase;
-
-import com.android.server.os.TombstoneProtos;
-import com.android.server.os.TombstoneProtos.Tombstone;
-
-public class BootReceiverTest extends AndroidTestCase {
- private static final String TAG = "BootReceiverTest";
-
- public void testRemoveMemoryFromTombstone() {
- Tombstone tombstoneBase = Tombstone.newBuilder()
- .setBuildFingerprint("build_fingerprint")
- .setRevision("revision")
- .setPid(123)
- .setTid(23)
- .setUid(34)
- .setSelinuxLabel("selinux_label")
- .addCommandLine("cmd1")
- .addCommandLine("cmd2")
- .addCommandLine("cmd3")
- .setProcessUptime(300)
- .setAbortMessage("abort")
- .addCauses(TombstoneProtos.Cause.newBuilder()
- .setHumanReadable("cause1")
- .setMemoryError(TombstoneProtos.MemoryError.newBuilder()
- .setTool(TombstoneProtos.MemoryError.Tool.SCUDO)
- .setType(TombstoneProtos.MemoryError.Type.DOUBLE_FREE)))
- .addLogBuffers(TombstoneProtos.LogBuffer.newBuilder().setName("name").addLogs(
- TombstoneProtos.LogMessage.newBuilder()
- .setTimestamp("123")
- .setMessage("message")))
- .addOpenFds(TombstoneProtos.FD.newBuilder().setFd(1).setPath("path"))
- .build();
-
- Tombstone tombstoneWithoutMemory = tombstoneBase.toBuilder()
- .putThreads(1, TombstoneProtos.Thread.newBuilder()
- .setId(1)
- .setName("thread1")
- .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
- .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
- .addBacktraceNote("backtracenote1")
- .addUnreadableElfFiles("files1")
- .setTaggedAddrCtrl(1)
- .setPacEnabledKeys(10)
- .build())
- .build();
-
- Tombstone tombstoneWithMemory = tombstoneBase.toBuilder()
- .addMemoryMappings(TombstoneProtos.MemoryMapping.newBuilder()
- .setBeginAddress(1)
- .setEndAddress(100)
- .setOffset(10)
- .setRead(true)
- .setWrite(true)
- .setExecute(false)
- .setMappingName("mapping")
- .setBuildId("build")
- .setLoadBias(70))
- .putThreads(1, TombstoneProtos.Thread.newBuilder()
- .setId(1)
- .setName("thread1")
- .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
- .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
- .addBacktraceNote("backtracenote1")
- .addUnreadableElfFiles("files1")
- .addMemoryDump(TombstoneProtos.MemoryDump.newBuilder()
- .setRegisterName("register1")
- .setMappingName("mapping")
- .setBeginAddress(10))
- .setTaggedAddrCtrl(1)
- .setPacEnabledKeys(10)
- .build())
- .build();
-
- assertThat(BootReceiver.removeMemoryFromTombstone(tombstoneWithMemory))
- .isEqualTo(tombstoneWithoutMemory);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java
index 5fd28f57..332b1a2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility.magnification;
+import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT;
import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE;
@@ -44,6 +45,7 @@
import androidx.test.InstrumentationRegistry;
import org.junit.After;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -70,6 +72,13 @@
private WindowMagnificationPromptController mWindowMagnificationPromptController;
private BroadcastReceiver mReceiver;
+ /**
+ * return whether window magnification is supported for current test context.
+ */
+ private boolean isWindowModeSupported() {
+ return mTestableContext.getPackageManager().hasSystemFeature(FEATURE_WINDOW_MAGNIFICATION);
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -78,6 +87,9 @@
setWindowMagnificationPromptSettings(true);
mWindowMagnificationPromptController = new WindowMagnificationPromptController(
mTestableContext, TEST_USER);
+
+ // skip test if window magnification is not supported to prevent fail results.
+ Assume.assumeTrue(isWindowModeSupported());
}
@After
@@ -111,8 +123,8 @@
final Intent intent = new Intent(ACTION_TURN_ON_IN_SETTINGS);
mReceiver.onReceive(mTestableContext, intent);
- assertThat(Settings.Secure.getInt(mResolver, ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
- -1)).isEqualTo(0);
+ assertThat(Settings.Secure.getIntForUser(mResolver,
+ ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, -1, TEST_USER)).isEqualTo(0);
verify(mTestableContext.getSpyContext()).unregisterReceiver(mReceiver);
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java b/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java
index 6fca561..69817ad 100644
--- a/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java
@@ -16,17 +16,21 @@
package com.android.server.audio;
-import static android.media.AudioAttributes.USAGE_MEDIA;
-import static android.media.AudioAttributes.USAGE_GAME;
-import static android.media.AudioAttributes.USAGE_ASSISTANT;
import static android.media.AudioAttributes.CONTENT_TYPE_SPEECH;
+import static android.media.AudioAttributes.USAGE_ASSISTANT;
+import static android.media.AudioAttributes.USAGE_EMERGENCY;
+import static android.media.AudioAttributes.USAGE_GAME;
+import static android.media.AudioAttributes.USAGE_MEDIA;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.media.AudioAttributes;
+import android.media.FadeManagerConfiguration;
import android.media.VolumeShaper;
+import android.platform.test.flag.junit.SetFlagsRule;
import com.google.common.truth.Expect;
@@ -42,6 +46,7 @@
public final class FadeConfigurationsTest {
private FadeConfigurations mFadeConfigurations;
private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
+ private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
private static final long DEFAULT_DELAY_FADE_IN_OFFENDERS_MS = 2000;
private static final long DURATION_FOR_UNFADEABLE_MS = 0;
private static final int TEST_UID_SYSTEM = 1000;
@@ -60,11 +65,19 @@
private static final VolumeShaper.Configuration DEFAULT_FADEOUT_VSHAPE =
new VolumeShaper.Configuration.Builder()
.setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID)
- .setCurve(/* times= */new float[]{0.f, 0.25f, 1.0f} ,
- /* volumes= */new float[]{1.f, 0.65f, 0.0f})
+ .setCurve(/* times= */ new float[]{0.f, 0.25f, 1.0f},
+ /* volumes= */ new float[]{1.f, 0.65f, 0.0f})
.setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
.setDuration(DEFAULT_FADE_OUT_DURATION_MS)
.build();
+ private static final VolumeShaper.Configuration DEFAULT_FADEIN_VSHAPE =
+ new VolumeShaper.Configuration.Builder()
+ .setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID)
+ .setCurve(/* times= */ new float[]{0.f, 0.50f, 1.0f},
+ /* volumes= */ new float[]{0.f, 0.30f, 1.0f})
+ .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+ .setDuration(DEFAULT_FADE_IN_DURATION_MS)
+ .build();
private static final AudioAttributes TEST_MEDIA_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build();
@@ -72,12 +85,18 @@
new AudioAttributes.Builder().setUsage(USAGE_GAME).build();
private static final AudioAttributes TEST_ASSISTANT_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setUsage(USAGE_ASSISTANT).build();
+ private static final AudioAttributes TEST_EMERGENCY_AUDIO_ATTRIBUTE =
+ new AudioAttributes.Builder().setSystemUsage(USAGE_EMERGENCY).build();
+
private static final AudioAttributes TEST_SPEECH_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setContentType(CONTENT_TYPE_SPEECH).build();
@Rule
public final Expect expect = Expect.create();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() {
mFadeConfigurations = new FadeConfigurations();
@@ -156,4 +175,110 @@
.that(mFadeConfigurations.isFadeable(TEST_GAME_AUDIO_ATTRIBUTE, TEST_UID_USER,
PLAYER_TYPE_AAUDIO)).isFalse();
}
+
+ @Test
+ public void testGetFadeableUsages_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<Integer> usageList = List.of(AudioAttributes.USAGE_ALARM,
+ AudioAttributes.USAGE_EMERGENCY);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ usageList,
+ /* unfadeableContentTypes= */ null, /* unfadeableUids= */ null,
+ /* unfadeableAudioAttrs= */ null);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Fadeable usages with fade manager configuration")
+ .that(fadeConfigs.getFadeableUsages()).isEqualTo(fmc.getFadeableUsages());
+ }
+
+ @Test
+ public void testGetUnfadeableContentTypes_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<Integer> contentTypesList = List.of(AudioAttributes.CONTENT_TYPE_MUSIC,
+ AudioAttributes.CONTENT_TYPE_MOVIE);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null,
+ /* unfadeableContentTypes= */ contentTypesList, /* unfadeableUids= */ null,
+ /* unfadeableAudioAttrs= */ null);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Unfadeable content types with fade manager configuration")
+ .that(fadeConfigs.getUnfadeableContentTypes())
+ .isEqualTo(fmc.getUnfadeableContentTypes());
+ }
+
+ @Test
+ public void testGetUnfadeableAudioAttributes_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<AudioAttributes> attrsList = List.of(TEST_ASSISTANT_AUDIO_ATTRIBUTE,
+ TEST_EMERGENCY_AUDIO_ATTRIBUTE);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null,
+ /* unfadeableContentTypes= */ null, /* unfadeableUids= */ null,
+ /* unfadeableAudioAttrs= */ attrsList);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Unfadeable audio attributes with fade manager configuration")
+ .that(fadeConfigs.getUnfadeableAudioAttributes())
+ .isEqualTo(fmc.getUnfadeableAudioAttributes());
+ }
+
+ @Test
+ public void testGetUnfadeableUids_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<Integer> uidsList = List.of(TEST_UID_SYSTEM, TEST_UID_USER);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null,
+ /* unfadeableContentTypes= */ null, /* unfadeableUids= */ uidsList,
+ /* unfadeableAudioAttrs= */ null);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Unfadeable uids with fade manager configuration")
+ .that(fadeConfigs.getUnfadeableUids()).isEqualTo(fmc.getUnfadeableUids());
+ }
+
+ private static FadeManagerConfiguration createFadeMgrConfig(List<Integer> fadeableUsages,
+ List<Integer> unfadeableContentTypes, List<Integer> unfadeableUids,
+ List<AudioAttributes> unfadeableAudioAttrs) {
+ FadeManagerConfiguration.Builder builder = new FadeManagerConfiguration.Builder();
+ if (fadeableUsages != null) {
+ builder.setFadeableUsages(fadeableUsages);
+ }
+ if (unfadeableContentTypes != null) {
+ builder.setUnfadeableContentTypes(unfadeableContentTypes);
+ }
+ if (unfadeableUids != null) {
+ builder.setUnfadeableUids(unfadeableUids);
+ }
+ if (unfadeableAudioAttrs != null) {
+ builder.setUnfadeableAudioAttributes(unfadeableAudioAttrs);
+ }
+ if (fadeableUsages != null) {
+ for (int index = 0; index < fadeableUsages.size(); index++) {
+ builder.setFadeOutVolumeShaperConfigForAudioAttributes(
+ createGenericAudioAttributesForUsage(fadeableUsages.get(index)),
+ DEFAULT_FADEOUT_VSHAPE);
+ }
+ }
+ if (fadeableUsages != null) {
+ for (int index = 0; index < fadeableUsages.size(); index++) {
+ builder.setFadeInVolumeShaperConfigForAudioAttributes(
+ createGenericAudioAttributesForUsage(fadeableUsages.get(index)),
+ DEFAULT_FADEIN_VSHAPE);
+ }
+ }
+
+ return builder.build();
+ }
+
+ private static AudioAttributes createGenericAudioAttributesForUsage(int usage) {
+ if (AudioAttributes.isSystemUsage(usage)) {
+ return new AudioAttributes.Builder().setSystemUsage(usage).build();
+ }
+ return new AudioAttributes.Builder().setUsage(usage).build();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
index 65059d5..fa94821 100644
--- a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
@@ -23,8 +23,6 @@
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN;
-import static org.junit.Assert.assertThrows;
-
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
@@ -75,20 +73,11 @@
@Before
public void setUp() {
- mFadeOutManager = new FadeOutManager(new FadeConfigurations());
+ mFadeOutManager = new FadeOutManager();
mContext = ApplicationProvider.getApplicationContext();
}
@Test
- public void constructor_nullFadeConfigurations_fails() {
- Throwable thrown = assertThrows(NullPointerException.class, () -> new FadeOutManager(
- /* FadeConfigurations= */ null));
-
- expect.withMessage("Constructor exception")
- .that(thrown).hasMessageThat().contains("Fade configurations can not be null");
- }
-
- @Test
public void testCanCauseFadeOut_forFaders_returnsTrue() {
FocusRequester winner = createFocusRequester(TEST_MEDIA_AUDIO_ATTRIBUTE, "winning-client",
"unit-test", TEST_UID_USER,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 3b5cae3..88b2ed4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -50,14 +50,22 @@
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceService;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.iris.IIrisService;
import android.os.Binder;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.InstrumentationRegistry;
@@ -89,6 +97,9 @@
@Rule
public MockitoRule mockitorule = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -118,6 +129,10 @@
@Captor
private ArgumentCaptor<List<FingerprintSensorPropertiesInternal>> mFingerprintPropsCaptor;
@Captor
+ private ArgumentCaptor<FingerprintSensorConfigurations> mFingerprintSensorConfigurationsCaptor;
+ @Captor
+ private ArgumentCaptor<FaceSensorConfigurations> mFaceSensorConfigurationsCaptor;
+ @Captor
private ArgumentCaptor<List<FaceSensorPropertiesInternal>> mFacePropsCaptor;
@Before
@@ -143,6 +158,9 @@
when(mContext.getResources()).thenReturn(mResources);
when(mInjector.getBiometricService()).thenReturn(mBiometricService);
when(mInjector.getConfiguration(any())).thenReturn(config);
+ when(mInjector.getFaceConfiguration(any())).thenReturn(config);
+ when(mInjector.getFingerprintConfiguration(any())).thenReturn(config);
+ when(mInjector.getIrisConfiguration(any())).thenReturn(config);
when(mInjector.getFingerprintService()).thenReturn(mFingerprintService);
when(mInjector.getFaceService()).thenReturn(mFaceService);
when(mInjector.getIrisService()).thenReturn(mIrisService);
@@ -173,12 +191,13 @@
}
@Test
+ @RequiresFlagsDisabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
public void testRegisterAuthenticator_registerAuthenticators() throws Exception {
final int fingerprintId = 0;
final int fingerprintStrength = 15;
final int faceId = 1;
- final int faceStrength = 4095;
+ final int faceStrength = 15;
final String[] config = {
// ID0:Fingerprint:Strong
@@ -206,6 +225,51 @@
Utils.authenticatorStrengthToPropertyStrength(faceStrength));
}
+ @Test
+ @RequiresFlagsEnabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
+ public void testRegisterAuthenticator_registerAuthenticatorsLegacy() throws RemoteException {
+ final int fingerprintId = 0;
+ final int fingerprintStrength = 15;
+
+ final int faceId = 1;
+ final int faceStrength = 4095;
+
+ final String[] config = {
+ // ID0:Fingerprint:Strong
+ String.format("%d:2:%d", fingerprintId, fingerprintStrength),
+ // ID2:Face:Convenience
+ String.format("%d:8:%d", faceId, faceStrength)
+ };
+
+ when(mInjector.getFingerprintConfiguration(any())).thenReturn(config);
+ when(mInjector.getFaceConfiguration(any())).thenReturn(config);
+ when(mInjector.getFingerprintAidlInstances()).thenReturn(new String[]{});
+ when(mInjector.getFaceAidlInstances()).thenReturn(new String[]{});
+
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ verify(mFingerprintService).registerAuthenticatorsLegacy(
+ mFingerprintSensorConfigurationsCaptor.capture());
+
+ final SensorProps[] fingerprintProp = mFingerprintSensorConfigurationsCaptor.getValue()
+ .getSensorPairForInstance("defaultHIDL").second;
+
+ assertEquals(fingerprintProp[0].commonProps.sensorId, fingerprintId);
+ assertEquals(fingerprintProp[0].commonProps.sensorStrength,
+ Utils.authenticatorStrengthToPropertyStrength(fingerprintStrength));
+
+ verify(mFaceService).registerAuthenticatorsLegacy(
+ mFaceSensorConfigurationsCaptor.capture());
+
+ final android.hardware.biometrics.face.SensorProps[] faceProp =
+ mFaceSensorConfigurationsCaptor.getValue()
+ .getSensorPairForInstance("defaultHIDL").second;
+
+ assertEquals(faceProp[0].commonProps.sensorId, faceId);
+ assertEquals(faceProp[0].commonProps.sensorStrength,
+ Utils.authenticatorStrengthToPropertyStrength(faceStrength));
+ }
// TODO(b/141025588): Check that an exception is thrown when the userId != callingUserId
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
new file mode 100644
index 0000000..c9e1c4a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2023 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.biometrics.sensors.face;
+
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceSensorConfigurations;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.biometrics.Flags;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+@SmallTest
+public class FaceServiceTest {
+ private static final int ID_DEFAULT = 2;
+ private static final int ID_VIRTUAL = 6;
+ private static final String NAME_DEFAULT = "default";
+ private static final String NAME_VIRTUAL = "virtual";
+
+ @Rule
+ public final MockitoRule mMockito = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+ @Rule
+ public final FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
+ @Mock
+ FaceProvider mFaceProviderDefault;
+ @Mock
+ FaceProvider mFaceProviderVirtual;
+ @Mock
+ IFace mDefaultFaceDaemon;
+ @Mock
+ IFace mVirtualFaceDaemon;
+ @Mock
+ IBiometricService mIBiometricService;
+
+ private final SensorProps mDefaultSensorProps = new SensorProps();
+ private final SensorProps mVirtualSensorProps = new SensorProps();
+ private FaceService mFaceService;
+ private final FaceSensorPropertiesInternal mSensorPropsDefault =
+ new FaceSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG,
+ 2 /* maxEnrollmentsPerUser */,
+ List.of(),
+ TYPE_UNKNOWN,
+ true /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */);
+ private final FaceSensorPropertiesInternal mSensorPropsVirtual =
+ new FaceSensorPropertiesInternal(ID_VIRTUAL, STRENGTH_STRONG,
+ 2 /* maxEnrollmentsPerUser */,
+ List.of(),
+ TYPE_UNKNOWN,
+ true /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */);
+ private FaceSensorConfigurations mFaceSensorConfigurations;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mDefaultFaceDaemon.getSensorProps()).thenReturn(
+ new SensorProps[]{mDefaultSensorProps});
+ when(mVirtualFaceDaemon.getSensorProps()).thenReturn(
+ new SensorProps[]{mVirtualSensorProps});
+ when(mFaceProviderDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
+ when(mFaceProviderVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
+
+ mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+ mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
+ (name) -> {
+ if (name.equals(IFace.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+ return mDefaultFaceDaemon;
+ } else if (name.equals(IFace.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+ return mVirtualFaceDaemon;
+ }
+ return null;
+ });
+ }
+
+ private void initService() {
+ mFaceService = new FaceService(mContext,
+ (filteredSensorProps, resetLockoutRequiresChallenge) -> {
+ if (NAME_DEFAULT.equals(filteredSensorProps.first)) return mFaceProviderDefault;
+ if (NAME_VIRTUAL.equals(filteredSensorProps.first)) return mFaceProviderVirtual;
+ return null;
+ }, () -> mIBiometricService);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
+ initService();
+
+ mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT),
+ eq(BiometricAuthenticator.TYPE_FACE),
+ eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)),
+ any());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
+ initService();
+ Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+ Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
+
+ mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
+ eq(BiometricAuthenticator.TYPE_FACE),
+ eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
+ mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+ mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_VIRTUAL},
+ (name) -> {
+ if (name.equals(IFace.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+ return mDefaultFaceDaemon;
+ } else if (name.equals(IFace.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+ return mVirtualFaceDaemon;
+ }
+ return null;
+ });
+ initService();
+
+ mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
+ eq(BiometricAuthenticator.TYPE_FACE),
+ eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
+ }
+
+ private void waitForRegistration() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mFaceService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
+ new IFaceAuthenticatorsRegisteredCallback.Stub() {
+ public void onAllAuthenticatorsRegistered(
+ List<FaceSensorPropertiesInternal> sensors) {
+ latch.countDown();
+ }
+ });
+ latch.await(10, TimeUnit.SECONDS);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index f43120d..8b1a291 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -16,6 +16,9 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -32,15 +35,19 @@
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.HidlFaceSensorConfig;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -49,6 +56,7 @@
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -59,6 +67,10 @@
@SmallTest
public class FaceProviderTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String TAG = "FaceProviderTest";
private static final float FRR_THRESHOLD = 0.2f;
@@ -109,7 +121,7 @@
mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext,
- mDaemon);
+ mDaemon, false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
}
@Test
@@ -124,10 +136,38 @@
assertThat(currentClient).isInstanceOf(FaceInternalCleanupClient.class);
assertThat(currentClient.getSensorId()).isEqualTo(prop.commonProps.sensorId);
- assertThat(currentClient.getTargetUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+ assertThat(currentClient.getTargetUserId()).isEqualTo(USER_SYSTEM);
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void testAddingHidlSensors() {
+ when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
+ when(mResources.getBoolean(anyInt())).thenReturn(false);
+
+ final int faceId = 0;
+ final int faceStrength = 15;
+ final String config = String.format("%d:8:%d", faceId, faceStrength);
+ final HidlFaceSensorConfig faceSensorConfig = new HidlFaceSensorConfig();
+ faceSensorConfig.parse(config, mContext);
+ final HidlFaceSensorConfig[] hidlFaceSensorConfig =
+ new HidlFaceSensorConfig[]{faceSensorConfig};
+ mFaceProvider = new FaceProvider(mContext,
+ mBiometricStateCallback, hidlFaceSensorConfig, TAG,
+ mLockoutResetDispatcher, mBiometricContext, mDaemon,
+ true /* resetLockoutRequiresChallenge */,
+ true /* testHalEnabled */);
+
+ assertThat(mFaceProvider.mFaceSensors.get(faceId)
+ .getLazySession().get().getUserId()).isEqualTo(USER_NULL);
+
+ waitForIdle();
+
+ assertThat(mFaceProvider.mFaceSensors.get(faceId)
+ .getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
+ }
+
@SuppressWarnings("rawtypes")
@Test
public void halServiceDied_resetsAllSchedulers() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 7a293e8..e7f7195 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -157,7 +157,7 @@
sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext,
null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext);
-
+ sensor.init(mLockoutResetDispatcher, mFaceProvider);
mScheduler.reset();
assertNull(mScheduler.getCurrentClient());
@@ -185,6 +185,7 @@
sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
internalProp, mLockoutResetDispatcher, mBiometricContext, mCurrentSession);
+ sensor.init(mLockoutResetDispatcher, mFaceProvider);
mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
USER_ID, mHalCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
new file mode 100644
index 0000000..4e43332
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2023 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.biometrics.sensors.face.hidl;
+
+import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.OptionalUint64;
+import android.hardware.biometrics.face.V1_0.Status;
+import android.hardware.face.Face;
+import android.hardware.face.HidlFaceSensorConfig;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+import com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class HidlToAidlSensorAdapterTest {
+ private static final String TAG = "HidlToAidlSensorAdapterTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+ private static final byte[] HAT = new byte[69];
+ @Rule
+ public final MockitoRule mMockito = MockitoJUnit.rule();
+
+ @Mock
+ private IBiometricService mBiometricService;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcherForSensor;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcherForClient;
+ @Mock
+ private BiometricLogger mLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private AuthSessionCoordinator mAuthSessionCoordinator;
+ @Mock
+ private FaceProvider mFaceProvider;
+ @Mock
+ private Runnable mInternalCleanupAndGetFeatureRunnable;
+ @Mock
+ private IBiometricsFace mDaemon;
+ @Mock
+ AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ @Mock
+ BiometricUtils<Face> mBiometricUtils;
+
+ private final TestLooper mLooper = new TestLooper();
+ private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter;
+ private final TestableContext mContext = new TestableContext(
+ ApplicationProvider.getApplicationContext());
+
+ @Before
+ public void setUp() throws RemoteException {
+ final OptionalUint64 result = new OptionalUint64();
+ result.status = Status.OK;
+
+ when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ when(mDaemon.setCallback(any())).thenReturn(result);
+ doAnswer((answer) -> {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onLockoutChanged(0);
+ return null;
+ }).when(mDaemon).resetLockout(any());
+ doAnswer((answer) -> {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onEnrollmentProgress(1, 0);
+ return null;
+ }).when(mDaemon).enroll(any(), anyInt(), any());
+
+ mContext.getOrCreateTestableResources();
+
+ final String config = String.format("%d:8:15", SENSOR_ID);
+ final BiometricScheduler scheduler = new BiometricScheduler(TAG,
+ new Handler(mLooper.getLooper()),
+ BiometricScheduler.SENSOR_TYPE_FACE,
+ null /* gestureAvailabilityTracker */,
+ mBiometricService, 10 /* recentOperationsLimit */);
+ final HidlFaceSensorConfig faceSensorConfig = new HidlFaceSensorConfig();
+ faceSensorConfig.parse(config, mContext);
+ mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG, mFaceProvider,
+ mContext, new Handler(mLooper.getLooper()), faceSensorConfig,
+ mLockoutResetDispatcherForSensor, mBiometricContext,
+ false /* resetLockoutRequiresChallenge */, mInternalCleanupAndGetFeatureRunnable,
+ mAuthSessionCoordinator, mDaemon, mAidlResponseHandlerCallback);
+ mHidlToAidlSensorAdapter.init(mLockoutResetDispatcherForSensor, mFaceProvider);
+ mHidlToAidlSensorAdapter.setScheduler(scheduler);
+ mHidlToAidlSensorAdapter.handleUserChanged(USER_ID);
+ }
+
+ @Test
+ public void lockoutTimedResetViaClient() {
+ setLockoutTimed();
+
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+ new FaceResetLockoutClient(mContext, mHidlToAidlSensorAdapter.getLazySession(),
+ USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+ mLooper.dispatchAll();
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false/* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mLockoutResetDispatcherForClient, never()).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutTimedResetViaCallback() {
+ setLockoutTimed();
+
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutChanged(0);
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(eq(
+ SENSOR_ID));
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID))
+ .isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutPermanentResetViaCallback() {
+ setLockoutPermanent();
+
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutChanged(0);
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(eq(
+ SENSOR_ID));
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutPermanentResetViaClient() {
+ setLockoutPermanent();
+
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+ new FaceResetLockoutClient(mContext,
+ mHidlToAidlSensorAdapter.getLazySession(),
+ USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForClient, never()).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void verifyOnEnrollSuccessCallback() {
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(new FaceEnrollClient(mContext,
+ mHidlToAidlSensorAdapter.getLazySession(), null /* token */, null /* listener */,
+ USER_ID, HAT, TAG, 1 /* requestId */, mBiometricUtils,
+ new int[]{} /* disabledFeatures */, ENROLL_TIMEOUT_SEC, null /* previewSurface */,
+ SENSOR_ID, mLogger, mBiometricContext, 1 /* maxTemplatesPerUser */,
+ false /* debugConsent */));
+ mLooper.dispatchAll();
+
+ verify(mAidlResponseHandlerCallback).onEnrollSuccess();
+ }
+
+ private void setLockoutTimed() {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onLockoutChanged(1);
+ mLooper.dispatchAll();
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID))
+ .isEqualTo(LockoutTracker.LOCKOUT_TIMED);
+ }
+
+ private void setLockoutPermanent() {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onLockoutChanged(-1);
+ mLooper.dispatchAll();
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_PERMANENT);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
similarity index 80%
rename from services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
rename to services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
index 9a40e8a..b9a4fb4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
@@ -16,7 +16,7 @@
package com.android.server.biometrics.sensors.face.hidl;
-import static com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter.ENROLL_TIMEOUT_SEC;
+import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC;
import static com.google.common.truth.Truth.assertThat;
@@ -68,7 +68,7 @@
@Presubmit
@SmallTest
-public class AidlToHidlAdapterTest {
+public class HidlToAidlSessionAdapterTest {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -84,25 +84,32 @@
private Clock mClock;
private final long mChallenge = 100L;
- private AidlToHidlAdapter mAidlToHidlAdapter;
+ private HidlToAidlSessionAdapter mHidlToAidlSessionAdapter;
private final Face mFace = new Face("face" /* name */, 1 /* faceId */, 0 /* deviceId */);
private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_REQUIRE_DIVERSITY;
private final byte[] mFeatures = new byte[]{Feature.REQUIRE_ATTENTION};
@Before
public void setUp() throws RemoteException {
+ final OptionalUint64 setCallbackResult = new OptionalUint64();
+ setCallbackResult.value = 1;
+
+ when(mSession.setCallback(any())).thenReturn(setCallbackResult);
+
TestableContext testableContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getContext());
testableContext.addMockSystemService(FaceManager.class, mFaceManager);
- mAidlToHidlAdapter = new AidlToHidlAdapter(testableContext, () -> mSession, 0 /* userId */,
- mAidlResponseHandler, mClock);
+
+ mHidlToAidlSessionAdapter = new HidlToAidlSessionAdapter(testableContext, () -> mSession,
+ 0 /* userId */, mAidlResponseHandler, mClock);
mHardwareAuthToken.timestamp = new Timestamp();
mHardwareAuthToken.mac = new byte[10];
- final OptionalUint64 result = new OptionalUint64();
- result.status = Status.OK;
- result.value = mChallenge;
- when(mSession.generateChallenge(anyInt())).thenReturn(result);
+ final OptionalUint64 generateChallengeResult = new OptionalUint64();
+ generateChallengeResult.status = Status.OK;
+ generateChallengeResult.value = mChallenge;
+
+ when(mSession.generateChallenge(anyInt())).thenReturn(generateChallengeResult);
when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(List.of(mFace));
}
@@ -112,16 +119,16 @@
final ArgumentCaptor<Long> challengeCaptor = ArgumentCaptor.forClass(Long.class);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
verify(mSession).generateChallenge(CHALLENGE_TIMEOUT_SEC);
verify(mAidlResponseHandler).onChallengeGenerated(challengeCaptor.capture());
assertThat(challengeCaptor.getValue()).isEqualTo(mChallenge);
forwardTime(10 /* seconds */);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
forwardTime(20 /* seconds */);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
//Confirms that the challenge is cached and the hal method is not called again
verifyNoMoreInteractions(mSession);
@@ -129,7 +136,7 @@
.onChallengeGenerated(mChallenge);
forwardTime(60 /* seconds */);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
//HAL method called after challenge has timed out
verify(mSession, times(2)).generateChallenge(CHALLENGE_TIMEOUT_SEC);
@@ -138,11 +145,11 @@
@Test
public void testRevokeChallenge_waitsUntilEmpty() throws RemoteException {
for (int i = 0; i < 3; i++) {
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
forwardTime(10 /* seconds */);
}
for (int i = 0; i < 3; i++) {
- mAidlToHidlAdapter.revokeChallenge(0);
+ mHidlToAidlSessionAdapter.revokeChallenge(0);
forwardTime((i + 1) * 10 /* seconds */);
}
@@ -151,20 +158,19 @@
@Test
public void testRevokeChallenge_timeout() throws RemoteException {
- mAidlToHidlAdapter.generateChallenge();
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
forwardTime(700);
- mAidlToHidlAdapter.generateChallenge();
- mAidlToHidlAdapter.revokeChallenge(0);
+ mHidlToAidlSessionAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.revokeChallenge(0);
verify(mSession).revokeChallenge();
}
@Test
public void testEnroll() throws RemoteException {
- ICancellationSignal cancellationSignal = mAidlToHidlAdapter.enroll(mHardwareAuthToken,
- EnrollmentType.DEFAULT, mFeatures,
- null /* previewSurface */);
+ ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.enroll(
+ mHardwareAuthToken, EnrollmentType.DEFAULT, mFeatures, null /* previewSurface */);
ArgumentCaptor<ArrayList<Integer>> featureCaptor = ArgumentCaptor.forClass(ArrayList.class);
verify(mSession).enroll(any(), eq(ENROLL_TIMEOUT_SEC), featureCaptor.capture());
@@ -182,7 +188,8 @@
@Test
public void testAuthenticate() throws RemoteException {
final int operationId = 2;
- ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+ ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.authenticate(
+ operationId);
verify(mSession).authenticate(operationId);
@@ -193,7 +200,7 @@
@Test
public void testDetectInteraction() throws RemoteException {
- ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+ ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.detectInteraction();
verify(mSession).authenticate(0);
@@ -204,7 +211,7 @@
@Test
public void testEnumerateEnrollments() throws RemoteException {
- mAidlToHidlAdapter.enumerateEnrollments();
+ mHidlToAidlSessionAdapter.enumerateEnrollments();
verify(mSession).enumerate();
}
@@ -212,7 +219,7 @@
@Test
public void testRemoveEnrollment() throws RemoteException {
final int[] enrollments = new int[]{1};
- mAidlToHidlAdapter.removeEnrollments(enrollments);
+ mHidlToAidlSessionAdapter.removeEnrollments(enrollments);
verify(mSession).remove(enrollments[0]);
}
@@ -226,8 +233,8 @@
when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
- mAidlToHidlAdapter.setFeature(mFeature);
- mAidlToHidlAdapter.getFeatures();
+ mHidlToAidlSessionAdapter.setFeature(mFeature);
+ mHidlToAidlSessionAdapter.getFeatures();
verify(mSession).getFeature(eq(mFeature), anyInt());
verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
@@ -244,8 +251,8 @@
when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
- mAidlToHidlAdapter.setFeature(mFeature);
- mAidlToHidlAdapter.getFeatures();
+ mHidlToAidlSessionAdapter.setFeature(mFeature);
+ mHidlToAidlSessionAdapter.getFeatures();
verify(mSession).getFeature(eq(mFeature), anyInt());
verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
@@ -260,8 +267,8 @@
when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
- mAidlToHidlAdapter.setFeature(mFeature);
- mAidlToHidlAdapter.getFeatures();
+ mHidlToAidlSessionAdapter.setFeature(mFeature);
+ mHidlToAidlSessionAdapter.getFeatures();
verify(mSession).getFeature(eq(mFeature), anyInt());
verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
@@ -270,7 +277,7 @@
@Test
public void testGetFeatures_featureNotSet() throws RemoteException {
- mAidlToHidlAdapter.getFeatures();
+ mHidlToAidlSessionAdapter.getFeatures();
verify(mSession, never()).getFeature(eq(mFeature), anyInt());
verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
@@ -283,7 +290,7 @@
when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())).thenReturn(Status.OK);
- mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+ mHidlToAidlSessionAdapter.setFeature(mHardwareAuthToken, feature, enabled);
verify(mAidlResponseHandler).onFeatureSet(feature);
}
@@ -296,7 +303,7 @@
when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt()))
.thenReturn(Status.INTERNAL_ERROR);
- mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+ mHidlToAidlSessionAdapter.setFeature(mHardwareAuthToken, feature, enabled);
verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN,
0 /* vendorCode */);
@@ -311,7 +318,7 @@
when(mSession.getAuthenticatorId()).thenReturn(result);
- mAidlToHidlAdapter.getAuthenticatorId();
+ mHidlToAidlSessionAdapter.getAuthenticatorId();
verify(mSession).getAuthenticatorId();
verify(mAidlResponseHandler).onAuthenticatorIdRetrieved(authenticatorId);
@@ -319,7 +326,7 @@
@Test
public void testResetLockout() throws RemoteException {
- mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+ mHidlToAidlSessionAdapter.resetLockout(mHardwareAuthToken);
ArgumentCaptor<ArrayList> hatCaptor = ArgumentCaptor.forClass(ArrayList.class);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 2aa62d9..f570ba2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -42,13 +42,19 @@
import android.app.AppOpsManager;
import android.content.pm.PackageManager;
import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.os.Binder;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.testing.TestableContext;
@@ -58,6 +64,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
@@ -89,6 +96,9 @@
@Rule
public final MockitoRule mMockito = MockitoJUnit.rule();
@Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
public final TestableContext mContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
@Rule
@@ -110,6 +120,10 @@
private IBinder mToken;
@Mock
private VirtualDeviceManagerInternal mVdmInternal;
+ @Mock
+ private IFingerprint mDefaultFingerprintDaemon;
+ @Mock
+ private IFingerprint mVirtualFingerprintDaemon;
@Captor
private ArgumentCaptor<FingerprintAuthenticateOptions> mAuthenticateOptionsCaptor;
@@ -126,7 +140,10 @@
List.of(),
TYPE_UDFPS_OPTICAL,
false /* resetLockoutRequiresHardwareAuthToken */);
+ private FingerprintSensorConfigurations mFingerprintSensorConfigurations;
private FingerprintService mService;
+ private final SensorProps mDefaultSensorProps = new SensorProps();
+ private final SensorProps mVirtualSensorProps = new SensorProps();
@Before
public void setup() throws Exception {
@@ -139,6 +156,10 @@
.thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
when(mFingerprintVirtual.containsSensor(anyInt()))
.thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
+ when(mDefaultFingerprintDaemon.getSensorProps()).thenReturn(
+ new SensorProps[]{mDefaultSensorProps});
+ when(mVirtualFingerprintDaemon.getSensorProps()).thenReturn(
+ new SensorProps[]{mVirtualSensorProps});
mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
for (int permission : List.of(OP_USE_BIOMETRIC, OP_USE_FINGERPRINT)) {
@@ -150,6 +171,18 @@
mContext.getTestablePermissions().setPermission(
permission, PackageManager.PERMISSION_GRANTED);
}
+
+ mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
+ (name) -> {
+ if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+ return mDefaultFingerprintDaemon;
+ } else if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+ return mVirtualFingerprintDaemon;
+ }
+ return null;
+ });
}
private void initServiceWith(String... aidlInstances) {
@@ -160,6 +193,10 @@
if (NAME_DEFAULT.equals(name)) return mFingerprintDefault;
if (NAME_VIRTUAL.equals(name)) return mFingerprintVirtual;
return null;
+ }, (sensorPropsPair, resetLockoutRequiresHardwareAuthToken) -> {
+ if (NAME_DEFAULT.equals(sensorPropsPair.first)) return mFingerprintDefault;
+ if (NAME_VIRTUAL.equals(sensorPropsPair.first)) return mFingerprintVirtual;
+ return null;
});
}
@@ -180,6 +217,17 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
+ initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+
+ mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
+ }
+
+ @Test
public void registerAuthenticators_virtualOnly() throws Exception {
initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
@@ -192,6 +240,19 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
+ initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+ Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+ Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
+
+ mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+ }
+
+ @Test
public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
initServiceWith(NAME_VIRTUAL);
@@ -201,6 +262,28 @@
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
+ mFingerprintSensorConfigurations =
+ new FingerprintSensorConfigurations(true);
+ mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_VIRTUAL},
+ (name) -> {
+ if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+ return mDefaultFingerprintDaemon;
+ } else if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+ return mVirtualFingerprintDaemon;
+ }
+ return null;
+ });
+ initServiceWith(NAME_VIRTUAL);
+
+ mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+ }
+
private void waitForRegistration() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 4cfb83f..bf5986c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -16,6 +16,9 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -34,17 +37,20 @@
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.SensorLocation;
import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.fingerprint.HidlFingerprintSensorConfig;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-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.HalClientMonitor;
@@ -52,6 +58,7 @@
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -64,6 +71,10 @@
private static final String TAG = "FingerprintProviderTest";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock
private Context mContext;
@Mock
@@ -115,7 +126,8 @@
mFingerprintProvider = new FingerprintProvider(mContext,
mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext,
- mDaemon);
+ mDaemon, false /* resetLockoutRequiresHardwareAuthToken */,
+ true /* testHalEnabled */);
}
@Test
@@ -123,17 +135,42 @@
waitForIdle();
for (SensorProps prop : mSensorProps) {
- final BiometricScheduler scheduler =
- mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
- .getScheduler();
- BaseClientMonitor currentClient = scheduler.getCurrentClient();
-
- assertThat(currentClient).isInstanceOf(FingerprintInternalCleanupClient.class);
- assertThat(currentClient.getSensorId()).isEqualTo(prop.commonProps.sensorId);
- assertThat(currentClient.getTargetUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+ final Sensor sensor =
+ mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId);
+ assertThat(sensor.getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void testAddingHidlSensors() {
+ when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
+ when(mResources.getBoolean(anyInt())).thenReturn(false);
+
+ final int fingerprintId = 0;
+ final int fingerprintStrength = 15;
+ final String config = String.format("%d:2:%d", fingerprintId, fingerprintStrength);
+ final HidlFingerprintSensorConfig fingerprintSensorConfig =
+ new HidlFingerprintSensorConfig();
+ fingerprintSensorConfig.parse(config, mContext);
+ HidlFingerprintSensorConfig[] hidlFingerprintSensorConfigs =
+ new HidlFingerprintSensorConfig[]{fingerprintSensorConfig};
+ mFingerprintProvider = new FingerprintProvider(mContext,
+ mBiometricStateCallback, mAuthenticationStateListeners,
+ hidlFingerprintSensorConfigs, TAG, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher, mBiometricContext, mDaemon,
+ false /* resetLockoutRequiresHardwareAuthToken */,
+ true /* testHalEnabled */);
+
+ assertThat(mFingerprintProvider.mFingerprintSensors.get(fingerprintId)
+ .getLazySession().get().getUserId()).isEqualTo(USER_NULL);
+
+ waitForIdle();
+
+ assertThat(mFingerprintProvider.mFingerprintSensors.get(fingerprintId)
+ .getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
+ }
+
@SuppressWarnings("rawtypes")
@Test
public void halServiceDied_resetsAllSchedulers() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 4102600..126a05e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -96,7 +96,7 @@
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
- private UserAwareBiometricScheduler mScheduler;
+ private BiometricScheduler mScheduler;
private AidlResponseHandler mHalCallback;
@Before
@@ -164,7 +164,8 @@
final Sensor sensor = new Sensor("SensorTest", mFingerprintProvider, mContext,
null /* handler */, internalProp, mLockoutResetDispatcher,
mGestureAvailabilityDispatcher, mBiometricContext, mCurrentSession);
- mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
+ sensor.init(mGestureAvailabilityDispatcher, mLockoutResetDispatcher);
+ mScheduler = sensor.getScheduler();
sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
USER_ID, mHalCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
new file mode 100644
index 0000000..89a4961
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2023 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.biometrics.sensors.fingerprint.hidl;
+
+import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.HidlFingerprintSensorConfig;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class HidlToAidlSensorAdapterTest {
+
+ private static final String TAG = "HidlToAidlSensorAdapterTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+ private static final byte[] HAT = new byte[69];
+
+ @Rule
+ public final MockitoRule mMockito = MockitoJUnit.rule();
+
+ @Mock
+ private IBiometricService mBiometricService;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcherForSensor;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcherForClient;
+ @Mock
+ private BiometricLogger mLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private AuthSessionCoordinator mAuthSessionCoordinator;
+ @Mock
+ private FingerprintProvider mFingerprintProvider;
+ @Mock
+ private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
+ @Mock
+ private Runnable mInternalCleanupRunnable;
+ @Mock
+ private AlarmManager mAlarmManager;
+ @Mock
+ private IBiometricsFingerprint mDaemon;
+ @Mock
+ private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ @Mock
+ private BiometricUtils<Fingerprint> mBiometricUtils;
+ @Mock
+ private AuthenticationStateListeners mAuthenticationStateListeners;
+
+ private final TestLooper mLooper = new TestLooper();
+ private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter;
+ private final TestableContext mContext = new TestableContext(
+ ApplicationProvider.getApplicationContext());
+
+ private final UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback =
+ new UserAwareBiometricScheduler.UserSwitchCallback() {
+ @NonNull
+ @Override
+ public StopUserClient<?> getStopUserClient(int userId) {
+ return new StopUserClient<IBiometricsFingerprint>(mContext,
+ mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null, USER_ID,
+ SENSOR_ID, mLogger, mBiometricContext, () -> {}) {
+ @Override
+ protected void startHalOperation() {
+ getCallback().onClientFinished(this, true /* success */);
+ }
+
+ @Override
+ public void unableToStart() {}
+ };
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+ return new StartUserClient<IBiometricsFingerprint, AidlSession>(mContext,
+ mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null,
+ USER_ID, SENSOR_ID,
+ mLogger, mBiometricContext,
+ (newUserId1, newUser, halInterfaceVersion) ->
+ mHidlToAidlSensorAdapter.handleUserChanged(newUserId1)) {
+ @Override
+ public void start(@NonNull ClientMonitorCallback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ mUserStartedCallback.onUserStarted(USER_ID, null, 0);
+ getCallback().onClientFinished(this, true /* success */);
+ }
+
+ @Override
+ public void unableToStart() {}
+ };
+ }
+ };;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ doAnswer((answer) -> {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onEnrollmentProgress(1 /* enrollmentId */, 0 /* remaining */);
+ return null;
+ }).when(mDaemon).enroll(any(), anyInt(), anyInt());
+
+ mContext.addMockSystemService(AlarmManager.class, mAlarmManager);
+ mContext.getOrCreateTestableResources();
+
+ final String config = String.format("%d:2:15", SENSOR_ID);
+ final UserAwareBiometricScheduler scheduler = new UserAwareBiometricScheduler(TAG,
+ new Handler(mLooper.getLooper()),
+ BiometricScheduler.SENSOR_TYPE_FP_OTHER,
+ null /* gestureAvailabilityDispatcher */,
+ mBiometricService,
+ () -> USER_ID,
+ mUserSwitchCallback);
+ final HidlFingerprintSensorConfig fingerprintSensorConfig =
+ new HidlFingerprintSensorConfig();
+ fingerprintSensorConfig.parse(config, mContext);
+ mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG,
+ mFingerprintProvider, mContext, new Handler(mLooper.getLooper()),
+ fingerprintSensorConfig, mLockoutResetDispatcherForSensor,
+ mGestureAvailabilityDispatcher, mBiometricContext,
+ false /* resetLockoutRequiresHardwareAuthToken */,
+ mInternalCleanupRunnable, mAuthSessionCoordinator, mDaemon,
+ mAidlResponseHandlerCallback);
+ mHidlToAidlSensorAdapter.init(mGestureAvailabilityDispatcher,
+ mLockoutResetDispatcherForSensor);
+ mHidlToAidlSensorAdapter.setScheduler(scheduler);
+ mHidlToAidlSensorAdapter.handleUserChanged(USER_ID);
+ }
+
+ @Test
+ public void lockoutTimedResetViaClient() {
+ setLockoutTimed();
+
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+ new FingerprintResetLockoutClient(mContext,
+ mHidlToAidlSensorAdapter.getLazySession(),
+ USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+ mLooper.dispatchAll();
+
+ verify(mAlarmManager).setExact(anyInt(), anyLong(), any());
+ verify(mLockoutResetDispatcherForClient).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutTimedResetViaCallback() {
+ setLockoutTimed();
+
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutCleared();
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForSensor, times(2)).notifyLockoutResetCallbacks(eq(
+ SENSOR_ID));
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutPermanentResetViaCallback() {
+ setLockoutPermanent();
+
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutCleared();
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForSensor, times(2)).notifyLockoutResetCallbacks(eq(
+ SENSOR_ID));
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutPermanentResetViaClient() {
+ setLockoutPermanent();
+
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+ new FingerprintResetLockoutClient(mContext,
+ mHidlToAidlSensorAdapter.getLazySession(),
+ USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+ mLooper.dispatchAll();
+
+ verify(mAlarmManager, atLeast(1)).setExact(anyInt(), anyLong(), any());
+ verify(mLockoutResetDispatcherForClient).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void verifyOnEnrollSuccessCallback() {
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(new FingerprintEnrollClient(
+ mContext, mHidlToAidlSensorAdapter.getLazySession(), null /* token */,
+ 1 /* requestId */, null /* listener */, USER_ID, HAT, TAG, mBiometricUtils,
+ SENSOR_ID, mLogger, mBiometricContext,
+ mHidlToAidlSensorAdapter.getSensorProperties(), null, null,
+ mAuthenticationStateListeners, 5 /* maxTemplatesPerUser */, ENROLL_ENROLL));
+ mLooper.dispatchAll();
+
+ verify(mAidlResponseHandlerCallback).onEnrollSuccess();
+ }
+
+ private void setLockoutPermanent() {
+ for (int i = 0; i < 20; i++) {
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .addFailedAttemptForUser(USER_ID);
+ }
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_PERMANENT);
+ }
+
+ private void setLockoutTimed() {
+ for (int i = 0; i < 5; i++) {
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .addFailedAttemptForUser(USER_ID);
+ }
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_TIMED);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
similarity index 79%
rename from services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
rename to services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
index b78ba82..d723e87 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
@@ -41,7 +41,7 @@
@Presubmit
@SmallTest
-public class AidlToHidlAdapterTest {
+public class HidlToAidlSessionAdapterTest {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -54,11 +54,11 @@
private final long mChallenge = 100L;
private final int mUserId = 0;
- private AidlToHidlAdapter mAidlToHidlAdapter;
+ private HidlToAidlSessionAdapter mHidlToAidlSessionAdapter;
@Before
public void setUp() {
- mAidlToHidlAdapter = new AidlToHidlAdapter(() -> mSession, mUserId,
+ mHidlToAidlSessionAdapter = new HidlToAidlSessionAdapter(() -> mSession, mUserId,
mAidlResponseHandler);
mHardwareAuthToken.timestamp = new Timestamp();
mHardwareAuthToken.mac = new byte[10];
@@ -67,7 +67,7 @@
@Test
public void testGenerateChallenge() throws RemoteException {
when(mSession.preEnroll()).thenReturn(mChallenge);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
verify(mSession).preEnroll();
verify(mAidlResponseHandler).onChallengeGenerated(mChallenge);
@@ -75,7 +75,7 @@
@Test
public void testRevokeChallenge() throws RemoteException {
- mAidlToHidlAdapter.revokeChallenge(mChallenge);
+ mHidlToAidlSessionAdapter.revokeChallenge(mChallenge);
verify(mSession).postEnroll();
verify(mAidlResponseHandler).onChallengeRevoked(0L);
@@ -84,9 +84,9 @@
@Test
public void testEnroll() throws RemoteException {
final ICancellationSignal cancellationSignal =
- mAidlToHidlAdapter.enroll(mHardwareAuthToken);
+ mHidlToAidlSessionAdapter.enroll(mHardwareAuthToken);
- verify(mSession).enroll(any(), anyInt(), eq(AidlToHidlAdapter.ENROLL_TIMEOUT_SEC));
+ verify(mSession).enroll(any(), anyInt(), eq(HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC));
cancellationSignal.cancel();
@@ -96,7 +96,8 @@
@Test
public void testAuthenticate() throws RemoteException {
final int operationId = 2;
- final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+ final ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.authenticate(
+ operationId);
verify(mSession).authenticate(operationId, mUserId);
@@ -107,7 +108,8 @@
@Test
public void testDetectInteraction() throws RemoteException {
- final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+ final ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter
+ .detectInteraction();
verify(mSession).authenticate(0 /* operationId */, mUserId);
@@ -118,7 +120,7 @@
@Test
public void testEnumerateEnrollments() throws RemoteException {
- mAidlToHidlAdapter.enumerateEnrollments();
+ mHidlToAidlSessionAdapter.enumerateEnrollments();
verify(mSession).enumerate();
}
@@ -126,7 +128,7 @@
@Test
public void testRemoveEnrollment() throws RemoteException {
final int[] enrollmentIds = new int[]{1};
- mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+ mHidlToAidlSessionAdapter.removeEnrollments(enrollmentIds);
verify(mSession).remove(mUserId, enrollmentIds[0]);
}
@@ -134,14 +136,14 @@
@Test
public void testRemoveMultipleEnrollments() throws RemoteException {
final int[] enrollmentIds = new int[]{1, 2};
- mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+ mHidlToAidlSessionAdapter.removeEnrollments(enrollmentIds);
verify(mSession).remove(mUserId, 0);
}
@Test
public void testResetLockout() throws RemoteException {
- mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+ mHidlToAidlSessionAdapter.resetLockout(mHardwareAuthToken);
verify(mAidlResponseHandler).onLockoutCleared();
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
index edfe1b4..071d571 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 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.
@@ -24,10 +24,8 @@
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.companion.virtual.camera.VirtualCameraCallback;
import android.companion.virtual.camera.VirtualCameraConfig;
-import android.companion.virtual.camera.VirtualCameraMetadata;
import android.companion.virtual.camera.VirtualCameraStreamConfig;
import android.companion.virtualcamera.IVirtualCameraService;
import android.companion.virtualcamera.VirtualCameraConfiguration;
@@ -156,10 +154,6 @@
@NonNull VirtualCameraStreamConfig streamConfig) {}
@Override
- public void onProcessCaptureRequest(
- int streamId, long frameId, @Nullable VirtualCameraMetadata metadata) {}
-
- @Override
public void onStreamClosed(int streamId) {}
};
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java
new file mode 100644
index 0000000..d9a38eb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraStreamConfigTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2023 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.companion.virtual.camera;
+
+import android.companion.virtual.camera.VirtualCameraStreamConfig;
+import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.testing.EqualsTester;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class VirtualCameraStreamConfigTest {
+
+ private static final int VGA_WIDTH = 640;
+ private static final int VGA_HEIGHT = 480;
+
+ private static final int QVGA_WIDTH = 320;
+ private static final int QVGA_HEIGHT = 240;
+
+ @Test
+ public void testEquals() {
+ VirtualCameraStreamConfig vgaYuvStreamConfig = new VirtualCameraStreamConfig(VGA_WIDTH,
+ VGA_HEIGHT,
+ ImageFormat.YUV_420_888);
+ VirtualCameraStreamConfig qvgaYuvStreamConfig = new VirtualCameraStreamConfig(QVGA_WIDTH,
+ QVGA_HEIGHT, ImageFormat.YUV_420_888);
+ VirtualCameraStreamConfig vgaRgbaStreamConfig = new VirtualCameraStreamConfig(VGA_WIDTH,
+ VGA_HEIGHT, PixelFormat.RGBA_8888);
+
+ new EqualsTester()
+ .addEqualityGroup(vgaYuvStreamConfig, reparcel(vgaYuvStreamConfig))
+ .addEqualityGroup(qvgaYuvStreamConfig, reparcel(qvgaYuvStreamConfig))
+ .addEqualityGroup(vgaRgbaStreamConfig, reparcel(vgaRgbaStreamConfig))
+ .testEquals();
+ }
+
+ private static VirtualCameraStreamConfig reparcel(VirtualCameraStreamConfig config) {
+ Parcel parcel = Parcel.obtain();
+ try {
+ config.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+ return VirtualCameraStreamConfig.CREATOR.createFromParcel(parcel);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 943a9c47..1dd64ff 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -18,7 +18,6 @@
import static android.os.UserHandle.USER_SYSTEM;
import static com.android.server.devicepolicy.DpmTestUtils.writeInputStreamToFile;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -222,21 +221,21 @@
prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R);
// Pretend some packages are suspended.
- when(getServices().packageManagerInternal.isSuspendingAnyPackages(
- PLATFORM_PACKAGE_NAME, USER_SYSTEM)).thenReturn(true);
+ when(getServices().packageManagerInternal.isAdminSuspendingAnyPackages(
+ USER_SYSTEM)).thenReturn(true);
final DevicePolicyManagerServiceTestable dpms = bootDpmsUp();
verify(getServices().packageManagerInternal, never())
- .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);
+ .unsuspendAdminSuspendedPackages(USER_SYSTEM);
sendBroadcastWithUser(dpms, Intent.ACTION_USER_STARTED, USER_SYSTEM);
// Verify that actual package suspension state is not modified after user start
verify(getServices().packageManagerInternal, never())
- .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);
+ .unsuspendAdminSuspendedPackages(USER_SYSTEM);
verify(getServices().ipackageManager, never()).setPackagesSuspendedAsUser(
- any(), anyBoolean(), any(), any(), any(), anyInt(), any(), anyInt());
+ any(), anyBoolean(), any(), any(), any(), anyInt(), any(), anyInt(), anyInt());
final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext);
poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID);
@@ -255,14 +254,14 @@
prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q);
// Pretend some packages are suspended.
- when(getServices().packageManagerInternal.isSuspendingAnyPackages(
- PLATFORM_PACKAGE_NAME, USER_SYSTEM)).thenReturn(true);
+ when(getServices().packageManagerInternal.isAdminSuspendingAnyPackages(
+ USER_SYSTEM)).thenReturn(true);
final DevicePolicyManagerServiceTestable dpms = bootDpmsUp();
// Verify that apps get unsuspended.
verify(getServices().packageManagerInternal)
- .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);
+ .unsuspendAdminSuspendedPackages(USER_SYSTEM);
final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext);
poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index f4dac2c..2470403 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -63,7 +63,6 @@
import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_PROFILE_OFF_DEADLINE;
import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_TURN_PROFILE_ON_NOTIFICATION;
import static com.android.server.devicepolicy.DpmMockContext.CALLER_USER_HANDLE;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.testutils.TestUtils.assertExpectException;
import static com.google.common.truth.Truth.assertThat;
@@ -5080,7 +5079,7 @@
verify(getServices().iwindowManager).refreshScreenCaptureDisabled();
// Unsuspend personal apps
verify(getServices().packageManagerInternal)
- .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, UserHandle.USER_SYSTEM);
+ .unsuspendAdminSuspendedPackages(UserHandle.USER_SYSTEM);
verify(getServices().subscriptionManager).setSubscriptionUserHandle(0, null);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
FLAG_ENABLE_WORK_PROFILE_TELEPHONY, "false", false);
@@ -7535,7 +7534,7 @@
.cancel(eq(SystemMessageProto.SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED));
// Verify that the apps are NOT unsuspeded.
verify(getServices().ipackageManager, never()).setPackagesSuspendedAsUser(
- any(), eq(false), any(), any(), any(), anyInt(), any(), anyInt());
+ any(), eq(false), any(), any(), any(), anyInt(), any(), anyInt(), anyInt());
// Verify that DPC is invoked to check policy compliance.
verify(mContext.spiedContext).startActivityAsUser(
MockUtils.checkIntentAction(ACTION_CHECK_POLICY_COMPLIANCE),
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index b4281d63c..101ea6d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -183,6 +183,13 @@
}
@Test
+ public void testIsUserInitialized_NonExistentUserReturnsFalse() {
+ int nonExistentUserId = UserHandle.USER_NULL;
+ assertThat(mUserManagerService.isUserInitialized(nonExistentUserId))
+ .isFalse();
+ }
+
+ @Test
public void testSetUserRestrictionWithIncorrectID() throws Exception {
int incorrectId = 1;
while (mUserManagerService.userExists(incorrectId)) {
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 8487903..89056cc 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -64,17 +64,18 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.intThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
import android.content.Context;
@@ -99,7 +100,6 @@
import android.view.Display;
import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -110,6 +110,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -125,6 +126,7 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
/**
@@ -175,6 +177,9 @@
private static final int ASSERT_RETRY_ATTEMPTS = 20;
private static final int ASSERT_RETRY_DELAY_MILLISECONDS = 500;
+ @DurationMillisLong
+ private static final long FLUSH_TIMEOUT_MILLISECONDS = 5_000;
+
/** Mock variable used in {@link MyInjector#isPackageInstalled(String, int, int)} */
private static boolean isPackageInstalled = true;
@@ -563,6 +568,7 @@
mInjector = new MyInjector(myContext, Looper.getMainLooper());
mController = setupController();
setupInitialUsageHistory();
+ flushHandler(mController);
}
@After
@@ -571,12 +577,9 @@
}
@Test
- @FlakyTest(bugId = 185169504)
public void testBoundWidgetPackageExempt() throws Exception {
assumeTrue(mInjector.getContext().getSystemService(AppWidgetManager.class) != null);
- assertEquals(STANDBY_BUCKET_ACTIVE,
- mController.getAppStandbyBucket(PACKAGE_EXEMPTED_1, USER_ID,
- mInjector.mElapsedRealtime, false));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_EXEMPTED_1);
}
@Test
@@ -654,7 +657,6 @@
}
@Test
- @FlakyTest(bugId = 185169504)
public void testIsAppIdle_Charging() throws Exception {
TestParoleListener paroleListener = new TestParoleListener();
mController.addListener(paroleListener);
@@ -662,7 +664,7 @@
setChargingState(mController, false);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
assertFalse(mController.isInParole());
@@ -671,7 +673,7 @@
setChargingState(mController, true);
paroleListener.awaitOnLatch(2000);
assertTrue(paroleListener.getParoleState());
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
assertTrue(mController.isInParole());
@@ -680,14 +682,13 @@
setChargingState(mController, false);
paroleListener.awaitOnLatch(2000);
assertFalse(paroleListener.getParoleState());
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
assertFalse(mController.isInParole());
}
@Test
- @FlakyTest(bugId = 185169504)
public void testIsAppIdle_Enabled() throws Exception {
setChargingState(mController, false);
TestParoleListener paroleListener = new TestParoleListener();
@@ -696,7 +697,7 @@
setAppIdleEnabled(mController, true);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
@@ -711,7 +712,7 @@
setAppIdleEnabled(mController, true);
paroleListener.awaitOnLatch(2000);
assertFalse(paroleListener.getParoleState());
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
}
@@ -723,6 +724,7 @@
private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket,
int userId) {
mInjector.mElapsedRealtime = elapsedTime;
+ flushHandler(controller);
controller.checkIdleStates(userId);
assertEquals(bucket,
controller.getAppStandbyBucket(PACKAGE_1, userId, mInjector.mElapsedRealtime,
@@ -744,50 +746,85 @@
}
private int getStandbyBucket(int userId, AppStandbyController controller, String packageName) {
+ flushHandler(controller);
return controller.getAppStandbyBucket(packageName, userId, mInjector.mElapsedRealtime,
true);
}
+ private List<AppStandbyInfo> getStandbyBuckets(int userId) {
+ flushHandler(mController);
+ return mController.getAppStandbyBuckets(userId);
+ }
+
private int getStandbyBucketReason(String packageName) {
+ flushHandler(mController);
return mController.getAppStandbyBucketReason(packageName, USER_ID,
mInjector.mElapsedRealtime);
}
- private void assertBucket(int bucket) throws InterruptedException {
- assertBucket(bucket, PACKAGE_1);
+ private void waitAndAssertBucket(int bucket, String pkg) {
+ waitAndAssertBucket(mController, bucket, pkg);
}
- private void assertBucket(int bucket, String pkg) throws InterruptedException {
- int retries = 0;
- do {
- if (bucket == getStandbyBucket(mController, pkg)) {
- // Success
- return;
- }
- Thread.sleep(ASSERT_RETRY_DELAY_MILLISECONDS);
- retries++;
- } while(retries < ASSERT_RETRY_ATTEMPTS);
- // try one last time
- assertEquals(bucket, getStandbyBucket(mController, pkg));
+ private void waitAndAssertBucket(AppStandbyController controller, int bucket, String pkg) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(pkg);
+ sb.append(" was not in the ");
+ sb.append(UsageStatsManager.standbyBucketToString(bucket));
+ sb.append(" (");
+ sb.append(bucket);
+ sb.append(") bucket.");
+ waitAndAssertBucket(sb.toString(), controller, bucket, pkg);
}
- private void assertNotBucket(int bucket) throws InterruptedException {
- final String pkg = PACKAGE_1;
+ private void waitAndAssertBucket(String msg, int bucket, String pkg) {
+ waitAndAssertBucket(msg, mController, bucket, pkg);
+ }
+
+ private void waitAndAssertBucket(String msg, AppStandbyController controller, int bucket,
+ String pkg) {
+ waitAndAssertBucket(msg, controller, bucket, USER_ID, pkg);
+ }
+
+ private void waitAndAssertBucket(String msg, AppStandbyController controller, int bucket,
+ int userId,
+ String pkg) {
+ waitUntil(() -> bucket == getStandbyBucket(userId, controller, pkg));
+ assertEquals(msg, bucket, getStandbyBucket(userId, controller, pkg));
+ }
+
+ private void waitAndAssertNotBucket(int bucket, String pkg) {
+ waitAndAssertNotBucket(mController, bucket, pkg);
+ }
+
+ private void waitAndAssertNotBucket(AppStandbyController controller, int bucket, String pkg) {
+ waitUntil(() -> bucket != getStandbyBucket(controller, pkg));
+ assertNotEquals(bucket, getStandbyBucket(controller, pkg));
+ }
+
+ private void waitAndAssertLastNoteEvent(int event) {
+ waitUntil(() -> {
+ flushHandler(mController);
+ return event == mInjector.mLastNoteEvent;
+ });
+ assertEquals(event, mInjector.mLastNoteEvent);
+ }
+
+ // Waits until condition is true or times out.
+ private void waitUntil(BooleanSupplier resultSupplier) {
int retries = 0;
do {
- if (bucket != getStandbyBucket(mController, pkg)) {
- // Success
- return;
+ if (resultSupplier.getAsBoolean()) return;
+ try {
+ Thread.sleep(ASSERT_RETRY_DELAY_MILLISECONDS);
+ } catch (InterruptedException ie) {
+ // Do nothing
}
- Thread.sleep(ASSERT_RETRY_DELAY_MILLISECONDS);
retries++;
- } while(retries < ASSERT_RETRY_ATTEMPTS);
- // try one last time
- assertNotEquals(bucket, getStandbyBucket(mController, pkg));
+ } while (retries < ASSERT_RETRY_ATTEMPTS);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testBuckets() throws Exception {
assertTimeout(mController, 0, STANDBY_BUCKET_NEVER);
@@ -820,14 +857,13 @@
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSetAppStandbyBucket() throws Exception {
// For a known package, standby bucket should be set properly
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
mInjector.mElapsedRealtime = HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_TIMEOUT);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// For an unknown package, standby bucket should not be set, hence NEVER is returned
// Ensure the unknown package is not already in history by removing it
@@ -836,21 +872,20 @@
mController.setAppStandbyBucket(PACKAGE_UNKNOWN, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_TIMEOUT);
isPackageInstalled = true; // Reset mocked variable for other tests
- assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN));
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_UNKNOWN);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testAppStandbyBucketOnInstallAndUninstall() throws Exception {
// On package install, standby bucket should be ACTIVE
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_UNKNOWN);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_UNKNOWN));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_UNKNOWN);
// On uninstall, package should not exist in history and should return a NEVER bucket
mController.clearAppIdleForPackage(PACKAGE_UNKNOWN, USER_ID);
- assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN));
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_UNKNOWN);
// Ensure uninstalled app is not in history
- List<AppStandbyInfo> buckets = mController.getAppStandbyBuckets(USER_ID);
+ List<AppStandbyInfo> buckets = getStandbyBuckets(USER_ID);
for(AppStandbyInfo bucket : buckets) {
if (bucket.mPackageName.equals(PACKAGE_UNKNOWN)) {
fail("packageName found in app idle history after uninstall.");
@@ -859,7 +894,6 @@
}
@Test
- @FlakyTest(bugId = 185169504)
public void testScreenTimeAndBuckets() throws Exception {
mInjector.setDisplayOn(false);
@@ -876,22 +910,21 @@
// RARE bucket, should fail because the screen wasn't ON.
mInjector.mElapsedRealtime = RARE_THRESHOLD + 1;
mController.checkIdleStates(USER_ID);
- assertNotEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertNotBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mInjector.setDisplayOn(true);
assertTimeout(mController, RARE_THRESHOLD + 2 * HOUR_MS + 1, STANDBY_BUCKET_RARE);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testForcedIdle() throws Exception {
mController.forceIdleState(PACKAGE_1, USER_ID, true);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
mController.forceIdleState(PACKAGE_1, USER_ID, false);
- assertEquals(STANDBY_BUCKET_ACTIVE, mController.getAppStandbyBucket(PACKAGE_1, USER_ID, 0,
- true));
+
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
}
@@ -901,15 +934,15 @@
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = 1;
rearmQuotaBumpLatch(PACKAGE_1, USER_ID);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
assertFalse(mQuotaBumpLatch.await(1, TimeUnit.SECONDS));
}
@@ -917,9 +950,10 @@
public void testNotificationEvent_bucketPromotion_changePromotedBucket() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
+ mInjector.mElapsedRealtime += RARE_THRESHOLD + 1;
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// TODO: Avoid hardcoding these string constants.
mInjector.mSettingsBuilder.setInt("notification_seen_promoted_bucket",
@@ -928,11 +962,10 @@
mInjector.getDeviceConfigProperties());
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testNotificationEvent_quotaBump() throws Exception {
mInjector.mSettingsBuilder
.setBoolean("trigger_quota_bump_on_notification_seen", true);
@@ -942,7 +975,7 @@
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = RARE_THRESHOLD * 2;
setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM);
@@ -951,83 +984,80 @@
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
assertTrue(mQuotaBumpLatch.await(1, TimeUnit.SECONDS));
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSlicePinnedEvent() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = 1;
reportEvent(mController, SLICE_PINNED, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, SLICE_PINNED, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSlicePinnedPrivEvent() throws Exception {
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, SLICE_PINNED_PRIV, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionTimedOut() throws Exception {
// Set it to timeout or usage, so that prediction can override it
mInjector.mElapsedRealtime = HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Fast forward 12 hours
mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD;
mController.checkIdleStates(USER_ID);
// Should still be in predicted bucket, since prediction timeout is 1 day since prediction
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Fast forward two more hours
mInjector.mElapsedRealtime += 2 * HOUR_MS;
mController.checkIdleStates(USER_ID);
// Should have now applied prediction timeout
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// Fast forward RARE bucket
mInjector.mElapsedRealtime += RARE_THRESHOLD;
mController.checkIdleStates(USER_ID);
// Should continue to apply prediction timeout
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
+ @Ignore("b/317086276")
public void testOverrides() throws Exception {
// Can force to NEVER
mInjector.mElapsedRealtime = HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_1);
// Prediction can't override FORCED reasons
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
// Prediction can't override NEVER
mInjector.mElapsedRealtime = 2 * HOUR_MS;
@@ -1035,115 +1065,114 @@
REASON_MAIN_DEFAULT);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_1);
// Prediction can't set to NEVER
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_USAGE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Prediction can't remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Force from user can remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// Force from system can remove from RESTRICTED if it was put it in due to system
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_PREDICTED);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Non-user usage can't remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_SYSTEM_INTERACTION);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_SYNC_ADAPTER);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_NON_DOZE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Explicit user usage can remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_USAGE | REASON_SUB_USAGE_MOVE_TO_FOREGROUND);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testTimeout() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = 2000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// bucketing works after timeout
mInjector.mElapsedRealtime = mController.mPredictionTimeoutMillis - 100;
mController.checkIdleStates(USER_ID);
// Use recent prediction
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
// Way past prediction timeout, use system thresholds
mInjector.mElapsedRealtime = RARE_THRESHOLD;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
/** Test that timeouts still work properly even if invalid configuration values are set. */
@Test
- @FlakyTest(bugId = 185169504)
public void testTimeout_InvalidThresholds() throws Exception {
mInjector.mSettingsBuilder
.setLong("screen_threshold_active", -1)
@@ -1161,19 +1190,19 @@
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = HOUR_MS;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
mInjector.mElapsedRealtime = 2 * HOUR_MS;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mInjector.mElapsedRealtime = 4 * HOUR_MS;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
@@ -1181,74 +1210,72 @@
* timeout has passed.
*/
@Test
- @FlakyTest(bugId = 185169504)
+ @Ignore("b/317086276")
public void testTimeoutBeforeRestricted() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
// Bucket shouldn't change
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// bucketing works after timeout
mInjector.mElapsedRealtime += DAY_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Way past all timeouts. Make sure timeout processing doesn't raise bucket.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
* Test that an app is put into the RESTRICTED bucket after enough time has passed.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictedDelay() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += mInjector.getAutoRestrictedBucketDelayMs() - 5000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
// Bucket shouldn't change
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// bucketing works after timeout
mInjector.mElapsedRealtime += 6000;
Thread.sleep(6000);
// Enough time has passed. The app should automatically be put into the RESTRICTED bucket.
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
* Test that an app is put into the RESTRICTED bucket after enough time has passed.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictedDelay_DelayChange() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mAutoRestrictedBucketDelayMs = 2 * HOUR_MS;
mInjector.mElapsedRealtime += 2 * HOUR_MS - 5000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
// Bucket shouldn't change
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// bucketing works after timeout
mInjector.mElapsedRealtime += 6000;
Thread.sleep(6000);
// Enough time has passed. The app should automatically be put into the RESTRICTED bucket.
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
@@ -1256,36 +1283,35 @@
* a low bucket after the RESTRICTED timeout.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictedTimeoutOverridesRestoredLowBucketPrediction() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Predict to RARE Not long enough to time out into RESTRICTED.
mInjector.mElapsedRealtime += RARE_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
// Add a short timeout event
mInjector.mElapsedRealtime += 1000;
reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += 1000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Long enough that it could have timed out into RESTRICTED. Instead of reverting to
// predicted RARE, should go into RESTRICTED
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Ensure that prediction can still raise it out despite this override.
mInjector.mElapsedRealtime += 1;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
/**
@@ -1293,7 +1319,6 @@
* a low bucket after the RESTRICTED timeout.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictedTimeoutOverridesPredictionLowBucket() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
@@ -1301,7 +1326,7 @@
mInjector.mElapsedRealtime += RARE_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mInjector.mElapsedRealtime += 1;
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
@@ -1310,10 +1335,10 @@
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
@@ -1321,261 +1346,250 @@
* interaction.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemInteractionOverridesRestrictedTimeout() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Long enough that it could have timed out into RESTRICTED.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Report system interaction.
mInjector.mElapsedRealtime += 1000;
reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
// Ensure that it's raised out of RESTRICTED for the system interaction elevation duration.
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += 1000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Elevation duration over. Should fall back down.
mInjector.mElapsedRealtime += 10 * MINUTE_MS;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionRaiseFromRestrictedTimeout_highBucket() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
// Way past all timeouts. App times out into RESTRICTED bucket.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Since the app timed out into RESTRICTED, prediction should be able to remove from the
// bucket.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionRaiseFromRestrictedTimeout_lowBucket() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
// Way past all timeouts. App times out into RESTRICTED bucket.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Prediction into a low bucket means no expectation of the app being used, so we shouldn't
// elevate the app from RESTRICTED.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testCascadingTimeouts() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
reportEvent(mController, NOTIFICATION_SEEN, 1000, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = 2000 + mController.mStrongUsageTimeoutMillis;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_WORKING_SET);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mInjector.mElapsedRealtime = 2000 + mController.mNotificationSeenTimeoutMillis;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testOverlappingTimeouts() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
reportEvent(mController, NOTIFICATION_SEEN, 1000, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Overlapping USER_INTERACTION before previous one times out
reportEvent(mController, USER_INTERACTION, mController.mStrongUsageTimeoutMillis - 1000,
PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Still in ACTIVE after first USER_INTERACTION times out
mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Both timed out, so NOTIFICATION_SEEN timeout should be effective
mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_WORKING_SET);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemInteractionTimeout() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
// Fast forward to RARE
mInjector.mElapsedRealtime = RARE_THRESHOLD + 100;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
// Trigger a SYSTEM_INTERACTION and verify bucket
reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify it's still in ACTIVE close to end of timeout
mInjector.mElapsedRealtime += mController.mSystemInteractionTimeoutMillis - 100;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify bucket moves to RARE after timeout
mInjector.mElapsedRealtime += 200;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testInitialForegroundServiceTimeout() throws Exception {
mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100;
// Make sure app is in NEVER bucket
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
REASON_MAIN_FORCED_BY_USER);
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_NEVER);
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_1);
mInjector.mElapsedRealtime += 100;
// Trigger a FOREGROUND_SERVICE_START and verify bucket
reportEvent(mController, FOREGROUND_SERVICE_START, mInjector.mElapsedRealtime, PACKAGE_1);
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify it's still in ACTIVE close to end of timeout
mInjector.mElapsedRealtime += mController.mInitialForegroundServiceStartTimeoutMillis - 100;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify bucket moves to RARE after timeout
mInjector.mElapsedRealtime += 200;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
// Trigger a FOREGROUND_SERVICE_START again
reportEvent(mController, FOREGROUND_SERVICE_START, mInjector.mElapsedRealtime, PACKAGE_1);
mController.checkIdleStates(USER_ID);
// Bucket should not be immediately elevated on subsequent service starts
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionNotOverridden() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = WORKING_SET_THRESHOLD - 1000;
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Falls back to WORKING_SET
mInjector.mElapsedRealtime += 5000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_WORKING_SET);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// Predict to ACTIVE
mInjector.mElapsedRealtime += 1000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// CheckIdleStates should not change the prediction
mInjector.mElapsedRealtime += 1000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionStrikesBack() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Predict to FREQUENT
mInjector.mElapsedRealtime = RARE_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
// Add a short timeout event
mInjector.mElapsedRealtime += 1000;
reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += 1000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify it reverted to predicted
mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD / 2;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemForcedFlags_NotAddedForUserForce() throws Exception {
final int expectedReason = REASON_MAIN_FORCED_BY_USER;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
assertEquals(expectedReason, getStandbyBucketReason(PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
assertEquals(expectedReason, getStandbyBucketReason(PACKAGE_1));
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemForcedFlags_AddedForSystemForce() throws Exception {
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_DEFAULT);
@@ -1584,13 +1598,13 @@
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE,
getStandbyBucketReason(PACKAGE_1));
mController.restrictApp(PACKAGE_1, USER_ID, REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Flags should be combined
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE
@@ -1598,7 +1612,6 @@
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemForcedFlags_SystemForceChangesBuckets() throws Exception {
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_DEFAULT);
@@ -1607,14 +1620,14 @@
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
- assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE,
getStandbyBucketReason(PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE);
- assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
// Flags should be combined
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE
@@ -1623,20 +1636,19 @@
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
// Flags should be combined
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY,
getStandbyBucketReason(PACKAGE_1));
mController.restrictApp(PACKAGE_1, USER_ID, REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Flags should not be combined since the bucket changed.
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED,
getStandbyBucketReason(PACKAGE_1));
}
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictApp_MainReason() throws Exception {
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_DEFAULT);
@@ -1644,11 +1656,11 @@
mController.restrictApp(PACKAGE_1, USER_ID, REASON_MAIN_PREDICTED, 0);
// Call should be ignored.
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.restrictApp(PACKAGE_1, USER_ID, REASON_MAIN_FORCED_BY_USER, 0);
// Call should go through
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
@Test
@@ -1724,15 +1736,15 @@
}
@Test
- @FlakyTest(bugId = 185169504)
public void testUserInteraction_CrossProfile() throws Exception {
mInjector.mRunningUsers = new int[] {USER_ID, USER_ID2, USER_ID3};
mInjector.mCrossProfileTargets = Arrays.asList(USER_HANDLE_USER2);
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals("Cross profile connected package bucket should be elevated on usage",
- STANDBY_BUCKET_ACTIVE, getStandbyBucket(USER_ID2, mController, PACKAGE_1));
- assertEquals("Not Cross profile connected package bucket should not be elevated on usage",
- STANDBY_BUCKET_NEVER, getStandbyBucket(USER_ID3, mController, PACKAGE_1));
+ waitAndAssertBucket("Cross profile connected package bucket should be elevated on usage",
+ mController, STANDBY_BUCKET_ACTIVE, USER_ID2, PACKAGE_1);
+ waitAndAssertBucket(
+ "Not Cross profile connected package bucket should not be elevated on usage",
+ mController, STANDBY_BUCKET_NEVER, USER_ID3, PACKAGE_1);
assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE, USER_ID);
assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE, USER_ID2);
@@ -1742,51 +1754,50 @@
mInjector.mCrossProfileTargets = Collections.emptyList();
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals("No longer cross profile connected package bucket should not be "
- + "elevated on usage",
- STANDBY_BUCKET_WORKING_SET, getStandbyBucket(USER_ID2, mController, PACKAGE_1));
+ waitAndAssertBucket("No longer cross profile connected package bucket should not be "
+ + "elevated on usage", mController, STANDBY_BUCKET_WORKING_SET, USER_ID2,
+ PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testUnexemptedSyncScheduled() throws Exception {
rearmLatch(PACKAGE_1);
mController.addListener(mListener);
- assertEquals("Test package did not start in the Never bucket", STANDBY_BUCKET_NEVER,
- getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Test package did not start in the Never bucket", STANDBY_BUCKET_NEVER,
+ PACKAGE_1);
mController.postReportSyncScheduled(PACKAGE_1, USER_ID, false);
mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
- assertEquals("Unexempted sync scheduled should bring the package out of the Never bucket",
- STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(
+ "Unexempted sync scheduled should bring the package out of the Never bucket",
+ STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM);
rearmLatch(PACKAGE_1);
mController.postReportSyncScheduled(PACKAGE_1, USER_ID, false);
mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
- assertEquals("Unexempted sync scheduled should not elevate a non Never bucket",
- STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Unexempted sync scheduled should not elevate a non Never bucket",
+ STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testExemptedSyncScheduled() throws Exception {
setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM);
mInjector.mDeviceIdleMode = true;
rearmLatch(PACKAGE_1);
mController.postReportSyncScheduled(PACKAGE_1, USER_ID, true);
mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
- assertEquals("Exempted sync scheduled in doze should set bucket to working set",
- STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Exempted sync scheduled in doze should set bucket to working set",
+ STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM);
mInjector.mDeviceIdleMode = false;
rearmLatch(PACKAGE_1);
mController.postReportSyncScheduled(PACKAGE_1, USER_ID, true);
mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
- assertEquals("Exempted sync scheduled while not in doze should set bucket to active",
- STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Exempted sync scheduled while not in doze should set bucket to active",
+ STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
@@ -1796,14 +1807,14 @@
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Updates shouldn't change bucket if the app was forced by the system for a non-buggy
// reason.
@@ -1814,11 +1825,11 @@
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Updates should change bucket if the app was forced by the system for a buggy reason.
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
@@ -1827,11 +1838,11 @@
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertNotEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertNotBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Updates shouldn't change bucket if the app was forced by the system for more than just
// a buggy reason.
@@ -1842,13 +1853,13 @@
| REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE,
getStandbyBucketReason(PACKAGE_1));
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Updates shouldn't change bucket if the app was forced by the user.
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
@@ -1857,11 +1868,11 @@
REASON_MAIN_FORCED_BY_USER);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
@Test
@@ -1876,37 +1887,37 @@
mController.setAppStandbyBucket(PACKAGE_SYSTEM_HEADFULL, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_RARE, PACKAGE_SYSTEM_HEADFULL);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_SYSTEM_HEADFULL);
// Make sure headless system apps don't get lowered.
mController.setAppStandbyBucket(PACKAGE_SYSTEM_HEADLESS, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_SYSTEM_HEADLESS);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_SYSTEM_HEADLESS);
// Package 1 doesn't have activities and is headless, but is not a system app, so it can
// be lowered.
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
public void testWellbeingAppElevated() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_WELLBEING);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_WELLBEING);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_WELLBEING);
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
// Make sure the default wellbeing app does not get lowered below WORKING_SET.
mController.setAppStandbyBucket(PACKAGE_WELLBEING, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_WELLBEING);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_WELLBEING);
// A non default wellbeing app should be able to fall lower than WORKING_SET.
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
@@ -1914,22 +1925,22 @@
mInjector.mClockApps.add(Pair.create(PACKAGE_1, UID_1));
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_2);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_2);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_2);
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
// Make sure a clock app does not get lowered below WORKING_SET.
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// A non clock app should be able to fall lower than WORKING_SET.
mController.setAppStandbyBucket(PACKAGE_2, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_RARE, PACKAGE_2);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_2);
}
@Test
@@ -2067,13 +2078,13 @@
public void testBackgroundLocationBucket() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime,
PACKAGE_BACKGROUND_LOCATION);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_BACKGROUND_LOCATION);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_BACKGROUND_LOCATION);
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
// Make sure PACKAGE_BACKGROUND_LOCATION does not get lowered than STANDBY_BUCKET_FREQUENT.
mController.setAppStandbyBucket(PACKAGE_BACKGROUND_LOCATION, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_BACKGROUND_LOCATION);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_BACKGROUND_LOCATION);
}
@Test
@@ -2083,41 +2094,41 @@
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE);
// Since we're staying on the PACKAGE_ACTIVE side, noteEvent shouldn't be called.
// Reset the last event to confirm the method isn't called.
mInjector.mLastNoteEvent = BatteryStats.HistoryItem.EVENT_NONE;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_NONE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_NONE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE);
// Since we're staying on the PACKAGE_ACTIVE side, noteEvent shouldn't be called.
// Reset the last event to confirm the method isn't called.
mInjector.mLastNoteEvent = BatteryStats.HistoryItem.EVENT_NONE;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_NONE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_NONE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_EXEMPTED,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE);
}
private String getAdminAppsStr(int userId) {
@@ -2187,8 +2198,7 @@
rearmLatch(pkg);
mController.setAppStandbyBucket(pkg, user, bucket, reason);
mStateChangedLatch.await(1, TimeUnit.SECONDS);
- assertEquals("Failed to set package bucket", bucket,
- getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Failed to set package bucket", bucket, PACKAGE_1);
}
private void rearmLatch(String pkgName) {
@@ -2205,4 +2215,12 @@
mLatchUserId = userId;
mQuotaBumpLatch = new CountDownLatch(1);
}
+
+ private void flushHandler(AppStandbyController controller) {
+ assertTrue("Failed to flush handler!", controller.flushHandler(FLUSH_TIMEOUT_MILLISECONDS));
+ // Some AppStandbyController handler messages queue another handler message. Flush again
+ // to catch those as well.
+ assertTrue("Failed to flush handler (the second time)!",
+ controller.flushHandler(FLUSH_TIMEOUT_MILLISECONDS));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 260ee396..30843d2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -245,8 +245,8 @@
}
@Test
- @TestParameters({"{origin: ORIGIN_USER}", "{origin: ORIGIN_INIT}", "{origin: ORIGIN_INIT_USER}",
- "{origin: ORIGIN_SYSTEM_OR_SYSTEMUI}"})
+ @TestParameters({"{origin: ORIGIN_USER}", "{origin: ORIGIN_INIT}",
+ "{origin: ORIGIN_INIT_USER}"})
public void apply_nightModeWithScreenOn_appliedImmediatelyBasedOnOrigin(ChangeOrigin origin) {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
@@ -263,7 +263,7 @@
@Test
@TestParameters({"{origin: ORIGIN_APP}", "{origin: ORIGIN_RESTORE_BACKUP}",
- "{origin: ORIGIN_UNKNOWN}"})
+ "{origin: ORIGIN_SYSTEM_OR_SYSTEMUI}", "{origin: ORIGIN_UNKNOWN}"})
public void apply_nightModeWithScreenOn_willBeAppliedLaterBasedOnOrigin(ChangeOrigin origin) {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 56c75b5..e004ca0 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -80,6 +80,9 @@
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
+import static android.service.notification.Condition.SOURCE_CONTEXT;
+import static android.service.notification.Condition.SOURCE_USER_ACTION;
+import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
@@ -224,6 +227,7 @@
import android.provider.MediaStore;
import android.provider.Settings;
import android.service.notification.Adjustment;
+import android.service.notification.Condition;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.DeviceEffectsApplier;
import android.service.notification.INotificationListener;
@@ -342,6 +346,11 @@
private static final String SCHEME_TIMEOUT = "timeout";
private static final String REDACTED_TEXT = "redacted text";
+ private static final AutomaticZenRule SOME_ZEN_RULE =
+ new AutomaticZenRule.Builder("rule", Uri.parse("uri"))
+ .setOwner(new ComponentName("pkg", "cls"))
+ .build();
+
private final int mUid = Binder.getCallingUid();
private final @UserIdInt int mUserId = UserHandle.getUserId(mUid);
@@ -9048,7 +9057,7 @@
zenPolicy, NotificationManager.INTERRUPTION_FILTER_NONE, isEnabled);
try {
- mBinderService.addAutomaticZenRule(rule, mContext.getPackageName());
+ mBinderService.addAutomaticZenRule(rule, mContext.getPackageName(), false);
fail("Zen policy only applies to priority only mode");
} catch (IllegalArgumentException e) {
// yay
@@ -9056,11 +9065,11 @@
rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
- mBinderService.addAutomaticZenRule(rule, mContext.getPackageName());
+ mBinderService.addAutomaticZenRule(rule, mContext.getPackageName(), false);
rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
null, NotificationManager.INTERRUPTION_FILTER_NONE, isEnabled);
- mBinderService.addAutomaticZenRule(rule, mContext.getPackageName());
+ mBinderService.addAutomaticZenRule(rule, mContext.getPackageName(), false);
}
@Test
@@ -9075,7 +9084,7 @@
boolean isEnabled = true;
AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
- mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+ mBinderService.addAutomaticZenRule(rule, "com.android.settings", false);
// verify that zen mode helper gets passed in a package name of "android"
verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
@@ -9097,7 +9106,7 @@
boolean isEnabled = true;
AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
- mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+ mBinderService.addAutomaticZenRule(rule, "com.android.settings", false);
// verify that zen mode helper gets passed in a package name of "android"
verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
@@ -9117,7 +9126,7 @@
boolean isEnabled = true;
AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
- mBinderService.addAutomaticZenRule(rule, "another.package");
+ mBinderService.addAutomaticZenRule(rule, "another.package", false);
// verify that zen mode helper gets passed in the package name from the arg, not the owner
verify(mockZenModeHelper).addAutomaticZenRule(
@@ -9128,10 +9137,8 @@
@Test
@EnableFlags(android.app.Flags.FLAG_MODES_API)
public void testAddAutomaticZenRule_typeManagedCanBeUsedByDeviceOwners() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.setCallerIsNormalPackage();
- mService.setZenHelper(mock(ZenModeHelper.class));
- when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
- .thenReturn(true);
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri"))
.setType(AutomaticZenRule.TYPE_MANAGED)
@@ -9139,8 +9146,9 @@
.build();
when(mDevicePolicyManager.isActiveDeviceOwner(anyInt())).thenReturn(true);
- mBinderService.addAutomaticZenRule(rule, "pkg");
- // No exception!
+ mBinderService.addAutomaticZenRule(rule, "pkg", /* fromUser= */ false);
+
+ verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(rule), anyInt(), any(), anyInt());
}
@Test
@@ -9158,7 +9166,144 @@
when(mDevicePolicyManager.isActiveDeviceOwner(anyInt())).thenReturn(false);
assertThrows(IllegalArgumentException.class,
- () -> mBinderService.addAutomaticZenRule(rule, "pkg"));
+ () -> mBinderService.addAutomaticZenRule(rule, "pkg", /* fromUser= */ false));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void addAutomaticZenRule_fromUser_mappedToOriginUser() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ true);
+
+ verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void addAutomaticZenRule_fromSystemNotUser_mappedToOriginSystem() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ false);
+
+ verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void addAutomaticZenRule_fromApp_mappedToOriginApp() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ false);
+
+ verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void addAutomaticZenRule_fromAppFromUser_blocked() throws Exception {
+ setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ assertThrows(SecurityException.class, () ->
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ true));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void updateAutomaticZenRule_fromUserFromSystem_allowed() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ mBinderService.updateAutomaticZenRule("id", SOME_ZEN_RULE, /* fromUser= */ true);
+
+ verify(zenModeHelper).updateAutomaticZenRule(eq("id"), eq(SOME_ZEN_RULE),
+ eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void updateAutomaticZenRule_fromUserFromApp_blocked() throws Exception {
+ setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ assertThrows(SecurityException.class, () ->
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ true));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void removeAutomaticZenRule_fromUserFromSystem_allowed() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ mBinderService.removeAutomaticZenRule("id", /* fromUser= */ true);
+
+ verify(zenModeHelper).removeAutomaticZenRule(eq("id"),
+ eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void removeAutomaticZenRule_fromUserFromApp_blocked() throws Exception {
+ setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ assertThrows(SecurityException.class, () ->
+ mBinderService.removeAutomaticZenRule("id", /* fromUser= */ true));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void setAutomaticZenRuleState_fromUserMatchesConditionSource_okay() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_CONTEXT);
+ mBinderService.setAutomaticZenRuleState("id", withSourceContext, /* fromUser= */ false);
+ verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
+
+ Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_USER_ACTION);
+ mBinderService.setAutomaticZenRuleState("id", withSourceUser, /* fromUser= */ true);
+ verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceUser),
+ eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void setAutomaticZenRuleState_fromUserDoesNotMatchConditionSource_blocked()
+ throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_CONTEXT);
+ assertThrows(IllegalArgumentException.class,
+ () -> mBinderService.setAutomaticZenRuleState("id", withSourceContext,
+ /* fromUser= */ true));
+
+ Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_USER_ACTION);
+ assertThrows(IllegalArgumentException.class,
+ () -> mBinderService.setAutomaticZenRuleState("id", withSourceUser,
+ /* fromUser= */ false));
+ }
+
+ private ZenModeHelper setUpMockZenTest() {
+ ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
+ mService.setZenHelper(zenModeHelper);
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ return zenModeHelper;
}
@Test
@@ -9184,7 +9329,7 @@
});
mService.getBinderService().setZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS, null,
- "testing!");
+ "testing!", false);
waitForIdle();
InOrder inOrder = inOrder(mContext);
@@ -12157,7 +12302,9 @@
/* isImageBitmap= */ true,
/* isExpired= */ true);
addRecordAndRemoveBitmaps(record);
- assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE)).isFalse();
+ assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE)).isTrue();
+ final Parcelable picture = record.getNotification().extras.getParcelable(EXTRA_PICTURE);
+ assertThat(picture).isNull();
}
@Test
@@ -12191,7 +12338,10 @@
/* isImageBitmap= */ false,
/* isExpired= */ true);
addRecordAndRemoveBitmaps(record);
- assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE_ICON)).isFalse();
+ assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE_ICON)).isTrue();
+ final Parcelable pictureIcon =
+ record.getNotification().extras.getParcelable(EXTRA_PICTURE_ICON);
+ assertThat(pictureIcon).isNull();
}
@Test
@@ -13441,9 +13591,10 @@
.thenReturn(true);
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
- mBinderService.setNotificationPolicy("package", policy);
+ mBinderService.setNotificationPolicy("package", policy, false);
- verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy));
+ verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy),
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP));
}
@Test
@@ -13457,7 +13608,7 @@
mService.isSystemUid = true;
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
- mBinderService.setNotificationPolicy("package", policy);
+ mBinderService.setNotificationPolicy("package", policy, false);
verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
}
@@ -13479,7 +13630,7 @@
.build()));
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
- mBinderService.setNotificationPolicy("package", policy);
+ mBinderService.setNotificationPolicy("package", policy, false);
verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
}
@@ -13495,7 +13646,7 @@
.thenReturn(true);
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
- mBinderService.setNotificationPolicy("package", policy);
+ mBinderService.setNotificationPolicy("package", policy, false);
verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
}
@@ -13525,7 +13676,7 @@
when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
.thenReturn(true);
- mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
+ mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
verify(zenHelper).applyGlobalZenModeAsImplicitZenRule(eq("package"), anyInt(),
eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
@@ -13542,7 +13693,7 @@
.thenReturn(true);
mService.isSystemUid = true;
- mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
+ mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), eq("package"),
@@ -13565,7 +13716,7 @@
.setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH)
.build()));
- mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
+ mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
index 4a1435f..1fcee06 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
@@ -99,7 +99,7 @@
public boolean getFromSystemOrSystemUi(int i) throws IllegalArgumentException {
// While this isn't a logged output value, it's still helpful to check in tests.
checkInRange(i);
- return mChanges.get(i).mFromSystemOrSystemUi;
+ return mChanges.get(i).isFromSystemOrSystemUi();
}
public boolean getIsUserAction(int i) throws IllegalArgumentException {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 1aea56c..44f0894 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -43,6 +43,8 @@
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
+import static android.service.notification.Condition.SOURCE_SCHEDULE;
+import static android.service.notification.Condition.SOURCE_USER_ACTION;
import static android.service.notification.Condition.STATE_FALSE;
import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
@@ -112,6 +114,7 @@
import android.os.Parcel;
import android.os.Process;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.provider.Settings.Global;
@@ -119,12 +122,13 @@
import android.service.notification.DeviceEffectsApplier;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
import android.service.notification.ZenModeConfig.ScheduleInfo;
import android.service.notification.ZenModeConfig.ZenRule;
import android.service.notification.ZenModeDiff;
import android.service.notification.ZenPolicy;
import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
+import android.testing.TestWithLooperRule;
import android.testing.TestableLooper;
import android.util.ArrayMap;
import android.util.Log;
@@ -147,6 +151,8 @@
import com.google.common.collect.ImmutableList;
import com.google.common.truth.Correspondence;
import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import org.junit.Before;
import org.junit.Rule;
@@ -172,7 +178,7 @@
@SmallTest
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
-@RunWith(AndroidTestingRunner.class)
+@RunWith(TestParameterInjector.class)
@TestableLooper.RunWithLooper
public class ZenModeHelperTest extends UiServiceTestCase {
@@ -211,7 +217,11 @@
private static final ZenDeviceEffects NO_EFFECTS = new ZenDeviceEffects.Builder().build();
@Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
+ SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+
+ @Rule(order = Integer.MAX_VALUE) // set the highest order so it's the innermost rule
+ public TestWithLooperRule mLooperRule = new TestWithLooperRule();
ConditionProviders mConditionProviders;
@Mock NotificationManager mNotificationManager;
@@ -2339,15 +2349,38 @@
assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
}
+ private enum ModesApiFlag {
+ ENABLED(true, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_USER),
+ DISABLED(false, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI);
+
+ private final boolean mEnabled;
+ @ConfigChangeOrigin private final int mOriginForUserActionInSystemUi;
+
+ ModesApiFlag(boolean enabled, @ConfigChangeOrigin int originForUserActionInSystemUi) {
+ this.mEnabled = enabled;
+ this.mOriginForUserActionInSystemUi = originForUserActionInSystemUi;
+ }
+
+ void applyFlag(SetFlagsRule setFlagsRule) {
+ if (mEnabled) {
+ setFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+ } else {
+ setFlagsRule.disableFlags(Flags.FLAG_MODES_API);
+ }
+ }
+ }
+
@Test
- public void testZenModeEventLog_setManualZenMode() throws IllegalArgumentException {
+ public void testZenModeEventLog_setManualZenMode(@TestParameter ModesApiFlag modesApiFlag)
+ throws IllegalArgumentException {
+ modesApiFlag.applyFlag(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
// Turn zen mode on (to important_interruptions)
// Need to additionally call the looper in order to finish the post-apply-config process
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
+ modesApiFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID);
// Now turn zen mode off, but via a different package UID -- this should get registered as
// "not an action by the user" because some other app is changing zen mode
@@ -2374,7 +2407,8 @@
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0));
assertEquals(DNDProtoEnums.MANUAL_RULE, mZenModeEventLogger.getChangedRuleType(0));
assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
- assertTrue(mZenModeEventLogger.getFromSystemOrSystemUi(0));
+ assertThat(mZenModeEventLogger.getFromSystemOrSystemUi(0)).isEqualTo(
+ modesApiFlag == ModesApiFlag.DISABLED);
assertTrue(mZenModeEventLogger.getIsUserAction(0));
assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0));
@@ -2399,7 +2433,9 @@
}
@Test
- public void testZenModeEventLog_automaticRules() throws IllegalArgumentException {
+ public void testZenModeEventLog_automaticRules(@TestParameter ModesApiFlag modesApiFlag)
+ throws IllegalArgumentException {
+ modesApiFlag.applyFlag(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -2421,8 +2457,8 @@
// Event 2: "User" turns off the automatic rule (sets it to not enabled)
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(id, zenRule,
+ modesApiFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID);
// Add a new system rule
AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
@@ -2440,8 +2476,8 @@
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Event 4: "User" deletes the rule
- mZenModeHelper.removeAutomaticZenRule(systemId, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
- Process.SYSTEM_UID);
+ mZenModeHelper.removeAutomaticZenRule(systemId, modesApiFlag.mOriginForUserActionInSystemUi,
+ "", Process.SYSTEM_UID);
// In total, this represents 4 events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -2497,20 +2533,109 @@
}
@Test
- public void testZenModeEventLog_policyChanges() throws IllegalArgumentException {
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testZenModeEventLog_automaticRuleActivatedFromAppByAppAndUser()
+ throws IllegalArgumentException {
+ mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
+ setupZenConfig();
+
+ // Ann app adds an automatic zen rule
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ null,
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
+ UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+
+ // Event 1: Mimic the rule coming on manually when the user turns it on in the app
+ // ("Turn on bedtime now" because user goes to bed earlier).
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_USER_ACTION),
+ UPDATE_ORIGIN_USER, CUSTOM_PKG_UID);
+
+ // Event 2: App deactivates the rule automatically (it's 8 AM, bedtime schedule ends)
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
+ UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+ // Event 3: App activates the rule automatically (it's now 11 PM, bedtime schedule starts)
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
+ UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+ // Event 4: User deactivates the rule manually (they get up before 8 AM on the next day)
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_USER_ACTION),
+ UPDATE_ORIGIN_USER, CUSTOM_PKG_UID);
+
+ // In total, this represents 4 events
+ assertEquals(4, mZenModeEventLogger.numLoggedChanges());
+
+ // Automatic rule turning on manually:
+ // - event ID: DND_TURNED_ON
+ // - 1 rule (newly) active
+ // - is a user action
+ // - package UID is the calling package
+ assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(),
+ mZenModeEventLogger.getEventId(0));
+ assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
+ assertTrue(mZenModeEventLogger.getIsUserAction(0));
+ assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
+
+ // Automatic rule turned off automatically by app:
+ // - event ID: DND_TURNED_OFF
+ // - 0 rules active
+ // - is not a user action
+ // - package UID is the calling package
+ assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(),
+ mZenModeEventLogger.getEventId(1));
+ assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
+ assertFalse(mZenModeEventLogger.getIsUserAction(1));
+ assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1));
+
+ // Automatic rule turned on automatically by app:
+ // - event ID: DND_TURNED_ON
+ // - 1 rule (newly) active
+ // - is not a user action
+ // - package UID is the calling package
+ assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(),
+ mZenModeEventLogger.getEventId(2));
+ assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(2));
+ assertEquals(1, mZenModeEventLogger.getNumRulesActive(2));
+ assertFalse(mZenModeEventLogger.getIsUserAction(2));
+ assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(2));
+
+ // Automatic rule turned off automatically by the user:
+ // - event ID: DND_TURNED_ON
+ // - 0 rules active
+ // - is a user action
+ // - package UID is the calling package
+ assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(),
+ mZenModeEventLogger.getEventId(3));
+ assertEquals(0, mZenModeEventLogger.getNumRulesActive(3));
+ assertTrue(mZenModeEventLogger.getIsUserAction(3));
+ assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(3));
+ }
+
+ @Test
+ public void testZenModeEventLog_policyChanges(@TestParameter ModesApiFlag modesApiFlag)
+ throws IllegalArgumentException {
+ modesApiFlag.applyFlag(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
// First just turn zen mode on
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
+ modesApiFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID);
// Now change the policy slightly; want to confirm that this'll be reflected in the logs
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
newConfig.allowAlarms = true;
newConfig.allowRepeatCallers = false;
mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ modesApiFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID);
// Turn zen mode off; we want to make sure policy changes do not get logged when zen mode
// is off.
@@ -2521,7 +2646,7 @@
newConfig.allowMessages = false;
newConfig.allowRepeatCallers = true;
mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ modesApiFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID);
// Total events: we only expect ones for turning on, changing policy, and turning off
assertEquals(3, mZenModeEventLogger.numLoggedChanges());
@@ -2554,7 +2679,9 @@
}
@Test
- public void testZenModeEventLog_ruleCounts() throws IllegalArgumentException {
+ public void testZenModeEventLog_ruleCounts(@TestParameter ModesApiFlag modesApiFlag)
+ throws IllegalArgumentException {
+ modesApiFlag.applyFlag(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -2657,8 +2784,10 @@
}
@Test
- public void testZenModeEventLog_noLogWithNoConfigChange() throws IllegalArgumentException {
+ public void testZenModeEventLog_noLogWithNoConfigChange(
+ @TestParameter ModesApiFlag modesApiFlag) throws IllegalArgumentException {
// If evaluateZenMode is called independently of a config change, don't log.
+ modesApiFlag.applyFlag(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -2675,9 +2804,11 @@
}
@Test
- public void testZenModeEventLog_reassignUid() throws IllegalArgumentException {
+ public void testZenModeEventLog_reassignUid(@TestParameter ModesApiFlag modesApiFlag)
+ throws IllegalArgumentException {
// Test that, only in specific cases, we reassign the calling UID to one associated with
// the automatic rule owner.
+ modesApiFlag.applyFlag(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -2689,7 +2820,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ UPDATE_ORIGIN_APP, "test", Process.SYSTEM_UID);
// Rule 2, same as rule 1 but owned by the system
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -2699,11 +2830,11 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ modesApiFlag.mOriginForUserActionInSystemUi, "test", Process.SYSTEM_UID);
// Turn on rule 1; call looks like it's from the system. Because setting a condition is
// typically an automatic (non-user-initiated) action, expect the calling UID to be
- // re-evaluated to the one associat.d with CUSTOM_PKG_NAME.
+ // re-evaluated to the one associated with CUSTOM_PKG_NAME.
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
@@ -2717,8 +2848,8 @@
// Disable rule 1. Because this looks like a user action, the UID should not be modified
// from the system-provided one.
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(id, zenRule,
+ modesApiFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID);
// Add a manual rule. Any manual rule changes should not get calling uids reassigned.
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
@@ -2775,8 +2906,10 @@
}
@Test
- public void testZenModeEventLog_channelsBypassingChanges() {
+ public void testZenModeEventLog_channelsBypassingChanges(
+ @TestParameter ModesApiFlag modesApiFlag) {
// Verify that the right thing happens when the canBypassDnd value changes.
+ modesApiFlag.applyFlag(mSetFlagsRule);
mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
setupZenConfig();
@@ -2872,14 +3005,11 @@
// Second message where we change the policy:
// - DND_POLICY_CHANGED (indicates only the policy changed and nothing else)
// - rule type: unknown (it's a policy change, not a rule change)
- // - user action (because it comes from a "system" uid)
// - change is in allow channels, and final policy
assertThat(mZenModeEventLogger.getEventId(1))
.isEqualTo(ZenModeEventLogger.ZenStateChangedEvent.DND_POLICY_CHANGED.getId());
assertThat(mZenModeEventLogger.getChangedRuleType(1))
.isEqualTo(DNDProtoEnums.UNKNOWN_RULE);
- assertThat(mZenModeEventLogger.getIsUserAction(1)).isTrue();
- assertThat(mZenModeEventLogger.getPackageUid(1)).isEqualTo(Process.SYSTEM_UID);
DNDPolicyProto dndProto = mZenModeEventLogger.getPolicyProto(1);
assertThat(dndProto.getAllowChannels().getNumber())
.isEqualTo(DNDProtoEnums.CHANNEL_TYPE_NONE);
@@ -3372,6 +3502,29 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void removeAutomaticZenRule_propagatesOriginToEffectsApplier() {
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ reset(mDeviceEffectsApplier);
+
+ String ruleId = addRuleWithEffects(new ZenDeviceEffects.Builder()
+ .setShouldSuppressAmbientDisplay(true)
+ .setShouldDimWallpaper(true)
+ .build());
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+ verify(mDeviceEffectsApplier).apply(any(), eq(UPDATE_ORIGIN_APP));
+
+ // Now delete the (currently active!) rule. For example, assume this is done from settings.
+ mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_USER, "remove",
+ Process.SYSTEM_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_USER));
+ }
+
+ @Test
public void testDeviceEffects_applied() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
@@ -3511,8 +3664,8 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setDeviceEffects(effects)
.build();
- return mZenModeHelper.addAutomaticZenRule("pkg", rule, UPDATE_ORIGIN_APP, "",
- CUSTOM_PKG_UID);
+ return mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+ UPDATE_ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
}
@Test
@@ -3619,7 +3772,8 @@
Policy policy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy,
+ UPDATE_ORIGIN_APP);
ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
.disallowAllSounds()
@@ -3643,13 +3797,14 @@
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- original);
+ original, UPDATE_ORIGIN_APP);
// Change priorityCallSenders: contacts -> starred.
Policy updated = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_SENDERS_STARRED, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated,
+ UPDATE_ORIGIN_APP);
ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
.disallowAllSounds()
@@ -3671,7 +3826,7 @@
withoutWtfCrash(
() -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME,
- CUSTOM_PKG_UID, new Policy(0, 0, 0)));
+ CUSTOM_PKG_UID, new Policy(0, 0, 0), UPDATE_ORIGIN_APP));
assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
}
@@ -3684,7 +3839,7 @@
Policy.getAllSuppressedVisualEffects(), STATE_FALSE,
CONVERSATION_SENDERS_IMPORTANT);
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- writtenPolicy);
+ writtenPolicy, UPDATE_ORIGIN_APP);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
CUSTOM_PKG_NAME);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 526201f..670f9f6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -49,6 +49,7 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -195,7 +196,7 @@
mAInfo.applicationInfo.flags = FLAG_SUSPENDED;
when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID))
- .thenReturn(PLATFORM_PACKAGE_NAME);
+ .thenReturn(UserPackage.of(TEST_USER_ID, PLATFORM_PACKAGE_NAME));
// THEN calling intercept returns true
assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
@@ -227,9 +228,10 @@
.setMessage("Test Message")
.setIcon(0x11110001)
.build();
+ UserPackage suspender = UserPackage.of(TEST_USER_ID, suspendingPackage);
when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID))
- .thenReturn(suspendingPackage);
- when(mPackageManagerInternal.getSuspendedDialogInfo(TEST_PACKAGE_NAME, suspendingPackage,
+ .thenReturn(suspender);
+ when(mPackageManagerInternal.getSuspendedDialogInfo(TEST_PACKAGE_NAME, suspender,
TEST_USER_ID)).thenReturn(dialogInfo);
return dialogInfo;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index d2c731c..ed99108 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -1087,4 +1087,16 @@
assertTrue(homeActivity.getTask().isFocused());
assertFalse(pinnedTask.isFocused());
}
+
+ @Test
+ public void testContinueWindowLayout_notifyClientLifecycleManager() {
+ clearInvocations(mClientLifecycleManager);
+ mAtm.deferWindowLayout();
+
+ verify(mClientLifecycleManager, never()).onLayoutContinued();
+
+ mAtm.continueWindowLayout();
+
+ verify(mClientLifecycleManager).onLayoutContinued();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index 7fdc5fc..c757457 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -52,14 +54,12 @@
* Build/Install/Run:
* atest WmTests:ClientLifecycleManagerTests
*/
-// Suppress GuardedBy warning on unit tests
-@SuppressWarnings("GuardedBy")
@SmallTest
@Presubmit
public class ClientLifecycleManagerTests {
@Rule(order = 0)
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@Rule(order = 1)
public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
@@ -225,4 +225,30 @@
verify(mTransaction).schedule();
verify(mTransaction).recycle();
}
+
+ @Test
+ public void testLayoutDeferred() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+ spyOn(mWms.mWindowPlacerLocked);
+ doReturn(false).when(mWms.mWindowPlacerLocked).isInLayout();
+ doReturn(false).when(mWms.mWindowPlacerLocked).isTraversalScheduled();
+ doReturn(true).when(mWms.mWindowPlacerLocked).isLayoutDeferred();
+
+ // Queue transactions during layout deferred.
+ mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mLifecycleItem);
+
+ verify(mLifecycleManager, never()).scheduleTransaction(any());
+
+ // Continue queueing when there are multi-level defer.
+ mLifecycleManager.onLayoutContinued();
+
+ verify(mLifecycleManager, never()).scheduleTransaction(any());
+
+ // Immediately dispatch when layout continue without ongoing/scheduled layout.
+ doReturn(false).when(mWms.mWindowPlacerLocked).isLayoutDeferred();
+
+ mLifecycleManager.onLayoutContinued();
+
+ verify(mLifecycleManager).scheduleTransaction(any());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java b/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java
index 1f7b65e..c187263 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CompatModePackagesTests.java
@@ -20,10 +20,13 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import android.app.GameManagerInternal;
+import android.content.pm.ApplicationInfo;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -90,6 +93,14 @@
public void testGetCompatScale_noGameManager() {
assertEquals(mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID), 1f,
0.01f);
- }
+ final ApplicationInfo info = new ApplicationInfo();
+ // Any non-zero value without FLAG_SUPPORTS_*_SCREENS.
+ info.flags = ApplicationInfo.FLAG_HAS_CODE;
+ info.packageName = info.sourceDir = "legacy.app";
+ mAtm.mCompatModePackages.compatibilityInfoForPackageLocked(info);
+ assertTrue(mAtm.mCompatModePackages.useLegacyScreenCompatMode(info.packageName));
+ mAtm.mCompatModePackages.handlePackageUninstalledLocked(info.packageName);
+ assertFalse(mAtm.mCompatModePackages.useLegacyScreenCompatMode(info.packageName));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index c6796dc..985be42 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -19,6 +19,7 @@
import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
@@ -673,6 +674,47 @@
}
@Test
+ @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+ public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_returnsUser()
+ throws Exception {
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+ public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_optOut_returnsUnchanged()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+ mDisplayContent.setIgnoreOrientationRequest(true);
+
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+ public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_returnsUnchanged()
+ throws Exception {
+ mDisplayContent.setIgnoreOrientationRequest(false);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+ public void testOverrideOrientationIfNeeded_fullscreenAndUserOverrideEnabled_returnsUnchanged()
+ throws Exception {
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ }
+
+ @Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait()
throws Exception {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
index ef427bb..a8b2178 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.server.wm.CtsWindowInfoUtils.dumpWindowsOnScreen;
+import static android.server.wm.CtsWindowInfoUtils.assertAndDumpWindowState;
import static android.server.wm.CtsWindowInfoUtils.waitForWindowFocus;
import static android.server.wm.CtsWindowInfoUtils.waitForWindowVisible;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -129,37 +129,22 @@
mScvh2.setView(mView2, lp2);
});
- boolean wasVisible = waitForWindowVisible(mView1);
- if (!wasVisible) {
- dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows");
- }
- assertTrue("Failed to wait for view1", wasVisible);
-
- wasVisible = waitForWindowVisible(mView2);
- if (!wasVisible) {
- dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-not visible");
- }
- assertTrue("Failed to wait for view2", wasVisible);
+ assertAndDumpWindowState(TAG, "Failed to wait for view1", waitForWindowVisible(mView1));
+ assertAndDumpWindowState(TAG, "Failed to wait for view2", waitForWindowVisible(mView2));
IWindow window = IWindow.Stub.asInterface(mSurfaceView.getWindowToken());
WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(window,
mScvh1.getInputTransferToken(), true);
- boolean gainedFocus = waitForWindowFocus(mView1, true);
- if (!gainedFocus) {
- dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-view1 not focus");
- }
- assertTrue("Failed to gain focus for view1", gainedFocus);
+ assertAndDumpWindowState(TAG, "Failed to wait for view1 focus",
+ waitForWindowFocus(mView1, true));
WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(window,
mScvh2.getInputTransferToken(), true);
- gainedFocus = waitForWindowFocus(mView2, true);
- if (!gainedFocus) {
- dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-view2 not focus");
- }
- assertTrue("Failed to gain focus for view2", gainedFocus);
+ assertAndDumpWindowState(TAG, "Failed to wait for view2 focus",
+ waitForWindowFocus(mView2, true));
}
private static class TestWindowlessWindowManager extends WindowlessWindowManager {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
index ac49839..6a15b05 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.server.wm.CtsWindowInfoUtils.assertAndDumpWindowState;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
@@ -138,11 +139,8 @@
return false;
}, TIMEOUT_S, TimeUnit.SECONDS);
- if (!foundTrusted[0]) {
- CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, mName.getMethodName());
- }
-
- assertTrue("Failed to find window or was not marked trusted", foundTrusted[0]);
+ assertAndDumpWindowState(TAG, "Failed to find window or was not marked trusted",
+ foundTrusted[0]);
}
private void testTrustedOverlayChildHelper(boolean expectedTrustedChild)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index f99b489..8bf4833 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -499,11 +499,16 @@
public void testAddWindowWithSubWindowTypeByWindowContext() {
spyOn(mWm.mWindowContextListenerController);
- final WindowToken windowToken = createTestWindowToken(TYPE_INPUT_METHOD, mDefaultDisplay);
- final Session session = getTestSession();
+ final WindowState parentWin = createWindow(null, TYPE_INPUT_METHOD, "ime");
+ final IBinder parentToken = parentWin.mToken.token;
+ parentWin.mAttrs.token = parentToken;
+ mWm.mWindowMap.put(parentToken, parentWin);
+ final Session session = parentWin.mSession;
+ session.onWindowAdded(parentWin);
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
TYPE_APPLICATION_ATTACHED_DIALOG);
- params.token = windowToken.token;
+ params.token = parentToken;
+ params.setTitle("attached-dialog");
final IBinder windowContextToken = new Binder();
params.setWindowContextToken(windowContextToken);
doReturn(true).when(mWm.mWindowContextListenerController)
@@ -517,6 +522,12 @@
verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
any(), any(), anyInt(), any(), anyBoolean());
+
+ assertTrue(parentWin.hasChild());
+ assertTrue(parentWin.isAttached());
+ session.binderDied();
+ assertFalse(parentWin.hasChild());
+ assertFalse(parentWin.isAttached());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index df4af11..616a23e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -294,7 +294,7 @@
*/
static void suppressInsetsAnimation(InsetsControlTarget target) {
spyOn(target);
- Mockito.doNothing().when(target).notifyInsetsControlChanged();
+ Mockito.doNothing().when(target).notifyInsetsControlChanged(anyInt());
}
@After
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 72db7fe..ccd4ce0 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2395,7 +2395,19 @@
return null;
}
- return queryEventsHelper(UserHandle.getCallingUserId(), query.getBeginTimeMillis(),
+ final int callingUserId = UserHandle.getCallingUserId();
+ int userId = query.getUserId();
+ if (userId == UserHandle.USER_NULL) {
+ // Convert userId to actual user Id if not specified in the query object.
+ userId = callingUserId;
+ }
+ if (userId != callingUserId) {
+ getContext().enforceCallingPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "No permission to query usage stats for user " + userId);
+ }
+
+ return queryEventsHelper(userId, query.getBeginTimeMillis(),
query.getEndTimeMillis(), callingPackage, query.getEventTypeFilter());
}
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index de8d144..9e425b0 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -65,12 +65,14 @@
java_library_static {
name: "services.voiceinteraction",
- defaults: ["platform_service_defaults"],
+ defaults: [
+ "platform_service_defaults",
+ "android.hardware.power-java_shared",
+ ],
srcs: [":services.voiceinteraction-sources"],
libs: [
"services.core",
"app-compat-annotations",
- "android.hardware.power-V1-java",
"android.hardware.power-V1.0-java",
],
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
index 4720d27..f9fa9b7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -106,96 +106,104 @@
@Override
public void onAttentionGained() {
Slog.v(TAG, "BinderCallback#onAttentionGained");
- mEgressingData = true;
- if (mAttentionListener == null) {
- return;
- }
- try {
- mAttentionListener.onAttentionGained();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error delivering attention gained event.", e);
- try {
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_ATTENTION_STATE,
- "Attention listener failed to switch to GAINED state."));
- } catch (RemoteException ex) {
- Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+ synchronized (mLock) {
+ mEgressingData = true;
+ if (mAttentionListener == null) {
+ return;
}
- return;
+ try {
+ mAttentionListener.onAttentionGained();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering attention gained event.", e);
+ try {
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_ATTENTION_STATE,
+ "Attention listener fails to switch to GAINED state."));
+ } catch (RemoteException ex) {
+ Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+ }
+ }
}
}
@Override
public void onAttentionLost() {
Slog.v(TAG, "BinderCallback#onAttentionLost");
- mEgressingData = false;
- if (mAttentionListener == null) {
- return;
- }
- try {
- mAttentionListener.onAttentionLost();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error delivering attention lost event.", e);
- try {
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_ATTENTION_STATE,
- "Attention listener failed to switch to LOST state."));
- } catch (RemoteException ex) {
- Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+ synchronized (mLock) {
+ mEgressingData = false;
+ if (mAttentionListener == null) {
+ return;
}
- return;
+ try {
+ mAttentionListener.onAttentionLost();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering attention lost event.", e);
+ try {
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_ATTENTION_STATE,
+ "Attention listener fails to switch to LOST state."));
+ } catch (RemoteException ex) {
+ Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+ }
+ }
}
}
@Override
public void onQueryDetected(@NonNull String partialQuery) throws RemoteException {
- Objects.requireNonNull(partialQuery);
Slog.v(TAG, "BinderCallback#onQueryDetected");
- if (!mEgressingData) {
- Slog.v(TAG, "Query should not be egressed within the unattention state.");
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_STREAMING_STATE,
- "Cannot stream queries without attention signals."));
- return;
+ synchronized (mLock) {
+ Objects.requireNonNull(partialQuery);
+ if (!mEgressingData) {
+ Slog.v(TAG, "Query should not be egressed within the unattention state.");
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_STREAMING_STATE,
+ "Cannot stream queries without attention signals."));
+ return;
+ }
+ mQueryStreaming = true;
+ callback.onQueryDetected(partialQuery);
+ Slog.i(TAG, "Egressed from visual query detection process.");
}
- mQueryStreaming = true;
- callback.onQueryDetected(partialQuery);
- Slog.i(TAG, "Egressed from visual query detection process.");
}
@Override
public void onQueryFinished() throws RemoteException {
Slog.v(TAG, "BinderCallback#onQueryFinished");
- if (!mQueryStreaming) {
- Slog.v(TAG, "Query streaming state signal FINISHED is block since there is"
- + " no active query being streamed.");
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_STREAMING_STATE,
- "Cannot send FINISHED signal with no query streamed."));
- return;
+ synchronized (mLock) {
+ if (!mQueryStreaming) {
+ Slog.v(TAG, "Query streaming state signal FINISHED is block since there is"
+ + " no active query being streamed.");
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_STREAMING_STATE,
+ "Cannot send FINISHED signal with no query streamed."));
+ return;
+ }
+ callback.onQueryFinished();
+ mQueryStreaming = false;
}
- callback.onQueryFinished();
- mQueryStreaming = false;
}
@Override
public void onQueryRejected() throws RemoteException {
Slog.v(TAG, "BinderCallback#onQueryRejected");
- if (!mQueryStreaming) {
- Slog.v(TAG, "Query streaming state signal REJECTED is block since there is"
- + " no active query being streamed.");
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_STREAMING_STATE,
- "Cannot send REJECTED signal with no query streamed."));
- return;
+ synchronized (mLock) {
+ if (!mQueryStreaming) {
+ Slog.v(TAG, "Query streaming state signal REJECTED is block since there is"
+ + " no active query being streamed.");
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_STREAMING_STATE,
+ "Cannot send REJECTED signal with no query streamed."));
+ return;
+ }
+ callback.onQueryRejected();
+ mQueryStreaming = false;
}
- callback.onQueryRejected();
- mQueryStreaming = false;
}
};
return mRemoteDetectionService.run(
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index def52a5..874c10c 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -210,100 +210,6 @@
"android.telecom.extra.SILENT_RINGING_REQUESTED";
/**
- * Call event sent from a {@link Call} via {@link #sendCallEvent(String, Bundle)} to inform
- * Telecom that the user has requested that the current {@link Call} should be handed over
- * to another {@link ConnectionService}.
- * <p>
- * The caller must specify the {@link #EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE} to indicate to
- * Telecom which {@link PhoneAccountHandle} the {@link Call} should be handed over to.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_REQUEST_HANDOVER =
- "android.telecom.event.REQUEST_HANDOVER";
-
- /**
- * Extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Specifies the
- * {@link PhoneAccountHandle} to which a call should be handed over to.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE =
- "android.telecom.extra.HANDOVER_PHONE_ACCOUNT_HANDLE";
-
- /**
- * Integer extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Specifies the
- * video state of the call when it is handed over to the new {@link PhoneAccount}.
- * <p>
- * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY},
- * {@link VideoProfile#STATE_BIDIRECTIONAL}, {@link VideoProfile#STATE_RX_ENABLED}, and
- * {@link VideoProfile#STATE_TX_ENABLED}.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EXTRA_HANDOVER_VIDEO_STATE =
- "android.telecom.extra.HANDOVER_VIDEO_STATE";
-
- /**
- * Extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Used by the
- * {@link InCallService} initiating a handover to provide a {@link Bundle} with extra
- * information to the handover {@link ConnectionService} specified by
- * {@link #EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE}.
- * <p>
- * This {@link Bundle} is not interpreted by Telecom, but passed as-is to the
- * {@link ConnectionService} via the request extras when
- * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}
- * is called to initate the handover.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EXTRA_HANDOVER_EXTRAS = "android.telecom.extra.HANDOVER_EXTRAS";
-
- /**
- * Call event sent from Telecom to the handover {@link ConnectionService} via
- * {@link Connection#onCallEvent(String, Bundle)} to inform a {@link Connection} that a handover
- * to the {@link ConnectionService} has completed successfully.
- * <p>
- * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_COMPLETE =
- "android.telecom.event.HANDOVER_COMPLETE";
-
- /**
- * Call event sent from Telecom to the handover destination {@link ConnectionService} via
- * {@link Connection#onCallEvent(String, Bundle)} to inform the handover destination that the
- * source connection has disconnected. The {@link Bundle} parameter for the call event will be
- * {@code null}.
- * <p>
- * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_SOURCE_DISCONNECTED =
- "android.telecom.event.HANDOVER_SOURCE_DISCONNECTED";
-
- /**
- * Call event sent from Telecom to the handover {@link ConnectionService} via
- * {@link Connection#onCallEvent(String, Bundle)} to inform a {@link Connection} that a handover
- * to the {@link ConnectionService} has failed.
- * <p>
- * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_FAILED =
- "android.telecom.event.HANDOVER_FAILED";
-
- /**
* Event reported from the Telecom stack to report an in-call diagnostic message which the
* dialer app may opt to display to the user. A diagnostic message is used to communicate
* scenarios the device has detected which may impact the quality of the ongoing call.
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 4a541da..ee9bf898 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -961,28 +961,6 @@
"android.telecom.event.CALL_REMOTELY_UNHELD";
/**
- * Connection event used to inform an {@link InCallService} which initiated a call handover via
- * {@link Call#EVENT_REQUEST_HANDOVER} that the handover from this {@link Connection} has
- * successfully completed.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_COMPLETE =
- "android.telecom.event.HANDOVER_COMPLETE";
-
- /**
- * Connection event used to inform an {@link InCallService} which initiated a call handover via
- * {@link Call#EVENT_REQUEST_HANDOVER} that the handover from this {@link Connection} has failed
- * to complete.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_FAILED =
- "android.telecom.event.HANDOVER_FAILED";
-
- /**
* String Connection extra key used to store SIP invite fields for an incoming call for IMS call
*/
public static final String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE";
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index bcd9929..101d285 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -525,6 +525,12 @@
public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
/**
+ * Used in the Preferred Network Types menu to determine if the 3G option is displayed.
+ */
+ @FlaggedApi(Flags.FLAG_HIDE_PREFER_3G_ITEM)
+ public static final String KEY_PREFER_3G_VISIBILITY_BOOL = "prefer_3g_visibility_bool";
+
+ /**
* Used in Cellular Network Settings for preferred network type to show 4G only mode.
* @hide
*/
@@ -10144,6 +10150,7 @@
sDefaults.putBoolean(KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL, false);
sDefaults.putBoolean(KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
sDefaults.putBoolean(KEY_PREFER_2G_BOOL, false);
+ sDefaults.putBoolean(KEY_PREFER_3G_VISIBILITY_BOOL, true);
sDefaults.putBoolean(KEY_4G_ONLY_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_APN_SETTING_CDMA_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_CDMA_CHOICES_BOOL, false);
diff --git a/tests/ActivityManagerPerfTests/tests/Android.bp b/tests/ActivityManagerPerfTests/tests/Android.bp
index e5813ae..cce40f3a 100644
--- a/tests/ActivityManagerPerfTests/tests/Android.bp
+++ b/tests/ActivityManagerPerfTests/tests/Android.bp
@@ -30,6 +30,12 @@
"ActivityManagerPerfTestsUtils",
"collector-device-lib-platform",
],
+ data: [
+ ":ActivityManagerPerfTestsTestApp",
+ ":ActivityManagerPerfTestsStubApp1",
+ ":ActivityManagerPerfTestsStubApp2",
+ ":ActivityManagerPerfTestsStubApp3",
+ ],
platform_apis: true,
min_sdk_version: "25",
// For android.permission.FORCE_STOP_PACKAGES permission
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index c49f8fe..be47ec7 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -19,12 +19,12 @@
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
+import android.tools.common.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.traces.parsers.toFlickerComponent
-import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -58,9 +58,10 @@
}
transitions {
broadcastActionTrigger.doAction(ACTION_FINISH_ACTIVITY)
- wmHelper.StateSyncBuilder()
- .withActivityRemoved(ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent())
- .waitForAndVerify()
+ wmHelper
+ .StateSyncBuilder()
+ .withActivityRemoved(ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent())
+ .waitForAndVerify()
}
teardown { simpleApp.exit(wmHelper) }
}
@@ -69,10 +70,16 @@
@Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
- @FlakyTest(bugId = 246284124)
+ @Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ flicker.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS.toMutableList().also {
+ it.add(simpleApp.componentMatcher)
+ }
+ )
+ }
}
@Presubmit
diff --git a/tests/testables/src/android/testing/TestWithLooperRule.java b/tests/testables/src/android/testing/TestWithLooperRule.java
index 99b303e..37b39c3 100644
--- a/tests/testables/src/android/testing/TestWithLooperRule.java
+++ b/tests/testables/src/android/testing/TestWithLooperRule.java
@@ -19,7 +19,6 @@
import android.testing.TestableLooper.LooperFrameworkMethod;
import android.testing.TestableLooper.RunWithLooper;
-import org.junit.internal.runners.statements.InvokeMethod;
import org.junit.rules.MethodRule;
import org.junit.runner.RunWith;
import org.junit.runners.model.FrameworkMethod;
@@ -79,13 +78,11 @@
while (next != null) {
switch (next.getClass().getSimpleName()) {
case "RunAfters":
- this.<List<FrameworkMethod>>wrapFieldMethodFor(next,
- next.getClass(), "afters", method, target);
+ this.wrapFieldMethodFor(next, "afters", method, target);
next = getNextStatement(next, "next");
break;
case "RunBefores":
- this.<List<FrameworkMethod>>wrapFieldMethodFor(next,
- next.getClass(), "befores", method, target);
+ this.wrapFieldMethodFor(next, "befores", method, target);
next = getNextStatement(next, "next");
break;
case "FailOnTimeout":
@@ -95,8 +92,10 @@
next = getNextStatement(next, "originalStatement");
break;
case "InvokeMethod":
- this.<FrameworkMethod>wrapFieldMethodFor(next,
- InvokeMethod.class, "testMethod", method, target);
+ this.wrapFieldMethodFor(next, "testMethod", method, target);
+ return;
+ case "InvokeParameterizedMethod":
+ this.wrapFieldMethodFor(next, "frameworkMethod", method, target);
return;
default:
throw new Exception(
@@ -112,12 +111,11 @@
// Wrapping the befores, afters, and InvokeMethods with LooperFrameworkMethod
// within the statement.
- private <T> void wrapFieldMethodFor(Statement base, Class<?> targetClass, String fieldStr,
- FrameworkMethod method, Object target)
- throws NoSuchFieldException, IllegalAccessException {
- Field field = targetClass.getDeclaredField(fieldStr);
+ private void wrapFieldMethodFor(Statement base, String fieldStr, FrameworkMethod method,
+ Object target) throws NoSuchFieldException, IllegalAccessException {
+ Field field = base.getClass().getDeclaredField(fieldStr);
field.setAccessible(true);
- T fieldInstance = (T) field.get(base);
+ Object fieldInstance = field.get(base);
if (fieldInstance instanceof FrameworkMethod) {
field.set(base, looperWrap(method, target, (FrameworkMethod) fieldInstance));
} else {
diff --git a/tools/hoststubgen/README.md b/tools/hoststubgen/README.md
index 3455b0a..1a895dc 100644
--- a/tools/hoststubgen/README.md
+++ b/tools/hoststubgen/README.md
@@ -34,11 +34,6 @@
- `test-tiny-framework/` See `README.md` in it.
- - `test-framework`
- This directory was used during the prototype phase, but now that we have real ravenwood tests,
- this directory is obsolete and should be deleted.
-
-
- `scripts`
- `dump-jar.sh`
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 0e2e158..57bcc04 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -9,7 +9,7 @@
// Visibility only for ravenwood prototype uses.
genrule_defaults {
- name: "hoststubgen-for-prototype-only-genrule",
+ name: "ravenwood-internal-only-visibility-genrule",
visibility: [
":__subpackages__",
"//frameworks/base",
@@ -19,7 +19,7 @@
// Visibility only for ravenwood prototype uses.
java_defaults {
- name: "hoststubgen-for-prototype-only-java",
+ name: "ravenwood-internal-only-visibility-java",
visibility: [
":__subpackages__",
"//frameworks/base",
@@ -29,7 +29,7 @@
// Visibility only for ravenwood prototype uses.
filegroup_defaults {
- name: "hoststubgen-for-prototype-only-filegroup",
+ name: "ravenwood-internal-only-visibility-filegroup",
visibility: [
":__subpackages__",
"//frameworks/base",
@@ -41,7 +41,7 @@
// This is only for the prototype. The productionized version is "ravenwood-annotations".
java_library {
name: "hoststubgen-annotations",
- defaults: ["hoststubgen-for-prototype-only-java"],
+ defaults: ["ravenwood-internal-only-visibility-java"],
srcs: [
"annotations-src/**/*.java",
],
@@ -115,7 +115,7 @@
// This is only for the prototype. The productionized version is "ravenwood-standard-options".
filegroup {
name: "hoststubgen-standard-options",
- defaults: ["hoststubgen-for-prototype-only-filegroup"],
+ defaults: ["ravenwood-internal-only-visibility-filegroup"],
srcs: [
"hoststubgen-standard-options.txt",
],
@@ -153,146 +153,25 @@
],
}
-// Generate the stub/impl from framework-all, with hidden APIs.
-java_genrule_host {
- name: "framework-all-hidden-api-host",
- defaults: ["hoststubgen-command-defaults"],
- cmd: hoststubgen_common_options +
- "--in-jar $(location :framework-all) " +
- "--policy-override-file $(location framework-policy-override.txt) ",
- srcs: [
- ":framework-all",
- "framework-policy-override.txt",
- ],
- visibility: ["//visibility:private"],
-}
-
-// Extract the stub jar from "framework-all-host" for subsequent build rules.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-hidden-api-host-stub",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-all-hidden-api-host{host_stub.jar}",
- ],
- out: [
- "host_stub.jar",
- ],
-}
-
-// Extract the impl jar from "framework-all-host" for subsequent build rules.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-hidden-api-host-impl",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-all-hidden-api-host{host_impl.jar}",
- ],
- out: [
- "host_impl.jar",
- ],
-}
-
-// Generate the stub/impl from framework-all, with only public/system/test APIs, without
-// hidden APIs.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-test-api-host",
- defaults: ["hoststubgen-command-defaults"],
- cmd: hoststubgen_common_options +
- "--intersect-stub-jar $(location :android_test_stubs_current{.jar}) " +
- "--in-jar $(location :framework-all) " +
- "--policy-override-file $(location framework-policy-override.txt) ",
- srcs: [
- ":framework-all",
- ":android_test_stubs_current{.jar}",
- "framework-policy-override.txt",
- ],
- visibility: ["//visibility:private"],
-}
-
-// Extract the stub jar from "framework-all-test-api-host" for subsequent build rules.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-test-api-host-stub",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-all-test-api-host{host_stub.jar}",
- ],
- out: [
- "host_stub.jar",
- ],
-}
-
-// Extract the impl jar from "framework-all-test-api-host" for subsequent build rules.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-test-api-host-impl",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-all-test-api-host{host_impl.jar}",
- ],
- out: [
- "host_impl.jar",
- ],
-}
-
-// This library contains helper classes to build hostside tests/targets.
-// This essentially contains dependencies from tests that we can't actually use the real ones.
-// For example, the actual AndroidTestCase and AndroidJUnit4 don't run on the host side (yet),
-// so we pup "fake" implementations here.
-// Ideally this library should be empty.
-java_library_host {
- name: "hoststubgen-helper-framework-buildtime",
- defaults: ["hoststubgen-for-prototype-only-java"],
- srcs: [
- "helper-framework-buildtime-src/**/*.java",
- ],
- libs: [
- // We need it to pull in some of the framework classes used in this library,
- // such as Context.java.
- "framework-all-hidden-api-host-impl",
- "junit",
- ],
-}
-
-// This module contains "fake" libcore/dalvik classes, framework native substitution, etc,
-// that are needed at runtime.
-java_library_host {
- name: "hoststubgen-helper-framework-runtime",
- defaults: ["hoststubgen-for-prototype-only-java"],
- srcs: [
- "helper-framework-runtime-src/**/*.java",
- ],
- libs: [
- "hoststubgen-helper-runtime",
- "framework-all-hidden-api-host-impl",
- ],
-}
-
java_library_host {
name: "hoststubgen-helper-libcore-runtime",
- defaults: ["hoststubgen-for-prototype-only-java"],
srcs: [
"helper-framework-runtime-src/libcore-fake/**/*.java",
],
+ visibility: ["//visibility:private"],
}
java_host_for_device {
name: "hoststubgen-helper-libcore-runtime.ravenwood",
- defaults: ["hoststubgen-for-prototype-only-java"],
libs: [
"hoststubgen-helper-libcore-runtime",
],
+ visibility: ["//visibility:private"],
}
java_library {
name: "hoststubgen-helper-framework-runtime.ravenwood",
- defaults: ["hoststubgen-for-prototype-only-java"],
+ defaults: ["ravenwood-internal-only-visibility-java"],
srcs: [
"helper-framework-runtime-src/framework/**/*.java",
],
@@ -305,88 +184,3 @@
"hoststubgen-helper-libcore-runtime.ravenwood",
],
}
-
-// Defaults for host side test modules.
-// We need two rules for each test.
-// 1. A "-test-lib" jar, which compiles the test against the stub jar.
-// This one is only used by the second rule, so it should be "private.
-// 2. A "-test" jar, which includes 1 + the runtime (impl) jars.
-
-// This and next ones are for tests using framework-app, with hidden APIs.
-java_defaults {
- name: "hosttest-with-framework-all-hidden-api-test-lib-defaults",
- installable: false,
- libs: [
- "framework-all-hidden-api-host-stub",
- ],
- static_libs: [
- "hoststubgen-helper-framework-buildtime",
- "framework-annotations-lib",
- ],
- visibility: ["//visibility:private"],
-}
-
-// Default rules to include `libandroid_runtime`. For now, it's empty, but we'll use it
-// once we start using JNI.
-java_defaults {
- name: "hosttest-with-libandroid_runtime",
- jni_libs: [
- // "libandroid_runtime",
-
- // TODO: Figure out how to build them automatically.
- // Following ones are depended by libandroid_runtime.
- // Without listing them here, not only we won't get them under
- // $ANDROID_HOST_OUT/testcases/*/lib64, but also not under
- // $ANDROID_HOST_OUT/lib64, so we'd fail to load them at runtime.
- // ($ANDROID_HOST_OUT/lib/ gets all of them though.)
- // "libcutils",
- // "libharfbuzz_ng",
- // "libminikin",
- // "libz",
- // "libbinder",
- // "libhidlbase",
- // "libvintf",
- // "libicu",
- // "libutils",
- // "libtinyxml2",
- ],
-}
-
-java_defaults {
- name: "hosttest-with-framework-all-hidden-api-test-defaults",
- defaults: ["hosttest-with-libandroid_runtime"],
- installable: false,
- test_config: "AndroidTest-host.xml",
- static_libs: [
- "hoststubgen-helper-runtime",
- "hoststubgen-helper-framework-runtime",
- "framework-all-hidden-api-host-impl",
- ],
-}
-
-// This and next ones are for tests using framework-app, with public/system/test APIs,
-// without hidden APIs.
-java_defaults {
- name: "hosttest-with-framework-all-test-api-test-lib-defaults",
- installable: false,
- libs: [
- "framework-all-test-api-host-stub",
- ],
- static_libs: [
- "hoststubgen-helper-framework-buildtime",
- "framework-annotations-lib",
- ],
- visibility: ["//visibility:private"],
-}
-
-java_defaults {
- name: "hosttest-with-framework-all-test-api-test-defaults",
- defaults: ["hosttest-with-libandroid_runtime"],
- installable: false,
- test_config: "AndroidTest-host.xml",
- static_libs: [
- "hoststubgen-helper-runtime",
- "hoststubgen-helper-framework-runtime",
- "framework-all-test-api-host-impl",
- ],
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/android/test/AndroidTestCase.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/android/test/AndroidTestCase.java
deleted file mode 100644
index e6d3866..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/android/test/AndroidTestCase.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 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.test;
-
-import android.content.Context;
-
-import junit.framework.TestCase;
-
-public class AndroidTestCase extends TestCase {
- protected Context mContext;
- public Context getContext() {
- throw new RuntimeException("[ravenwood] Class Context is not supported yet.");
- }
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java
deleted file mode 100644
index 51c5d9a..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2013 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 androidx.annotation;
-
-// [ravenwood] TODO: Find the actual androidx jar containing it.s
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * Denotes that a parameter, field or method return value can never be null.
- * <p>
- * This is a marker annotation and it has no specific attributes.
- *
- * @paramDoc This value cannot be {@code null}.
- * @returnDoc This value cannot be {@code null}.
- * @hide
- */
-@Retention(SOURCE)
-@Target({METHOD, PARAMETER, FIELD})
-public @interface NonNull {
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java
deleted file mode 100644
index f1f0e8b..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2013 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 androidx.annotation;
-
-// [ravenwood] TODO: Find the actual androidx jar containing it.s
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * Denotes that a parameter, field or method return value can be null.
- * <p>
- * When decorating a method call parameter, this denotes that the parameter can
- * legitimately be null and the method will gracefully deal with it. Typically
- * used on optional parameters.
- * <p>
- * When decorating a method, this denotes the method might legitimately return
- * null.
- * <p>
- * This is a marker annotation and it has no specific attributes.
- *
- * @paramDoc This value may be {@code null}.
- * @returnDoc This value may be {@code null}.
- * @hide
- */
-@Retention(SOURCE)
-@Target({METHOD, PARAMETER, FIELD})
-public @interface Nullable {
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java
deleted file mode 100644
index 0c82e4e..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2023 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 androidx.test.ext.junit.runners;
-
-import org.junit.runners.BlockJUnit4ClassRunner;
-import org.junit.runners.model.InitializationError;
-
-// TODO: We need to simulate the androidx test runner.
-// https://source.corp.google.com/piper///depot/google3/third_party/android/androidx_test/ext/junit/java/androidx/test/ext/junit/runners/AndroidJUnit4.java
-// https://source.corp.google.com/piper///depot/google3/third_party/android/androidx_test/runner/android_junit_runner/java/androidx/test/internal/runner/junit4/AndroidJUnit4ClassRunner.java
-
-public class AndroidJUnit4 extends BlockJUnit4ClassRunner {
- public AndroidJUnit4(Class<?> testClass) throws InitializationError {
- super(testClass);
- }
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java
deleted file mode 100644
index 2470d839..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Designates a test as being flaky (non-deterministic).
- *
- * <p>Can then be used to filter tests on execution using -e annotation or -e notAnnotation as
- * desired.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface FlakyTest {
- /**
- * An optional bug number associated with the test. -1 Means that no bug number is associated with
- * the flaky annotation.
- *
- * @return int
- */
- int bugId() default -1;
-
- /**
- * Details, such as the reason of why the test is flaky.
- *
- * @return String
- */
- String detail() default "";
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java
deleted file mode 100644
index 578d7dc..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation to assign a large test size qualifier to a test. This annotation can be used at a
- * method or class level.
- *
- * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a
- * test suite of similar run time.
- *
- * <p>Execution time: >1000ms
- *
- * <p>Large tests should be focused on testing integration of all application components. These
- * tests fully participate in the system and may make use of all resources such as databases, file
- * systems and network. As a rule of thumb most functional UI tests are large tests.
- *
- * <p>Note: This class replaces the deprecated Android platform size qualifier <a
- * href="{@docRoot}reference/android/test/suitebuilder/annotation/LargeTest.html"><code>
- * android.test.suitebuilder.annotation.LargeTest</code></a> and is the recommended way to annotate
- * tests written with the AndroidX Test Library.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface LargeTest {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java
deleted file mode 100644
index dfdaa53..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation to assign a medium test size qualifier to a test. This annotation can be used at a
- * method or class level.
- *
- * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a
- * test suite of similar run time.
- *
- * <p>Execution time: <1000ms
- *
- * <p>Medium tests should be focused on a very limited subset of components or a single component.
- * Resource access to the file system through well defined interfaces like databases,
- * ContentProviders, or Context is permitted. Network access should be restricted, (long-running)
- * blocking operations should be avoided and use mock objects instead.
- *
- * <p>Note: This class replaces the deprecated Android platform size qualifier <a
- * href="{@docRoot}reference/android/test/suitebuilder/annotation/MediumTest.html"><code>
- * android.test.suitebuilder.annotation.MediumTest</code></a> and is the recommended way to annotate
- * tests written with the AndroidX Test Library.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface MediumTest {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java
deleted file mode 100644
index 3d3ee33..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Indicates that a specific test should not be run on emulator.
- *
- * <p>It will be executed only if the test is running on the physical android device.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-public @interface RequiresDevice {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java
deleted file mode 100644
index dd65ddb..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Indicates that a specific test or class requires a minimum or maximum API Level to execute.
- *
- * <p>Test(s) will be skipped when executed on android platforms less/more than specified level
- * (inclusive).
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-public @interface SdkSuppress {
- /** The minimum API level to execute (inclusive) */
- int minSdkVersion() default 1;
- /** The maximum API level to execute (inclusive) */
- int maxSdkVersion() default Integer.MAX_VALUE;
- /**
- * The {@link android.os.Build.VERSION.CODENAME} to execute on. This is intended to be used to run
- * on a pre-release SDK, where the {@link android.os.Build.VERSION.SDK_INT} has not yet been
- * finalized. This is treated as an OR operation with respect to the minSdkVersion and
- * maxSdkVersion attributes.
- *
- * <p>For example, to filter a test so it runs on only the prerelease R SDK: <code>
- * {@literal @}SdkSuppress(minSdkVersion = Build.VERSION_CODES.R, codeName = "R")
- * </code>
- */
- String codeName() default "unset";
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java
deleted file mode 100644
index dd32df4..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation to assign a small test size qualifier to a test. This annotation can be used at a
- * method or class level.
- *
- * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a
- * test suite of similar run time.
- *
- * <p>Execution time: <200ms
- *
- * <p>Small tests should be run very frequently. Focused on units of code to verify specific logical
- * conditions. These tests should runs in an isolated environment and use mock objects for external
- * dependencies. Resource access (such as file system, network, or databases) are not permitted.
- * Tests that interact with hardware, make binder calls, or that facilitate android instrumentation
- * should not use this annotation.
- *
- * <p>Note: This class replaces the deprecated Android platform size qualifier <a
- * href="http://developer.android.com/reference/android/test/suitebuilder/annotation/SmallTest.html">
- * android.test.suitebuilder.annotation.SmallTest</a> and is the recommended way to annotate tests
- * written with the AndroidX Test Library.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface SmallTest {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java
deleted file mode 100644
index 88e636c..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Use this annotation on test classes or test methods that should not be included in a test suite.
- * If the annotation appears on the class then no tests in that class will be included. If the
- * annotation appears only on a test method then only that method will be excluded.
- *
- * <p>Note: This class replaces the deprecated Android platform annotation <a
- * href="http://developer.android.com/reference/android/test/suitebuilder/annotation/Suppress.html">
- * android.test.suitebuilder.annotation.Suppress</a> and is the recommended way to suppress tests
- * written with the AndroidX Test Library.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface Suppress {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java
deleted file mode 100644
index e137939..0000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2023 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 androidx.test.runner;
-
-import org.junit.runners.model.InitializationError;
-
-public class AndroidJUnit4 extends androidx.test.ext.junit.runners.AndroidJUnit4 {
- public AndroidJUnit4(Class<?> testClass) throws InitializationError {
- super(testClass);
- }
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java
new file mode 100644
index 0000000..631fc02
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2023 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.hoststubgen.nativesubstitution;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.util.Base64;
+
+import java.text.DecimalFormat;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+public class CursorWindow_host {
+
+ private static final HashMap<Long, CursorWindow_host> sInstances = new HashMap<>();
+ private static long sNextId = 1;
+
+ private int mColumnNum;
+ private static class Row {
+ String[] fields;
+ int[] types;
+ }
+
+ private final List<Row> mRows = new ArrayList<>();
+
+ public static long nativeCreate(String name, int cursorWindowSize) {
+ CursorWindow_host instance = new CursorWindow_host();
+ long instanceId = sNextId++;
+ sInstances.put(instanceId, instance);
+ return instanceId;
+ }
+
+ public static void nativeDispose(long windowPtr) {
+ sInstances.remove(windowPtr);
+ }
+
+ public static boolean nativeSetNumColumns(long windowPtr, int columnNum) {
+ sInstances.get(windowPtr).mColumnNum = columnNum;
+ return true;
+ }
+
+ public static int nativeGetNumRows(long windowPtr) {
+ return sInstances.get(windowPtr).mRows.size();
+ }
+
+ public static boolean nativeAllocRow(long windowPtr) {
+ CursorWindow_host instance = sInstances.get(windowPtr);
+ Row row = new Row();
+ row.fields = new String[instance.mColumnNum];
+ row.types = new int[instance.mColumnNum];
+ Arrays.fill(row.types, Cursor.FIELD_TYPE_NULL);
+ instance.mRows.add(row);
+ return true;
+ }
+
+ private static boolean put(long windowPtr, String value, int type, int row, int column) {
+ CursorWindow_host instance = sInstances.get(windowPtr);
+ if (row >= instance.mRows.size() || column >= instance.mColumnNum) {
+ return false;
+ }
+ Row r = instance.mRows.get(row);
+ r.fields[column] = value;
+ r.types[column] = type;
+ return true;
+ }
+
+ public static int nativeGetType(long windowPtr, int row, int column) {
+ CursorWindow_host instance = sInstances.get(windowPtr);
+ if (row >= instance.mRows.size() || column >= instance.mColumnNum) {
+ return Cursor.FIELD_TYPE_NULL;
+ }
+
+ return instance.mRows.get(row).types[column];
+ }
+
+ public static boolean nativePutString(long windowPtr, String value,
+ int row, int column) {
+ return put(windowPtr, value, Cursor.FIELD_TYPE_STRING, row, column);
+ }
+
+ public static String nativeGetString(long windowPtr, int row, int column) {
+ CursorWindow_host instance = sInstances.get(windowPtr);
+ if (row >= instance.mRows.size() || column >= instance.mColumnNum) {
+ return null;
+ }
+
+ return instance.mRows.get(row).fields[column];
+ }
+
+ public static boolean nativePutLong(long windowPtr, long value, int row, int column) {
+ return put(windowPtr, Long.toString(value), Cursor.FIELD_TYPE_INTEGER, row, column);
+ }
+
+ public static long nativeGetLong(long windowPtr, int row, int column) {
+ String value = nativeGetString(windowPtr, row, column);
+ if (value == null) {
+ return 0;
+ }
+
+ Number number = new DecimalFormat().parse(value, new ParsePosition(0));
+ return number == null ? 0 : number.longValue();
+ }
+
+ public static boolean nativePutDouble(long windowPtr, double value, int row, int column) {
+ return put(windowPtr, Double.toString(value), Cursor.FIELD_TYPE_FLOAT, row, column);
+ }
+
+ public static double nativeGetDouble(long windowPtr, int row, int column) {
+ String value = nativeGetString(windowPtr, row, column);
+ if (value == null) {
+ return 0;
+ }
+
+ Number number = new DecimalFormat().parse(value, new ParsePosition(0));
+ return number == null ? 0 : number.doubleValue();
+ }
+
+ public static boolean nativePutBlob(long windowPtr, byte[] value, int row, int column) {
+ return put(windowPtr, value == null ? null : Base64.encodeToString(value, 0),
+ Cursor.FIELD_TYPE_BLOB, row, column);
+ }
+
+ public static byte[] nativeGetBlob(long windowPtr, int row, int column) {
+ int type = nativeGetType(windowPtr, row, column);
+ switch (type) {
+ case Cursor.FIELD_TYPE_BLOB: {
+ String value = nativeGetString(windowPtr, row, column);
+ return value == null ? null : Base64.decode(value, 0);
+ }
+ case Cursor.FIELD_TYPE_STRING: {
+ String value = nativeGetString(windowPtr, row, column);
+ return value == null ? null : value.getBytes();
+ }
+ case Cursor.FIELD_TYPE_FLOAT:
+ throw new SQLiteException();
+ case Cursor.FIELD_TYPE_INTEGER:
+ throw new SQLiteException();
+ case Cursor.FIELD_TYPE_NULL:
+ default:
+ return null;
+ }
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-framework/Android.bp b/tools/hoststubgen/hoststubgen/test-framework/Android.bp
deleted file mode 100644
index 2b91cc1..0000000
--- a/tools/hoststubgen/hoststubgen/test-framework/Android.bp
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (C) 2023 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 {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-build = ["AndroidHostTest.bp"]
diff --git a/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp b/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp
deleted file mode 100644
index 1f8382a..0000000
--- a/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (C) 2023 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.
-
-// Add `build = ["AndroidHostTest.bp"]` to Android.bp to include this file.
-
-// Compile the test jar, using 2 rules.
-// 1. Build the test against the stub.
-java_library_host {
- name: "HostStubGenTest-framework-test-host-test-lib",
- defaults: ["hosttest-with-framework-all-hidden-api-test-lib-defaults"],
- srcs: [
- "src/**/*.java",
- ],
- static_libs: [
- "junit",
- "truth",
- "mockito",
-
- // http://cs/h/googleplex-android/platform/superproject/main/+/main:platform_testing/libraries/annotations/src/android/platform/test/annotations/
- "platform-test-annotations",
- "hoststubgen-annotations",
- ],
-}
-
-// 2. Link the above module with necessary runtime dependencies, so it can be executed stand-alone.
-java_test_host {
- name: "HostStubGenTest-framework-all-test-host-test",
- defaults: ["hosttest-with-framework-all-hidden-api-test-defaults"],
- static_libs: [
- "HostStubGenTest-framework-test-host-test-lib",
- ],
- test_suites: ["general-tests"],
-}
-
-// "Productionized" build rule.
-android_ravenwood_test {
- name: "HostStubGenTest-framework-test",
- srcs: [
- "src/**/*.java",
- ],
- static_libs: [
- "junit",
- "truth",
- "mockito",
- ],
-}
diff --git a/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml b/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml
deleted file mode 100644
index f35dcf6..0000000
--- a/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2023 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.
--->
-
-<!-- [Ravenwood] Copied from $ANDROID_BUILD_TOP/cts/hostsidetests/devicepolicy/AndroidTest.xml -->
-<configuration description="CtsContentTestCases host-side test">
- <option name="test-suite-tag" value="ravenwood" />
- <option name="config-descriptor:metadata" key="component" value="framework" />
- <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
- <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
-
- <test class="com.android.tradefed.testtype.IsolatedHostTest" >
- <option name="jar" value="HostStubGenTest-framework-all-test-host-test.jar" />
- </test>
-</configuration>
diff --git a/tools/hoststubgen/hoststubgen/test-framework/README.md b/tools/hoststubgen/hoststubgen/test-framework/README.md
deleted file mode 100644
index 26a9ad1..0000000
--- a/tools/hoststubgen/hoststubgen/test-framework/README.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# HostStubGen: (obsolete) real framework test
-
-This directory contains tests against the actual framework.jar code. The tests were
-copied from somewhere else in the android tree. We use this directory to quickly run existing
-tests.
-
-This directory was used during the prototype phase, but now that we have real ravenwood tests,
-this directory is obsolete and should be deleted.
-
-## How to run
-
-- With `atest`. This is the proper way to run it, but it may fail due to atest's known problems.
-
-```
-$ atest HostStubGenTest-framework-all-test-host-test
-```
-
-- Advanced option: `run-test-without-atest.sh` runs the test without using `atest`
-
-```
-$ ./run-test-without-atest.sh
-```
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh b/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh
deleted file mode 100755
index cfc06a1..0000000
--- a/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/bin/bash
-# Copyright (C) 2023 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.
-
-# Run HostStubGenTest-framework-test-host-test directly with JUnit.
-# (without using atest.)
-
-source "${0%/*}"/../../common.sh
-
-
-# Options:
-# -v enable verbose log
-# -d enable debugger
-
-verbose=0
-debug=0
-while getopts "vd" opt; do
- case "$opt" in
- v) verbose=1 ;;
- d) debug=1 ;;
- esac
-done
-shift $(($OPTIND - 1))
-
-
-if (( $verbose )) ; then
- JAVA_OPTS="$JAVA_OPTS -verbose:class"
-fi
-
-if (( $debug )) ; then
- JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8700"
-fi
-
-#=======================================
-module=HostStubGenTest-framework-all-test-host-test
-module_jar=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/$module/$module.jar
-run m $module
-
-out=out
-
-rm -fr $out
-mkdir -p $out
-
-
-# Copy and extract the relevant jar files so we can look into them.
-run cp \
- $module_jar \
- $SOONG_INT/frameworks/base/tools/hoststubgen/hoststubgen/framework-all-hidden-api-host/linux_glibc_common/gen/*.jar \
- $out
-
-run extract $out/*.jar
-
-# Result is the number of failed tests.
-result=0
-
-
-# This suite runs all tests in the JAR.
-tests=(com.android.hoststubgen.hosthelper.HostTestSuite)
-
-# Uncomment this to run a specific test.
-# tests=(com.android.hoststubgen.frameworktest.LogTest)
-
-
-for class in ${tests[@]} ; do
- echo "Running $class ..."
-
- run cd "${module_jar%/*}"
- run $JAVA $JAVA_OPTS \
- -cp $module_jar \
- org.junit.runner.JUnitCore \
- $class || result=$(( $result + 1 ))
-done
-
-exit $result
diff --git a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java b/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java
deleted file mode 100644
index 2c5949c..0000000
--- a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java
+++ /dev/null
@@ -1,472 +0,0 @@
-/*
- * Copyright (C) 2023 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.hoststubgen.frameworktest;
-
-// [ravewnwood] Copied from cts/, and commented out unsupported stuff.
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.util.ArrayMap;
-import android.util.Log;
-
-import org.junit.Test;
-
-import java.util.AbstractMap;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.function.BiFunction;
-
-/**
- * Some basic tests for {@link android.util.ArrayMap}.
- */
-public class ArrayMapTest {
- static final boolean DEBUG = false;
-
- private static boolean compare(Object v1, Object v2) {
- if (v1 == null) {
- return v2 == null;
- }
- if (v2 == null) {
- return false;
- }
- return v1.equals(v2);
- }
-
- private static void compareMaps(HashMap map, ArrayMap array) {
- if (map.size() != array.size()) {
- fail("Bad size: expected " + map.size() + ", got " + array.size());
- }
-
- Set<Entry> mapSet = map.entrySet();
- for (Map.Entry entry : mapSet) {
- Object expValue = entry.getValue();
- Object gotValue = array.get(entry.getKey());
- if (!compare(expValue, gotValue)) {
- fail("Bad value: expected " + expValue + ", got " + gotValue
- + " at key " + entry.getKey());
- }
- }
-
- for (int i = 0; i < array.size(); i++) {
- Object gotValue = array.valueAt(i);
- Object key = array.keyAt(i);
- Object expValue = map.get(key);
- if (!compare(expValue, gotValue)) {
- fail("Bad value: expected " + expValue + ", got " + gotValue
- + " at key " + key);
- }
- }
-
- if (map.entrySet().hashCode() != array.entrySet().hashCode()) {
- fail("Entry set hash codes differ: map=0x"
- + Integer.toHexString(map.entrySet().hashCode()) + " array=0x"
- + Integer.toHexString(array.entrySet().hashCode()));
- }
-
- if (!map.entrySet().equals(array.entrySet())) {
- fail("Failed calling equals on map entry set against array set");
- }
-
- if (!array.entrySet().equals(map.entrySet())) {
- fail("Failed calling equals on array entry set against map set");
- }
-
- if (map.keySet().hashCode() != array.keySet().hashCode()) {
- fail("Key set hash codes differ: map=0x"
- + Integer.toHexString(map.keySet().hashCode()) + " array=0x"
- + Integer.toHexString(array.keySet().hashCode()));
- }
-
- if (!map.keySet().equals(array.keySet())) {
- fail("Failed calling equals on map key set against array set");
- }
-
- if (!array.keySet().equals(map.keySet())) {
- fail("Failed calling equals on array key set against map set");
- }
-
- if (!map.keySet().containsAll(array.keySet())) {
- fail("Failed map key set contains all of array key set");
- }
-
- if (!array.keySet().containsAll(map.keySet())) {
- fail("Failed array key set contains all of map key set");
- }
-
- if (!array.containsAll(map.keySet())) {
- fail("Failed array contains all of map key set");
- }
-
- if (!map.entrySet().containsAll(array.entrySet())) {
- fail("Failed map entry set contains all of array entry set");
- }
-
- if (!array.entrySet().containsAll(map.entrySet())) {
- fail("Failed array entry set contains all of map entry set");
- }
- }
-
- private static void validateArrayMap(ArrayMap array) {
- Set<Map.Entry> entrySet = array.entrySet();
- int index = 0;
- Iterator<Entry> entryIt = entrySet.iterator();
- while (entryIt.hasNext()) {
- Map.Entry entry = entryIt.next();
- Object value = entry.getKey();
- Object realValue = array.keyAt(index);
- if (!compare(realValue, value)) {
- fail("Bad array map entry set: expected key " + realValue
- + ", got " + value + " at index " + index);
- }
- value = entry.getValue();
- realValue = array.valueAt(index);
- if (!compare(realValue, value)) {
- fail("Bad array map entry set: expected value " + realValue
- + ", got " + value + " at index " + index);
- }
- index++;
- }
-
- index = 0;
- Set keySet = array.keySet();
- Iterator keyIt = keySet.iterator();
- while (keyIt.hasNext()) {
- Object value = keyIt.next();
- Object realValue = array.keyAt(index);
- if (!compare(realValue, value)) {
- fail("Bad array map key set: expected key " + realValue
- + ", got " + value + " at index " + index);
- }
- index++;
- }
-
- index = 0;
- Collection valueCol = array.values();
- Iterator valueIt = valueCol.iterator();
- while (valueIt.hasNext()) {
- Object value = valueIt.next();
- Object realValue = array.valueAt(index);
- if (!compare(realValue, value)) {
- fail("Bad array map value col: expected value " + realValue
- + ", got " + value + " at index " + index);
- }
- index++;
- }
- }
-
- private static void dump(Map map, ArrayMap array) {
- Log.e("test", "HashMap of " + map.size() + " entries:");
- Set<Map.Entry> mapSet = map.entrySet();
- for (Map.Entry entry : mapSet) {
- Log.e("test", " " + entry.getKey() + " -> " + entry.getValue());
- }
- Log.e("test", "ArrayMap of " + array.size() + " entries:");
- for (int i = 0; i < array.size(); i++) {
- Log.e("test", " " + array.keyAt(i) + " -> " + array.valueAt(i));
- }
- }
-
- private static void dump(ArrayMap map1, ArrayMap map2) {
- Log.e("test", "ArrayMap of " + map1.size() + " entries:");
- for (int i = 0; i < map1.size(); i++) {
- Log.e("test", " " + map1.keyAt(i) + " -> " + map1.valueAt(i));
- }
- Log.e("test", "ArrayMap of " + map2.size() + " entries:");
- for (int i = 0; i < map2.size(); i++) {
- Log.e("test", " " + map2.keyAt(i) + " -> " + map2.valueAt(i));
- }
- }
-
- @Test
- public void testCopyArrayMap() {
- // map copy constructor test
- ArrayMap newMap = new ArrayMap<Integer, String>();
- for (int i = 0; i < 10; ++i) {
- newMap.put(i, String.valueOf(i));
- }
- ArrayMap mapCopy = new ArrayMap(newMap);
- if (!compare(mapCopy, newMap)) {
- String msg = "ArrayMap copy constructor failure: expected " +
- newMap + ", got " + mapCopy;
- Log.e("test", msg);
- dump(newMap, mapCopy);
- fail(msg);
- return;
- }
- }
-
- @Test
- public void testEqualsArrayMap() {
- ArrayMap<Integer, String> map1 = new ArrayMap<>();
- ArrayMap<Integer, String> map2 = new ArrayMap<>();
- HashMap<Integer, String> map3 = new HashMap<>();
- if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) {
- fail("ArrayMap equals failure for empty maps " + map1 + ", " +
- map2 + ", " + map3);
- }
-
- for (int i = 0; i < 10; ++i) {
- String value = String.valueOf(i);
- map1.put(i, value);
- map2.put(i, value);
- map3.put(i, value);
- }
- if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) {
- fail("ArrayMap equals failure for populated maps " + map1 + ", " +
- map2 + ", " + map3);
- }
-
- map1.remove(0);
- if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) {
- fail("ArrayMap equals failure for map size " + map1 + ", " +
- map2 + ", " + map3);
- }
-
- map1.put(0, "-1");
- if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) {
- fail("ArrayMap equals failure for map contents " + map1 + ", " +
- map2 + ", " + map3);
- }
- }
-
- private static void checkEntrySetToArray(ArrayMap<?, ?> testMap) {
- try {
- testMap.entrySet().toArray();
- fail();
- } catch (UnsupportedOperationException expected) {
- }
-
- try {
- Map.Entry<?, ?>[] entries = new Map.Entry[20];
- testMap.entrySet().toArray(entries);
- fail();
- } catch (UnsupportedOperationException expected) {
- }
- }
-
- // http://b/32294038, Test ArrayMap.entrySet().toArray()
- @Test
- public void testEntrySetArray() {
- // Create
- ArrayMap<Integer, String> testMap = new ArrayMap<>();
-
- // Test empty
- checkEntrySetToArray(testMap);
-
- // Test non-empty
- for (int i = 0; i < 10; ++i) {
- testMap.put(i, String.valueOf(i));
- }
- checkEntrySetToArray(testMap);
- }
-
- @Test
- public void testCanNotIteratePastEnd_entrySetIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Set<Map.Entry<String, String>> expectedEntriesToIterate = new HashSet<>(Arrays.asList(
- entryOf("key 1", "value 1"),
- entryOf("key 2", "value 2")
- ));
- Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
-
- // Assert iteration over the expected two entries in any order
- assertTrue(iterator.hasNext());
- Map.Entry<String, String> firstEntry = copyOf(iterator.next());
- assertTrue(expectedEntriesToIterate.remove(firstEntry));
-
- assertTrue(iterator.hasNext());
- Map.Entry<String, String> secondEntry = copyOf(iterator.next());
- assertTrue(expectedEntriesToIterate.remove(secondEntry));
-
- assertFalse(iterator.hasNext());
-
- try {
- iterator.next();
- fail();
- } catch (NoSuchElementException expected) {
- }
- }
-
- private static <K, V> Map.Entry<K, V> entryOf(K key, V value) {
- return new AbstractMap.SimpleEntry<>(key, value);
- }
-
- private static <K, V> Map.Entry<K, V> copyOf(Map.Entry<K, V> entry) {
- return entryOf(entry.getKey(), entry.getValue());
- }
-
- @Test
- public void testCanNotIteratePastEnd_keySetIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Set<String> expectedKeysToIterate = new HashSet<>(Arrays.asList("key 1", "key 2"));
- Iterator<String> iterator = map.keySet().iterator();
-
- // Assert iteration over the expected two keys in any order
- assertTrue(iterator.hasNext());
- String firstKey = iterator.next();
- assertTrue(expectedKeysToIterate.remove(firstKey));
-
- assertTrue(iterator.hasNext());
- String secondKey = iterator.next();
- assertTrue(expectedKeysToIterate.remove(secondKey));
-
- assertFalse(iterator.hasNext());
-
- try {
- iterator.next();
- fail();
- } catch (NoSuchElementException expected) {
- }
- }
-
- @Test
- public void testCanNotIteratePastEnd_valuesIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Set<String> expectedValuesToIterate = new HashSet<>(Arrays.asList("value 1", "value 2"));
- Iterator<String> iterator = map.values().iterator();
-
- // Assert iteration over the expected two values in any order
- assertTrue(iterator.hasNext());
- String firstValue = iterator.next();
- assertTrue(expectedValuesToIterate.remove(firstValue));
-
- assertTrue(iterator.hasNext());
- String secondValue = iterator.next();
- assertTrue(expectedValuesToIterate.remove(secondValue));
-
- assertFalse(iterator.hasNext());
-
- try {
- iterator.next();
- fail();
- } catch (NoSuchElementException expected) {
- }
- }
-
- @Test
- public void testForEach() {
- ArrayMap<String, Integer> map = new ArrayMap<>();
-
- for (int i = 0; i < 50; ++i) {
- map.put(Integer.toString(i), i * 10);
- }
-
- // Make sure forEach goes through all of the elements.
- HashMap<String, Integer> seen = new HashMap<>();
- map.forEach(seen::put);
- compareMaps(seen, map);
- }
-
- /**
- * The entrySet Iterator returns itself from each call to {@code next()}. This is unusual
- * behavior for {@link Iterator#next()}; this test ensures that any future change to this
- * behavior is deliberate.
- */
- @Test
- public void testUnusualBehavior_eachEntryIsSameAsIterator_entrySetIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
-
- assertSame(iterator, iterator.next());
- assertSame(iterator, iterator.next());
- }
-
- @SuppressWarnings("SelfEquals")
- @Test
- public void testUnusualBehavior_equalsThrowsAfterRemove_entrySetIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
- iterator.next();
- iterator.remove();
- try {
- iterator.equals(iterator);
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- private static <T> void assertEqualsBothWays(T a, T b) {
- assertEquals(a, b);
- assertEquals(b, a);
- assertEquals(a.hashCode(), b.hashCode());
- }
-
- @Test
- public void testRemoveAll() {
- final ArrayMap<Integer, String> map = new ArrayMap<>();
- for (Integer i : Arrays.asList(0, 1, 2, 3, 4, 5)) {
- map.put(i, i.toString());
- }
-
- final ArrayMap<Integer, String> expectedMap = new ArrayMap<>();
- for (Integer i : Arrays.asList(2, 4)) {
- expectedMap.put(i, String.valueOf(i));
- }
- map.removeAll(Arrays.asList(0, 1, 3, 5, 6));
- if (!compare(map, expectedMap)) {
- fail("ArrayMap removeAll failure, expect " + expectedMap + ", but " + map);
- }
-
- map.removeAll(Collections.emptyList());
- if (!compare(map, expectedMap)) {
- fail("ArrayMap removeAll failure for empty maps, expect " + expectedMap + ", but " +
- map);
- }
-
- map.removeAll(Arrays.asList(2, 4));
- if (!map.isEmpty()) {
- fail("ArrayMap removeAll failure, expect empty, but " + map);
- }
- }
-
- @Test
- public void testReplaceAll() {
- final ArrayMap<Integer, Integer> map = new ArrayMap<>();
- final ArrayMap<Integer, Integer> expectedMap = new ArrayMap<>();
- final BiFunction<Integer, Integer, Integer> function = (k, v) -> 2 * v;
- for (Integer i : Arrays.asList(0, 1, 2, 3, 4, 5)) {
- map.put(i, i);
- expectedMap.put(i, 2 * i);
- }
-
- map.replaceAll(function);
- if (!compare(map, expectedMap)) {
- fail("ArrayMap replaceAll failure, expect " + expectedMap + ", but " + map);
- }
- }
-}
diff --git a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java b/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java
deleted file mode 100644
index 3e33b54..0000000
--- a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2023 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.hoststubgen.frameworktest;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.util.Log;
-
-import org.junit.Test;
-
-/**
- * Some basic tests for {@link android.util.Log}.
- */
-public class LogTest {
- @Test
- public void testBasicLogging() {
- Log.v("TAG", "Test v log");
- Log.d("TAG", "Test d log");
- Log.i("TAG", "Test i log");
- Log.w("TAG", "Test w log");
- Log.e("TAG", "Test e log");
- }
-
- @Test
- public void testNativeMethods() {
- assertThat(Log.isLoggable("mytag", Log.INFO)).isTrue();
- }
-}
diff --git a/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh b/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh
deleted file mode 100755
index 7268123..0000000
--- a/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-# Copyright (C) 2023 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.
-
-
-# Script to build `framework-host-stub` and `framework-host-impl`, and copy the
-# generated jars to $out, and unzip them. Useful for looking into the generated files.
-
-source "${0%/*}"/../common.sh
-
-out=framework-all-stub-out
-
-rm -fr $out
-mkdir -p $out
-
-# Build the jars with `m`.
-run m framework-all-hidden-api-host
-
-# Copy the jar to out/ and extract them.
-run cp \
- $SOONG_INT/frameworks/base/tools/hoststubgen/hoststubgen/framework-all-hidden-api-host/linux_glibc_common/gen/* \
- $out
-
-extract $out/*.jar
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index 222c874..a6847ae 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -22,7 +22,6 @@
# These tests are known to pass.
READY_TEST_MODULES=(
- HostStubGenTest-framework-all-test-host-test
hoststubgen-test-tiny-test
CtsUtilTestCasesRavenwood
CtsOsTestCasesRavenwood # This one uses native sustitution, so let's run it too.
@@ -30,7 +29,6 @@
MUST_BUILD_MODULES=(
"${NOT_READY_TEST_MODULES[*]}"
- HostStubGenTest-framework-test
)
# First, build all the test / etc modules. This shouldn't fail.
@@ -44,11 +42,8 @@
# files, and they may fail when something changes in the build system.
run ./hoststubgen/test-tiny-framework/diff-and-update-golden.sh
-run ./hoststubgen/test-framework/run-test-without-atest.sh
-
run ./hoststubgen/test-tiny-framework/run-test-manually.sh
run atest $ATEST_ARGS tiny-framework-dump-test
-run ./scripts/build-framework-hostside-jars-and-extract.sh
# This script is already broken on goog/master
# run ./scripts/build-framework-hostside-jars-without-genrules.sh