Merge "[Catalyst] Provide launch intent for preference graph" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 8bd4367..f267131 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -294,7 +294,7 @@
field public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
field public static final String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH";
field public static final String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE";
- field @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public static final String SET_BIOMETRIC_DIALOG_ADVANCED = "android.permission.SET_BIOMETRIC_DIALOG_ADVANCED";
+ field public static final String SET_BIOMETRIC_DIALOG_ADVANCED = "android.permission.SET_BIOMETRIC_DIALOG_ADVANCED";
field public static final String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP";
field @Deprecated public static final String SET_PREFERRED_APPLICATIONS = "android.permission.SET_PREFERRED_APPLICATIONS";
field public static final String SET_PROCESS_LIMIT = "android.permission.SET_PROCESS_LIMIT";
@@ -16747,7 +16747,7 @@
method public boolean hasGlyph(String);
method public final boolean isAntiAlias();
method public final boolean isDither();
- method public boolean isElegantTextHeight();
+ method @Deprecated @FlaggedApi("com.android.text.flags.deprecate_elegant_text_height_api") public boolean isElegantTextHeight();
method public final boolean isFakeBoldText();
method public final boolean isFilterBitmap();
method public final boolean isLinearText();
@@ -16768,7 +16768,7 @@
method public void setColor(@ColorLong long);
method public android.graphics.ColorFilter setColorFilter(android.graphics.ColorFilter);
method public void setDither(boolean);
- method public void setElegantTextHeight(boolean);
+ method @Deprecated @FlaggedApi("com.android.text.flags.deprecate_elegant_text_height_api") public void setElegantTextHeight(boolean);
method public void setEndHyphenEdit(int);
method public void setFakeBoldText(boolean);
method public void setFilterBitmap(boolean);
@@ -19103,11 +19103,11 @@
method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public void authenticate(@NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject, @NonNull android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public void authenticate(@NonNull android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
method @Nullable public int getAllowedAuthenticators();
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView();
+ method @Nullable public android.hardware.biometrics.PromptContentView getContentView();
method @Nullable public CharSequence getDescription();
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.graphics.Bitmap getLogoBitmap();
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public String getLogoDescription();
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public int getLogoRes();
+ method @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.graphics.Bitmap getLogoBitmap();
+ method @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public String getLogoDescription();
+ method @DrawableRes @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public int getLogoRes();
method @Nullable public CharSequence getNegativeButtonText();
method @Nullable public CharSequence getSubtitle();
method @NonNull public CharSequence getTitle();
@@ -19156,12 +19156,12 @@
method @NonNull public android.hardware.biometrics.BiometricPrompt build();
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setAllowedAuthenticators(int);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setConfirmationRequired(boolean);
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView);
+ method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.hardware.biometrics.BiometricPrompt.Builder setLogoBitmap(@NonNull android.graphics.Bitmap);
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.hardware.biometrics.BiometricPrompt.Builder setLogoDescription(@NonNull String);
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.hardware.biometrics.BiometricPrompt.Builder setLogoRes(@DrawableRes int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.hardware.biometrics.BiometricPrompt.Builder setLogoBitmap(@NonNull android.graphics.Bitmap);
+ method @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.hardware.biometrics.BiometricPrompt.Builder setLogoDescription(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.hardware.biometrics.BiometricPrompt.Builder setLogoRes(@DrawableRes int);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence);
@@ -19184,27 +19184,27 @@
method @Nullable public java.security.Signature getSignature();
}
- @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentItem {
+ public interface PromptContentItem {
}
- @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
+ public final class PromptContentItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
ctor public PromptContentItemBulletedText(@NonNull String);
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemBulletedText> CREATOR;
}
- @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemPlainText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
+ public final class PromptContentItemPlainText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
ctor public PromptContentItemPlainText(@NonNull String);
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemPlainText> CREATOR;
}
- @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentView {
+ public interface PromptContentView {
}
- @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentViewWithMoreOptionsButton implements android.os.Parcelable android.hardware.biometrics.PromptContentView {
+ public final class PromptContentViewWithMoreOptionsButton implements android.os.Parcelable android.hardware.biometrics.PromptContentView {
method public int describeContents();
method @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public String getDescription();
method @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.content.DialogInterface.OnClickListener getMoreOptionsButtonListener();
@@ -19219,7 +19219,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED) public android.hardware.biometrics.PromptContentViewWithMoreOptionsButton.Builder setMoreOptionsButtonListener(@NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
}
- @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptVerticalListContentView implements android.os.Parcelable android.hardware.biometrics.PromptContentView {
+ public final class PromptVerticalListContentView implements android.os.Parcelable android.hardware.biometrics.PromptContentView {
method public int describeContents();
method @Nullable public String getDescription();
method @NonNull public java.util.List<android.hardware.biometrics.PromptContentItem> getListItems();
@@ -20109,6 +20109,7 @@
public class MultiResolutionImageReader implements java.lang.AutoCloseable {
ctor public MultiResolutionImageReader(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int, @IntRange(from=1) int);
+ ctor @FlaggedApi("com.android.internal.camera.flags.multiresolution_imagereader_usage_public") public MultiResolutionImageReader(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int, @IntRange(from=1) int, long);
method public void close();
method protected void finalize();
method public void flush();
@@ -22668,7 +22669,6 @@
method public void sendEvent(int, int, @Nullable byte[]) throws android.media.MediaCasException;
method public void setEventListener(@Nullable android.media.MediaCas.EventListener, @Nullable android.os.Handler);
method public void setPrivateData(@NonNull byte[]) throws android.media.MediaCasException;
- method @FlaggedApi("com.android.media.flags.update_client_profile_priority") public boolean updateResourcePriority(int, int);
field public static final int PLUGIN_STATUS_PHYSICAL_MODULE_CHANGED = 0; // 0x0
field public static final int PLUGIN_STATUS_SESSION_NUMBER_CHANGED = 1; // 0x1
field public static final int SCRAMBLING_MODE_AES128 = 9; // 0x9
@@ -34179,7 +34179,7 @@
method public android.os.StrictMode.VmPolicy build();
method @NonNull public android.os.StrictMode.VmPolicy.Builder detectActivityLeaks();
method @NonNull public android.os.StrictMode.VmPolicy.Builder detectAll();
- method @FlaggedApi("com.android.window.flags.bal_strict_mode") @NonNull public android.os.StrictMode.VmPolicy.Builder detectBlockedBackgroundActivityLaunch();
+ method @FlaggedApi("com.android.window.flags.bal_strict_mode_ro") @NonNull public android.os.StrictMode.VmPolicy.Builder detectBlockedBackgroundActivityLaunch();
method @NonNull public android.os.StrictMode.VmPolicy.Builder detectCleartextNetwork();
method @NonNull public android.os.StrictMode.VmPolicy.Builder detectContentUriWithoutPermission();
method @NonNull public android.os.StrictMode.VmPolicy.Builder detectCredentialProtectedWhileLocked();
@@ -34192,7 +34192,7 @@
method @NonNull public android.os.StrictMode.VmPolicy.Builder detectNonSdkApiUsage();
method @NonNull public android.os.StrictMode.VmPolicy.Builder detectUnsafeIntentLaunch();
method @NonNull public android.os.StrictMode.VmPolicy.Builder detectUntaggedSockets();
- method @FlaggedApi("com.android.window.flags.bal_strict_mode") @NonNull public android.os.StrictMode.VmPolicy.Builder ignoreBlockedBackgroundActivityLaunch();
+ method @FlaggedApi("com.android.window.flags.bal_strict_mode_ro") @NonNull public android.os.StrictMode.VmPolicy.Builder ignoreBlockedBackgroundActivityLaunch();
method @NonNull public android.os.StrictMode.VmPolicy.Builder penaltyDeath();
method @NonNull public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnCleartextNetwork();
method @NonNull public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnFileUriExposure();
@@ -51080,6 +51080,7 @@
method @NonNull public android.view.DisplayShape getShape();
method @Deprecated public void getSize(android.graphics.Point);
method public int getState();
+ method @FlaggedApi("com.android.server.display.feature.flags.enable_get_suggested_frame_rate") public float getSuggestedFrameRate(int);
method public android.view.Display.Mode[] getSupportedModes();
method @Deprecated public float[] getSupportedRefreshRates();
method @Deprecated public int getWidth();
@@ -51097,6 +51098,8 @@
field public static final int FLAG_ROUND = 16; // 0x10
field public static final int FLAG_SECURE = 2; // 0x2
field public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1; // 0x1
+ field @FlaggedApi("com.android.server.display.feature.flags.enable_get_suggested_frame_rate") public static final int FRAME_RATE_CATEGORY_HIGH = 1; // 0x1
+ field @FlaggedApi("com.android.server.display.feature.flags.enable_get_suggested_frame_rate") public static final int FRAME_RATE_CATEGORY_NORMAL = 0; // 0x0
field public static final int INVALID_DISPLAY = -1; // 0xffffffff
field public static final int STATE_DOZE = 3; // 0x3
field public static final int STATE_DOZE_SUSPEND = 4; // 0x4
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 4d1a423..4c9ce07 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -144,21 +144,12 @@
public class UsbManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int getGadgetHalVersion();
- method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int getUsbBandwidthMbps();
method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int getUsbHalVersion();
field public static final int GADGET_HAL_NOT_SUPPORTED = -1; // 0xffffffff
field public static final int GADGET_HAL_V1_0 = 10; // 0xa
field public static final int GADGET_HAL_V1_1 = 11; // 0xb
field public static final int GADGET_HAL_V1_2 = 12; // 0xc
field public static final int GADGET_HAL_V2_0 = 20; // 0x14
- field public static final int USB_DATA_TRANSFER_RATE_10G = 10240; // 0x2800
- field public static final int USB_DATA_TRANSFER_RATE_20G = 20480; // 0x5000
- field public static final int USB_DATA_TRANSFER_RATE_40G = 40960; // 0xa000
- field public static final int USB_DATA_TRANSFER_RATE_5G = 5120; // 0x1400
- field public static final int USB_DATA_TRANSFER_RATE_FULL_SPEED = 12; // 0xc
- field public static final int USB_DATA_TRANSFER_RATE_HIGH_SPEED = 480; // 0x1e0
- field public static final int USB_DATA_TRANSFER_RATE_LOW_SPEED = 2; // 0x2
- field public static final int USB_DATA_TRANSFER_RATE_UNKNOWN = -1; // 0xffffffff
field public static final int USB_HAL_NOT_SUPPORTED = -1; // 0xffffffff
field public static final int USB_HAL_RETRY = -2; // 0xfffffffe
field public static final int USB_HAL_V1_0 = 10; // 0xa
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f9cd316..c21d077 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7066,6 +7066,7 @@
public class UsbManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public long getCurrentFunctions();
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_USB) public java.util.List<android.hardware.usb.UsbPort> getPorts();
+ method @FlaggedApi("android.hardware.usb.flags.expose_usb_speed_system_api") @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int getUsbBandwidthMbps();
method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void grantPermission(android.hardware.usb.UsbDevice, String);
method public static boolean isUvcSupportEnabled();
method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void registerDisplayPortAltModeInfoListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.usb.UsbManager.DisplayPortAltModeInfoListener);
@@ -7092,6 +7093,14 @@
field public static final long FUNCTION_UVC = 128L; // 0x80L
field public static final String USB_CONFIGURED = "configured";
field public static final String USB_CONNECTED = "connected";
+ field @FlaggedApi("android.hardware.usb.flags.expose_usb_speed_system_api") public static final int USB_DATA_TRANSFER_RATE_10G = 10240; // 0x2800
+ field @FlaggedApi("android.hardware.usb.flags.expose_usb_speed_system_api") public static final int USB_DATA_TRANSFER_RATE_20G = 20480; // 0x5000
+ field @FlaggedApi("android.hardware.usb.flags.expose_usb_speed_system_api") public static final int USB_DATA_TRANSFER_RATE_40G = 40960; // 0xa000
+ field @FlaggedApi("android.hardware.usb.flags.expose_usb_speed_system_api") public static final int USB_DATA_TRANSFER_RATE_5G = 5120; // 0x1400
+ field @FlaggedApi("android.hardware.usb.flags.expose_usb_speed_system_api") public static final int USB_DATA_TRANSFER_RATE_FULL_SPEED = 12; // 0xc
+ field @FlaggedApi("android.hardware.usb.flags.expose_usb_speed_system_api") public static final int USB_DATA_TRANSFER_RATE_HIGH_SPEED = 480; // 0x1e0
+ field @FlaggedApi("android.hardware.usb.flags.expose_usb_speed_system_api") public static final int USB_DATA_TRANSFER_RATE_LOW_SPEED = 2; // 0x2
+ field @FlaggedApi("android.hardware.usb.flags.expose_usb_speed_system_api") public static final int USB_DATA_TRANSFER_RATE_UNKNOWN = -1; // 0xffffffff
field public static final String USB_FUNCTION_NCM = "ncm";
field public static final String USB_FUNCTION_RNDIS = "rndis";
}
@@ -7218,6 +7227,7 @@
field @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public static final int USAGE_CALL_ASSISTANT = 17; // 0x11
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_EMERGENCY = 1000; // 0x3e8
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_SAFETY = 1001; // 0x3e9
+ field @FlaggedApi("android.media.audio.speaker_cleanup_usage") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_SPEAKER_CLEANUP = 1004; // 0x3ec
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_VEHICLE_STATUS = 1002; // 0x3ea
}
@@ -7572,6 +7582,11 @@
method @NonNull public android.media.HwAudioSource.Builder setAudioDeviceInfo(@NonNull android.media.AudioDeviceInfo);
}
+ public final class MediaCas implements java.lang.AutoCloseable {
+ method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceHolderRetain(boolean);
+ method @FlaggedApi("com.android.media.flags.update_client_profile_priority") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean updateResourcePriority(int, int);
+ }
+
public final class MediaCodec {
method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_RESOURCE_OVERRIDE_PID) public static android.media.MediaCodec createByCodecNameForClient(@NonNull String, int, int) throws java.io.IOException;
}
@@ -8344,6 +8359,7 @@
method public int setLnaEnabled(boolean);
method public int setMaxNumberOfFrontends(int, @IntRange(from=0) int);
method public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener);
+ method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceHolderRetain(boolean);
method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener);
method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
method public int transferOwner(@NonNull android.media.tv.tuner.Tuner);
@@ -10991,7 +11007,7 @@
public class Environment {
method @NonNull public static java.io.File getDataCePackageDirectoryForUser(@NonNull java.util.UUID, @NonNull android.os.UserHandle, @NonNull String);
method @NonNull public static java.io.File getDataDePackageDirectoryForUser(@NonNull java.util.UUID, @NonNull android.os.UserHandle, @NonNull String);
- method @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") @NonNull public static java.io.File getDataSystemDeDirectory();
+ method @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") @NonNull public static java.io.File getDataSystemDeviceProtectedDirectory();
method @NonNull public static java.util.Collection<java.io.File> getInternalMediaDirectories();
method @NonNull public static java.io.File getOdmDirectory();
method @NonNull public static java.io.File getOemDirectory();
@@ -15686,6 +15702,9 @@
method @FlaggedApi("android.permission.flags.get_emergency_role_holder_api_enabled") @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getEmergencyAssistancePackageName();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
+ method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @NonNull @RequiresPermission(value=android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, conditional=true) public java.util.List<java.lang.String> getImsPcscfAddresses();
+ method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @Nullable @RequiresPermission(android.Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER) public String getImsPrivateUserIdentity();
+ method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @NonNull @RequiresPermission(value=android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, conditional=true) public java.util.List<android.net.Uri> getImsPublicUserIdentities();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
method @FlaggedApi("com.android.server.telecom.flags.get_last_known_cell_identity") @Nullable @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID}) public android.telephony.CellIdentity getLastKnownCellIdentity();
@@ -15704,6 +15723,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimCardState(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimCardState(int, int);
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Locale getSimLocale();
+ method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getSimServiceTable(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<byte[],java.lang.Exception>);
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Collection<android.telephony.UiccSlotMapping> getSimSlotMapping();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.telephony.RadioAccessSpecifier> getSystemSelectionChannels();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
@@ -16828,8 +16848,11 @@
method public void callSessionRttMessageReceived(String);
method public void callSessionRttModifyRequestReceived(android.telephony.ims.ImsCallProfile);
method public void callSessionRttModifyResponseReceived(int);
+ method @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public final void callSessionSendAnbrQuery(int, int, @IntRange(from=0) int);
method public void callSessionSuppServiceReceived(android.telephony.ims.ImsSuppServiceNotification);
method public void callSessionTerminated(android.telephony.ims.ImsReasonInfo);
+ method @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public void callSessionTransferFailed(@NonNull android.telephony.ims.ImsReasonInfo);
+ method @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public void callSessionTransferred();
method public void callSessionTtyModeReceived(int);
method public void callSessionUpdateFailed(android.telephony.ims.ImsReasonInfo);
method public void callSessionUpdateReceived(android.telephony.ims.ImsCallProfile);
@@ -17634,6 +17657,26 @@
method public int getRadioTech();
}
+ @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public final class ConnectionFailureInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getCauseCode();
+ method public int getReason();
+ method public int getWaitTimeMillis();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.feature.ConnectionFailureInfo> CREATOR;
+ field public static final int REASON_ACCESS_DENIED = 1; // 0x1
+ field public static final int REASON_NAS_FAILURE = 2; // 0x2
+ field public static final int REASON_NONE = 0; // 0x0
+ field public static final int REASON_NO_SERVICE = 7; // 0x7
+ field public static final int REASON_PDN_NOT_AVAILABLE = 8; // 0x8
+ field public static final int REASON_RACH_FAILURE = 3; // 0x3
+ field public static final int REASON_RF_BUSY = 9; // 0x9
+ field public static final int REASON_RLC_FAILURE = 4; // 0x4
+ field public static final int REASON_RRC_REJECT = 5; // 0x5
+ field public static final int REASON_RRC_TIMEOUT = 6; // 0x6
+ field public static final int REASON_UNSPECIFIED = 65535; // 0xffff
+ }
+
public abstract class ImsFeature {
ctor public ImsFeature();
method public abstract void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
@@ -17660,6 +17703,11 @@
method public void onChangeCapabilityConfigurationError(int, int, int);
}
+ @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public interface ImsTrafficSessionCallback {
+ method public void onError(@NonNull android.telephony.ims.feature.ConnectionFailureInfo);
+ method public void onReady();
+ }
+
public class MmTelFeature extends android.telephony.ims.feature.ImsFeature {
ctor public MmTelFeature();
ctor public MmTelFeature(@NonNull java.util.concurrent.Executor);
@@ -17672,6 +17720,7 @@
method @NonNull public android.telephony.ims.stub.ImsMultiEndpointImplBase getMultiEndpoint();
method @NonNull public android.telephony.ims.stub.ImsSmsImplBase getSmsImplementation();
method @NonNull public android.telephony.ims.stub.ImsUtImplBase getUt();
+ method @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public final void modifyImsTrafficSession(int, @NonNull android.telephony.ims.feature.ImsTrafficSessionCallback);
method public final void notifyCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.MmTelFeature.MmTelCapabilities);
method @Deprecated public final void notifyIncomingCall(@NonNull android.telephony.ims.stub.ImsCallSessionImplBase, @NonNull android.os.Bundle);
method @Nullable public final android.telephony.ims.ImsCallSessionListener notifyIncomingCall(@NonNull android.telephony.ims.stub.ImsCallSessionImplBase, @NonNull String, @NonNull android.os.Bundle);
@@ -17692,10 +17741,24 @@
method public void setTerminalBasedCallWaitingStatus(boolean);
method public void setUiTtyMode(int, @Nullable android.os.Message);
method public int shouldProcessCall(@NonNull String[]);
+ method @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public final void startImsTrafficSession(int, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.feature.ImsTrafficSessionCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public final void stopImsTrafficSession(@NonNull android.telephony.ims.feature.ImsTrafficSessionCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public final void triggerEpsFallback(int);
field public static final int AUDIO_HANDLER_ANDROID = 0; // 0x0
field public static final int AUDIO_HANDLER_BASEBAND = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public static final int EPS_FALLBACK_REASON_NO_NETWORK_RESPONSE = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public static final int EPS_FALLBACK_REASON_NO_NETWORK_TRIGGER = 1; // 0x1
field public static final String EXTRA_IS_UNKNOWN_CALL = "android.telephony.ims.feature.extra.IS_UNKNOWN_CALL";
field public static final String EXTRA_IS_USSD = "android.telephony.ims.feature.extra.IS_USSD";
+ field @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public static final int IMS_TRAFFIC_DIRECTION_INCOMING = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public static final int IMS_TRAFFIC_DIRECTION_OUTGOING = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public static final int IMS_TRAFFIC_TYPE_EMERGENCY = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public static final int IMS_TRAFFIC_TYPE_EMERGENCY_SMS = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public static final int IMS_TRAFFIC_TYPE_REGISTRATION = 5; // 0x5
+ field @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public static final int IMS_TRAFFIC_TYPE_SMS = 4; // 0x4
+ field @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public static final int IMS_TRAFFIC_TYPE_UT_XCAP = 6; // 0x6
+ field @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public static final int IMS_TRAFFIC_TYPE_VIDEO = 3; // 0x3
+ field @FlaggedApi("com.android.internal.telephony.flags.support_ims_mmtel_interface") public static final int IMS_TRAFFIC_TYPE_VOICE = 2; // 0x2
field public static final int PROCESS_CALL_CSFB = 1; // 0x1
field public static final int PROCESS_CALL_IMS = 0; // 0x0
}
@@ -18179,7 +18242,7 @@
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>);
}
- @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteManager {
+ public final class SatelliteManager {
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getAttachRestrictionReasonsForCarrier(int);
@@ -19001,10 +19064,10 @@
field public final android.content.pm.Signature[] signatures;
}
- public final class WebViewUpdateService {
- method public static android.webkit.WebViewProviderInfo[] getAllWebViewPackages();
- method public static String getCurrentWebViewPackageName();
- method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public static android.webkit.WebViewProviderInfo[] getValidWebViewPackages();
+ @Deprecated @FlaggedApi("android.webkit.update_service_ipc_wrapper") public final class WebViewUpdateService {
+ method @Deprecated public static android.webkit.WebViewProviderInfo[] getAllWebViewPackages();
+ method @Deprecated public static String getCurrentWebViewPackageName();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public static android.webkit.WebViewProviderInfo[] getValidWebViewPackages();
}
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 50cd267..ca98da7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1747,7 +1747,7 @@
printRow(pw, TWO_COUNT_COLUMNS, "Death Recipients:", binderDeathObjectCount,
"WebViews:", webviewInstanceCount);
- if (com.android.libcore.Flags.nativeMetrics()) {
+ if (com.android.libcore.readonly.Flags.nativeMetrics()) {
dumpMemInfoNativeAllocations(pw);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ca1662e6..c6c0395 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -11831,7 +11831,7 @@
if (length <= 0) continue;
try {
- totalLength += Math.addExact(totalLength, length);
+ totalLength = Math.addExact(totalLength, length);
segments.add(sanitizeSegment(segment, backgroundColor,
defaultProgressColor));
} catch (ArithmeticException e) {
@@ -11853,7 +11853,6 @@
for (Point point : mProgressPoints) {
final int position = point.getPosition();
if (position < 0 || position > totalLength) continue;
-
points.add(sanitizePoint(point, backgroundColor, defaultProgressColor));
}
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index 8944bb9..74cae07 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -50,29 +50,40 @@
* <p>**Building App Functions:**
*
* <p>Most developers should build app functions through the AppFunctions SDK. This SDK library
- * offers a more convenient and type-safe way to represent the inputs and outputs of an app
- * function, using custom data classes called "AppFunction Schemas".
+ * offers a more convenient and type-safe way to build app functions. The SDK provides predefined
+ * function schemas for common use cases and associated data classes for function parameters and
+ * return values. Apps only have to implement the provided interfaces. Internally, the SDK converts
+ * these data classes into {@link ExecuteAppFunctionRequest#getParameters()} and
+ * {@link ExecuteAppFunctionResponse#getResultDocument()}.
*
- * <p>The suggested way to build an app function is to use the AppFunctions SDK. The SDK provides
- * custom data classes (AppFunctions Schemas) and handles the conversion to the underlying {@link
- * android.app.appsearch.GenericDocument}/{@link android.os.Bundle} format used in {@link
- * ExecuteAppFunctionRequest} and {@link ExecuteAppFunctionResponse}.
- *
- * <p>**Discovering (Listing) App Functions:**
+ * <p>**Discovering App Functions:**
*
* <p>When there is a package change or the device starts up, the metadata of available functions is
- * indexed on-device by {@link AppSearchManager}. AppSearch stores the indexed information as a
- * {@code AppFunctionStaticMetadata} document. This allows other apps and the app itself to discover
- * these functions using the AppSearch search APIs. Visibility to this metadata document is based on
- * the packages that have visibility to the app providing the app functions.
+ * indexed on-device by {@link AppSearchManager}. AppSearch stores the indexed information as an
+ * {@code AppFunctionStaticMetadata} document. This document contains the {@code functionIdentifier}
+ * and the schema information that the app function implements. This allows other apps and the app
+ * itself to discover these functions using the AppSearch search APIs. Visibility to this metadata
+ * document is based on the packages that have visibility to the app providing the app functions.
+ * AppFunction SDK provides a convenient way to achieve this and is the preferred method.
*
* <p>**Executing App Functions:**
*
- * <p>Requests to execute a function are built using the {@link ExecuteAppFunctionRequest} class.
- * Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS} or {@code
- * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} permission to execute app functions from other
- * apps. An app has automatic visibility to its own functions and doesn't need these permissions to
- * call its own functions via {@code AppFunctionManager}.
+ * <p>To execute an app function, the caller app can retrieve the {@code functionIdentifier} from
+ * the {@code AppFunctionStaticMetadata} document and use it to build an
+ * {@link ExecuteAppFunctionRequest}. Then, invoke {@link #executeAppFunction} with the request
+ * to execute the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS}
+ * or {@code android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} permission to execute app
+ * functions from other apps. An app can always execute its own app functions and doesn't need these
+ * permissions. AppFunction SDK provides a convenient way to achieve this and is the preferred
+ * method.
+ *
+ * <p>**Example:**
+ *
+ * <p>An assistant app is trying to fulfill the user request "Save XYZ into my note". The assistant
+ * app should first list all available app functions as {@code AppFunctionStaticMetadata} documents
+ * from AppSearch. Then, it should identify an app function that implements the {@code CreateNote}
+ * schema. Finally, the assistant app can invoke {@link #executeAppFunction} with the {@code
+ * functionIdentifier} of the chosen function.
*/
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
@SystemService(Context.APP_FUNCTION_SERVICE)
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
index 4c5e8c1..41bb622 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
@@ -114,18 +114,14 @@
* <p>The bundle may have missing parameters. Developers are advised to implement defensive
* handling measures.
*
- * <p>Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be
- * obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This
- * metadata will contain enough information for the caller to resolve the required parameters
- * either using information from the metadata itself or using the AppFunction SDK for function
- * callers.
+ * @see AppFunctionManager on how to determine the expected parameters.
*/
@NonNull
public GenericDocument getParameters() {
return mParameters.getValue();
}
- /** Returns the additional data relevant to this function execution. */
+ /** Returns the additional metadata for this function execution request. */
@NonNull
public Bundle getExtras() {
return mExtras;
@@ -153,19 +149,31 @@
@NonNull
private GenericDocument mParameters = new GenericDocument.Builder<>("", "", "").build();
+ /**
+ * Creates a new instance of this builder class.
+ *
+ * @param targetPackageName The package name of the target app providing the app function to
+ * invoke.
+ * @param functionIdentifier The identifier used by the {@link AppFunctionService} from the
+ * target app to uniquely identify the function to be invoked.
+ */
public Builder(@NonNull String targetPackageName, @NonNull String functionIdentifier) {
mTargetPackageName = Objects.requireNonNull(targetPackageName);
mFunctionIdentifier = Objects.requireNonNull(functionIdentifier);
}
- /** Sets the additional data relevant to this function execution. */
+ /** Sets the additional metadata for this function execution request. */
@NonNull
public Builder setExtras(@NonNull Bundle extras) {
mExtras = Objects.requireNonNull(extras);
return this;
}
- /** Sets the function parameters. */
+ /**
+ * Sets the function parameters.
+ *
+ * @see #ExecuteAppFunctionRequest#getParameters()
+ */
@NonNull
public Builder setParameters(@NonNull GenericDocument parameters) {
Objects.requireNonNull(parameters);
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index c907ef1..83453a9 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -156,7 +156,7 @@
* Returns a successful response.
*
* @param resultDocument The return value of the executed function.
- * @param extras The additional metadata data relevant to this function execution response.
+ * @param extras The additional metadata for this function execution response.
*/
@NonNull
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
@@ -174,7 +174,7 @@
* Returns a failure response.
*
* @param resultCode The result code of the app function execution.
- * @param extras The additional metadata data relevant to this function execution response.
+ * @param extras The additional metadata for this function execution response.
* @param errorMessage The error message associated with the result, if any.
*/
@NonNull
@@ -216,13 +216,15 @@
* // Do something with the returnValue
* }
* </pre>
+ *
+ * @see AppFunctionManager on how to determine the expected function return.
*/
@NonNull
public GenericDocument getResultDocument() {
return mResultDocumentWrapper.getValue();
}
- /** Returns the extras of the app function execution. */
+ /** Returns the additional metadata for this function execution response. */
@NonNull
public Bundle getExtras() {
return mExtras;
diff --git a/core/java/android/app/backup/NotificationLoggingConstants.java b/core/java/android/app/backup/NotificationLoggingConstants.java
new file mode 100644
index 0000000..add4562
--- /dev/null
+++ b/core/java/android/app/backup/NotificationLoggingConstants.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.backup;
+
+/**
+ * @hide
+ */
+public class NotificationLoggingConstants {
+
+ // Key under which the payload blob is stored
+ public static final String KEY_NOTIFICATIONS = "notifications";
+
+ @BackupRestoreEventLogger.BackupRestoreDataType
+ public static final String DATA_TYPE_ZEN_CONFIG = KEY_NOTIFICATIONS + ":zen_config";
+ @BackupRestoreEventLogger.BackupRestoreDataType
+ public static final String DATA_TYPE_ZEN_RULES = KEY_NOTIFICATIONS + ":zen_rules";
+
+ @BackupRestoreEventLogger.BackupRestoreError
+ public static final String ERROR_XML_PARSING = KEY_NOTIFICATIONS + ":invalid_xml_parsing";
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 1d4c18f..0fc4291 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -66,6 +66,16 @@
}
flag {
+ name: "modes_multiuser"
+ namespace: "systemui"
+ description: "Fixes for modes (and DND/Zen in general) when callers are not the current user"
+ bug: "323163267"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "api_tvextender"
is_exported: true
namespace: "systemui"
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index ed965b3..6db7dfe 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -78,7 +78,8 @@
/**
* Applications can use OverlayManager to create overlays to overlay on itself resources. The
- * overlay target is itself and the work range is only in caller application.
+ * overlay target is itself, or the Android package, and the work range is only in caller
+ * application.
*
* <p>In {@link android.content.Context#getSystemService(String)}, it crashes because of {@link
* java.lang.NullPointerException} if the parameter is OverlayManager. if the self-targeting is
@@ -401,7 +402,7 @@
}
/**
- * Get the related information of overlays for {@code targetPackageName}.
+ * Get the related information of self-targeting overlays for {@code targetPackageName}.
*
* @param targetPackageName the target package name
* @return a list of overlay information
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index becd0ea..87b2e93 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -209,6 +209,7 @@
*/
public static final class Builder {
private final List<Request> mRequests = new ArrayList<>();
+ private boolean mSelfTargeting = false;
/**
* Request that an overlay package be enabled and change its loading
@@ -246,6 +247,18 @@
}
/**
+ * Request that an overlay package be self-targeting. Self-targeting overlays enable
+ * applications to overlay on itself resources. The overlay target is itself, or the Android
+ * package, and the work range is only in caller application.
+ * @param selfTargeting whether the overlay is self-targeting, the default is false.
+ * @hide
+ */
+ public Builder setSelfTargeting(boolean selfTargeting) {
+ mSelfTargeting = selfTargeting;
+ return this;
+ }
+
+ /**
* Registers the fabricated overlay with the overlay manager so it can be enabled and
* disabled for any user.
*
@@ -286,7 +299,7 @@
*/
@NonNull
public OverlayManagerTransaction build() {
- return new OverlayManagerTransaction(mRequests, false /* selfTargeting */);
+ return new OverlayManagerTransaction(mRequests, mSelfTargeting);
}
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 6fd4d01..347bebd 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -75,7 +75,10 @@
private static final String TAG = "AssetManager";
private static final boolean DEBUG_REFS = false;
- private static final String FRAMEWORK_APK_PATH = getFrameworkApkPath();
+ /**
+ * @hide
+ */
+ public static final String FRAMEWORK_APK_PATH = getFrameworkApkPath();
private static final String FRAMEWORK_APK_PATH_DEVICE = "/system/framework/framework-res.apk";
private static final String FRAMEWORK_APK_PATH_RAVENWOOD = "ravenwood-data/framework-res.apk";
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index b097bc0..830b7e0 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -90,8 +90,6 @@
throws IOException {
Objects.requireNonNull(overlayInfo);
Preconditions.checkArgument(overlayInfo.isFabricated(), "Not accepted overlay");
- Preconditions.checkStringNotEmpty(
- overlayInfo.getTargetOverlayableName(), "Without overlayable name");
final String overlayName =
OverlayManagerImpl.checkOverlayNameValid(overlayInfo.getOverlayName());
final String path =
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index d77e628..75c7e26 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -241,7 +241,8 @@
NoPreloadHolder.DEBUG_SQL_STATEMENTS, NoPreloadHolder.DEBUG_SQL_TIME,
mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount);
} catch (SQLiteCantOpenDatabaseException e) {
- final StringBuilder message = new StringBuilder(e.getMessage())
+ final StringBuilder message = new StringBuilder("Cannot open database ")
+ .append("[").append(e.getMessage()).append("]")
.append(" '").append(file).append("'")
.append(" with flags 0x")
.append(Integer.toHexString(mConfiguration.openFlags));
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index e3fdd26..3ff21d8 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -22,7 +22,6 @@
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
-import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT;
import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
@@ -200,7 +199,6 @@
* @param logoRes A drawable resource of the logo that will be shown on the prompt.
* @return This builder.
*/
- @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
@NonNull
public BiometricPrompt.Builder setLogoRes(@DrawableRes int logoRes) {
@@ -226,7 +224,6 @@
* @param logoBitmap A bitmap drawable of the logo that will be shown on the prompt.
* @return This builder.
*/
- @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
@NonNull
public BiometricPrompt.Builder setLogoBitmap(@NonNull Bitmap logoBitmap) {
@@ -250,7 +247,6 @@
* @return This builder.
* @throws IllegalArgumentException If logo description is null.
*/
- @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
@NonNull
public BiometricPrompt.Builder setLogoDescription(@NonNull String logoDescription) {
@@ -342,7 +338,6 @@
* @param view The customized view information.
* @return This builder.
*/
- @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@NonNull
public BiometricPrompt.Builder setContentView(@NonNull PromptContentView view) {
mPromptInfo.setContentView(view);
@@ -851,7 +846,6 @@
*
* @return The drawable resource of the logo, or 0 if the prompt has no logo resource set.
*/
- @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
@DrawableRes
public int getLogoRes() {
@@ -864,7 +858,6 @@
*
* @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set.
*/
- @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
@Nullable
public Bitmap getLogoBitmap() {
@@ -879,7 +872,6 @@
* @return The logo description of the prompt, or null if the prompt has no logo description
* set.
*/
- @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
@Nullable
public String getLogoDescription() {
@@ -939,7 +931,6 @@
*
* @return The content view for the prompt, or null if the prompt has no content view.
*/
- @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@Nullable
public PromptContentView getContentView() {
return mPromptInfo.getContentView();
diff --git a/core/java/android/hardware/biometrics/PromptContentItem.java b/core/java/android/hardware/biometrics/PromptContentItem.java
index c47b37a..0c41782 100644
--- a/core/java/android/hardware/biometrics/PromptContentItem.java
+++ b/core/java/android/hardware/biometrics/PromptContentItem.java
@@ -16,14 +16,9 @@
package android.hardware.biometrics;
-import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-
-import android.annotation.FlaggedApi;
-
/**
* An item shown on {@link PromptContentView}.
*/
-@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public interface PromptContentItem {
}
diff --git a/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
index 25e5cca..a026498 100644
--- a/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
@@ -16,9 +16,6 @@
package android.hardware.biometrics;
-import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
@@ -26,7 +23,6 @@
/**
* A list item with bulleted text shown on {@link PromptVerticalListContentView}.
*/
-@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public final class PromptContentItemBulletedText implements PromptContentItemParcelable {
private final String mText;
diff --git a/core/java/android/hardware/biometrics/PromptContentItemParcelable.java b/core/java/android/hardware/biometrics/PromptContentItemParcelable.java
index 668912cf..1860aae 100644
--- a/core/java/android/hardware/biometrics/PromptContentItemParcelable.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemParcelable.java
@@ -16,15 +16,11 @@
package android.hardware.biometrics;
-import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-
-import android.annotation.FlaggedApi;
import android.os.Parcelable;
/**
* A parcelable {@link PromptContentItem}.
*/
-@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
sealed interface PromptContentItemParcelable extends PromptContentItem, Parcelable
permits PromptContentItemPlainText, PromptContentItemBulletedText {
}
diff --git a/core/java/android/hardware/biometrics/PromptContentItemPlainText.java b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
index 7919256..a5e6120 100644
--- a/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
@@ -16,9 +16,6 @@
package android.hardware.biometrics;
-import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
@@ -26,7 +23,6 @@
/**
* A list item with plain text shown on {@link PromptVerticalListContentView}.
*/
-@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public final class PromptContentItemPlainText implements PromptContentItemParcelable {
private final String mText;
diff --git a/core/java/android/hardware/biometrics/PromptContentView.java b/core/java/android/hardware/biometrics/PromptContentView.java
index ff9313e..0836d72 100644
--- a/core/java/android/hardware/biometrics/PromptContentView.java
+++ b/core/java/android/hardware/biometrics/PromptContentView.java
@@ -16,13 +16,8 @@
package android.hardware.biometrics;
-import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-
-import android.annotation.FlaggedApi;
-
/**
* Contains the information of the template of content view for Biometric Prompt.
*/
-@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public interface PromptContentView {
}
diff --git a/core/java/android/hardware/biometrics/PromptContentViewParcelable.java b/core/java/android/hardware/biometrics/PromptContentViewParcelable.java
index b5982d4..6e03563 100644
--- a/core/java/android/hardware/biometrics/PromptContentViewParcelable.java
+++ b/core/java/android/hardware/biometrics/PromptContentViewParcelable.java
@@ -16,15 +16,11 @@
package android.hardware.biometrics;
-import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-
-import android.annotation.FlaggedApi;
import android.os.Parcelable;
/**
* A parcelable {@link PromptContentView}.
*/
-@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
sealed interface PromptContentViewParcelable extends PromptContentView, Parcelable
permits PromptVerticalListContentView, PromptContentViewWithMoreOptionsButton {
}
diff --git a/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java b/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java
index 4b9d5ce..aa0ce58 100644
--- a/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java
+++ b/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java
@@ -17,10 +17,8 @@
package android.hardware.biometrics;
import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
-import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
import android.annotation.CallbackExecutor;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -61,7 +59,6 @@
* .build();
* </pre>
*/
-@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public final class PromptContentViewWithMoreOptionsButton implements PromptContentViewParcelable {
private static final String TAG = "PromptContentViewWithMoreOptionsButton";
@VisibleForTesting
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index e23ffeb..3e304e4 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -217,7 +217,7 @@
* Returns if the PromptContentViewWithMoreOptionsButton is set.
*/
public boolean isContentViewMoreOptionsButtonUsed() {
- return Flags.customBiometricPrompt() && mContentView != null
+ return mContentView != null
&& mContentView instanceof PromptContentViewWithMoreOptionsButton;
}
diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
index 86006f8..2a521d1 100644
--- a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
+++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
@@ -16,9 +16,6 @@
package android.hardware.biometrics;
-import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
@@ -48,7 +45,6 @@
* .build();
* </pre>
*/
-@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public final class PromptVerticalListContentView implements PromptContentViewParcelable {
private static final String TAG = "PromptVerticalListContentView";
@VisibleForTesting
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 52a4898..2d29900 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -54,3 +54,10 @@
description: "This flag is for API changes related to Identity Check"
bug: "373424727"
}
+
+flag {
+ name: "private_space_bp"
+ namespace: "biometrics_framework"
+ description: "Feature flag for biometric prompt improvements in private space"
+ bug: "365554098"
+}
diff --git a/core/java/android/hardware/camera2/MultiResolutionImageReader.java b/core/java/android/hardware/camera2/MultiResolutionImageReader.java
index 116928b..8ede7f3 100644
--- a/core/java/android/hardware/camera2/MultiResolutionImageReader.java
+++ b/core/java/android/hardware/camera2/MultiResolutionImageReader.java
@@ -224,9 +224,8 @@
* @see
* android.hardware.camera2.params.MultiResolutionStreamConfigurationMap
*
- * @hide
*/
- @FlaggedApi(Flags.FLAG_MULTIRESOLUTION_IMAGEREADER_USAGE_CONFIG)
+ @FlaggedApi(Flags.FLAG_MULTIRESOLUTION_IMAGEREADER_USAGE_PUBLIC)
public MultiResolutionImageReader(
@NonNull Collection<MultiResolutionStreamInfo> streams,
@Format int format,
diff --git a/core/java/android/hardware/input/AidlInputGestureData.aidl b/core/java/android/hardware/input/AidlInputGestureData.aidl
new file mode 100644
index 0000000..137f672
--- /dev/null
+++ b/core/java/android/hardware/input/AidlInputGestureData.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.input;
+
+/** @hide */
+@JavaDerive(equals=true)
+parcelable AidlInputGestureData {
+ int keycode;
+ int modifierState;
+ int gestureType;
+
+ // App launch parameters: Only set if gestureType is KEY_GESTURE_TYPE_LAUNCH_APPLICATION
+ String appLaunchCategory;
+ String appLaunchRole;
+ String appLaunchPackageName;
+ String appLaunchClassName;
+}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 102f56e..bce9518 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -17,6 +17,7 @@
package android.hardware.input;
import android.graphics.Rect;
+import android.hardware.input.AidlInputGestureData;
import android.hardware.input.HostUsiVersion;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.KeyboardLayout;
@@ -261,4 +262,21 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.MANAGE_KEY_GESTURES)")
void unregisterKeyGestureHandler(IKeyGestureHandler handler);
+
+ @PermissionManuallyEnforced
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.MANAGE_KEY_GESTURES)")
+ int addCustomInputGesture(in AidlInputGestureData data);
+
+ @PermissionManuallyEnforced
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.MANAGE_KEY_GESTURES)")
+ int removeCustomInputGesture(in AidlInputGestureData data);
+
+ @PermissionManuallyEnforced
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.MANAGE_KEY_GESTURES)")
+ void removeAllCustomInputGestures();
+
+ AidlInputGestureData[] getCustomInputGestures();
}
diff --git a/core/java/android/hardware/input/InputGestureData.java b/core/java/android/hardware/input/InputGestureData.java
new file mode 100644
index 0000000..5ab73ce
--- /dev/null
+++ b/core/java/android/hardware/input/InputGestureData.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.input;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.KeyEvent;
+
+import java.util.Objects;
+
+/**
+ * Data class to store input gesture data.
+ *
+ * <p>
+ * All input gestures are of type Trigger -> Action(Key gesture type, app data). And currently types
+ * of triggers supported are:
+ * - KeyTrigger (Keycode + modifierState)
+ * - TODO(b/365064144): Add Touchpad gesture based trigger
+ * </p>
+ * @hide
+ */
+public final class InputGestureData {
+
+ @NonNull
+ private final AidlInputGestureData mInputGestureData;
+
+ public InputGestureData(AidlInputGestureData inputGestureData) {
+ this.mInputGestureData = inputGestureData;
+ validate();
+ }
+
+ /** Returns the trigger information for this input gesture */
+ public Trigger getTrigger() {
+ if (mInputGestureData.keycode != KeyEvent.KEYCODE_UNKNOWN) {
+ return new KeyTrigger(mInputGestureData.keycode, mInputGestureData.modifierState);
+ }
+ throw new RuntimeException("InputGestureData is corrupted, invalid trigger type!");
+ }
+
+ /** Returns the action to perform for this input gesture */
+ public Action getAction() {
+ return new Action(mInputGestureData.gestureType, getAppLaunchData());
+ }
+
+ private void validate() {
+ Trigger trigger = getTrigger();
+ Action action = getAction();
+ if (trigger == null) {
+ throw new IllegalArgumentException("No trigger found");
+ }
+ if (action.keyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
+ throw new IllegalArgumentException("No system action found");
+ }
+ if (action.keyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION
+ && action.appLaunchData == null) {
+ throw new IllegalArgumentException(
+ "No app launch data for system action launch application");
+ }
+ }
+
+ public AidlInputGestureData getAidlData() {
+ return mInputGestureData;
+ }
+
+ @Nullable
+ private AppLaunchData getAppLaunchData() {
+ if (mInputGestureData.gestureType != KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION) {
+ return null;
+ }
+ return AppLaunchData.createLaunchData(mInputGestureData.appLaunchCategory,
+ mInputGestureData.appLaunchRole, mInputGestureData.appLaunchPackageName,
+ mInputGestureData.appLaunchClassName);
+ }
+
+ /** Builder class for creating {@link InputGestureData} */
+ public static class Builder {
+ @Nullable
+ private Trigger mTrigger = null;
+ private int mKeyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED;
+ @Nullable
+ private AppLaunchData mAppLaunchData = null;
+
+ /** Set input gesture trigger data for key based gestures */
+ public Builder setTrigger(Trigger trigger) {
+ mTrigger = trigger;
+ return this;
+ }
+
+ /** Set input gesture system action */
+ public Builder setKeyGestureType(@KeyGestureEvent.KeyGestureType int keyGestureType) {
+ mKeyGestureType = keyGestureType;
+ return this;
+ }
+
+ /** Set input gesture system action as launching a target app */
+ public Builder setAppLaunchData(@NonNull AppLaunchData appLaunchData) {
+ mKeyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION;
+ mAppLaunchData = appLaunchData;
+ return this;
+ }
+
+ /** Creates {@link android.hardware.input.InputGestureData} based on data provided */
+ public InputGestureData build() throws IllegalArgumentException {
+ if (mTrigger == null) {
+ throw new IllegalArgumentException("No trigger found");
+ }
+ if (mKeyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
+ throw new IllegalArgumentException("No system action found");
+ }
+ if (mKeyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION
+ && mAppLaunchData == null) {
+ throw new IllegalArgumentException(
+ "No app launch data for system action launch application");
+ }
+ AidlInputGestureData data = new AidlInputGestureData();
+ if (mTrigger instanceof KeyTrigger keyTrigger) {
+ data.keycode = keyTrigger.getKeycode();
+ data.modifierState = keyTrigger.getModifierState();
+ } else {
+ throw new IllegalArgumentException("Invalid trigger type!");
+ }
+ data.gestureType = mKeyGestureType;
+ if (mAppLaunchData != null) {
+ if (mAppLaunchData instanceof AppLaunchData.CategoryData categoryData) {
+ data.appLaunchCategory = categoryData.getCategory();
+ } else if (mAppLaunchData instanceof AppLaunchData.RoleData roleData) {
+ data.appLaunchRole = roleData.getRole();
+ } else if (mAppLaunchData instanceof AppLaunchData.ComponentData componentData) {
+ data.appLaunchPackageName = componentData.getPackageName();
+ data.appLaunchClassName = componentData.getClassName();
+ } else {
+ throw new IllegalArgumentException("AppLaunchData type is invalid!");
+ }
+ }
+ return new InputGestureData(data);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "InputGestureData { "
+ + "trigger = " + getTrigger()
+ + ", action = " + getAction()
+ + " }";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ InputGestureData that = (InputGestureData) o;
+ return mInputGestureData.keycode == that.mInputGestureData.keycode
+ && mInputGestureData.modifierState == that.mInputGestureData.modifierState
+ && mInputGestureData.gestureType == that.mInputGestureData.gestureType
+ && Objects.equals(mInputGestureData.appLaunchCategory, that.mInputGestureData.appLaunchCategory)
+ && Objects.equals(mInputGestureData.appLaunchRole, that.mInputGestureData.appLaunchRole)
+ && Objects.equals(mInputGestureData.appLaunchPackageName, that.mInputGestureData.appLaunchPackageName)
+ && Objects.equals(mInputGestureData.appLaunchPackageName, that.mInputGestureData.appLaunchPackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ int _hash = 1;
+ _hash = 31 * _hash + mInputGestureData.keycode;
+ _hash = 31 * _hash + mInputGestureData.modifierState;
+ _hash = 31 * _hash + mInputGestureData.gestureType;
+ _hash = 31 * _hash + (mInputGestureData.appLaunchCategory != null
+ ? mInputGestureData.appLaunchCategory.hashCode() : 0);
+ _hash = 31 * _hash + (mInputGestureData.appLaunchRole != null
+ ? mInputGestureData.appLaunchRole.hashCode() : 0);
+ _hash = 31 * _hash + (mInputGestureData.appLaunchPackageName != null
+ ? mInputGestureData.appLaunchPackageName.hashCode() : 0);
+ _hash = 31 * _hash + (mInputGestureData.appLaunchPackageName != null
+ ? mInputGestureData.appLaunchPackageName.hashCode() : 0);
+ return _hash;
+ }
+
+ public interface Trigger {
+ }
+
+ /** Creates a input gesture trigger based on a key press */
+ public static Trigger createKeyTrigger(int keycode, int modifierState) {
+ return new KeyTrigger(keycode, modifierState);
+ }
+
+ /** Key based input gesture trigger */
+ public static class KeyTrigger implements Trigger {
+ private static final int SHORTCUT_META_MASK =
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON
+ | KeyEvent.META_SHIFT_ON;
+ private final int mKeycode;
+ private final int mModifierState;
+
+ private KeyTrigger(int keycode, int modifierState) {
+ if (keycode <= KeyEvent.KEYCODE_UNKNOWN || keycode > KeyEvent.getMaxKeyCode()) {
+ throw new IllegalArgumentException("Invalid keycode = " + keycode);
+ }
+ mKeycode = keycode;
+ mModifierState = modifierState;
+ }
+
+ public int getKeycode() {
+ return mKeycode;
+ }
+
+ public int getModifierState() {
+ return mModifierState;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof KeyTrigger that)) return false;
+ return mKeycode == that.mKeycode && mModifierState == that.mModifierState;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mKeycode, mModifierState);
+ }
+
+ @Override
+ public String toString() {
+ return "KeyTrigger{" +
+ "mKeycode=" + KeyEvent.keyCodeToString(mKeycode) +
+ ", mModifierState=" + mModifierState +
+ '}';
+ }
+ }
+
+ /** Data for action to perform when input gesture is triggered */
+ public record Action(@KeyGestureEvent.KeyGestureType int keyGestureType,
+ @Nullable AppLaunchData appLaunchData) {
+ }
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 22728f7..876ba10 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -18,6 +18,7 @@
import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
import static com.android.input.flags.Flags.FLAG_DEVICE_ASSOCIATIONS;
+import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
import static com.android.hardware.input.Flags.keyboardGlyphMap;
@@ -258,6 +259,52 @@
}
/**
+ * Custom input gesture error: Input gesture already exists
+ *
+ * @hide
+ */
+ public static final int CUSTOM_INPUT_GESTURE_RESULT_SUCCESS = 1;
+
+ /**
+ * Custom input gesture error: Input gesture already exists
+ *
+ * @hide
+ */
+ public static final int CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS = 2;
+
+ /**
+ * Custom input gesture error: Input gesture does not exist
+ *
+ * @hide
+ */
+ public static final int CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST = 3;
+
+ /**
+ * Custom input gesture error: Input gesture is reserved for system action
+ *
+ * @hide
+ */
+ public static final int CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE = 4;
+
+ /**
+ * Custom input gesture error: Failure error code for all other errors/warnings
+ *
+ * @hide
+ */
+ public static final int CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER = 5;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "CUSTOM_INPUT_GESTURE_RESULT_" }, value = {
+ CUSTOM_INPUT_GESTURE_RESULT_SUCCESS,
+ CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS,
+ CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST,
+ CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE,
+ CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER,
+ })
+ public @interface CustomInputGestureResult {}
+
+ /**
* Switch State: Unknown.
*
* The system has yet to report a valid value for the switch.
@@ -1432,6 +1479,84 @@
mGlobal.unregisterKeyGestureEventHandler(handler);
}
+ /** Adds a new custom input gesture
+ *
+ * @param inputGestureData gesture data to add as custom gesture
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
+ @CustomInputGestureResult
+ public int addCustomInputGesture(@NonNull InputGestureData inputGestureData) {
+ if (!enableCustomizableInputGestures()) {
+ return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER;
+ }
+ try {
+ return mIm.addCustomInputGesture(inputGestureData.getAidlData());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER;
+ }
+
+ /** Removes an existing custom gesture
+ *
+ * <p> NOTE: Should not be used to remove system gestures. This API is only to be used to
+ * remove gestures added using {@link #addCustomInputGesture(InputGestureData)}
+ *
+ * @param inputGestureData gesture data for the existing custom gesture to remove
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
+ @CustomInputGestureResult
+ public int removeCustomInputGesture(@NonNull InputGestureData inputGestureData) {
+ if (!enableCustomizableInputGestures()) {
+ return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER;
+ }
+ try {
+ return mIm.removeCustomInputGesture(inputGestureData.getAidlData());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER;
+ }
+
+ /** Removes all custom input gestures
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
+ public void removeAllCustomInputGestures() {
+ if (!enableCustomizableInputGestures()) {
+ return;
+ }
+ try {
+ mIm.removeAllCustomInputGestures();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /** Get all custom input gestures
+ *
+ * @hide
+ */
+ public List<InputGestureData> getCustomInputGestures() {
+ List<InputGestureData> result = new ArrayList<>();
+ if (!enableCustomizableInputGestures()) {
+ return result;
+ }
+ try {
+ for (AidlInputGestureData data : mIm.getCustomInputGestures()) {
+ result.add(new InputGestureData(data));
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return result;
+ }
+
/**
* A callback used to be notified about battery state changes for an input device. The
* {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
diff --git a/core/java/android/hardware/input/KeyGlyphMap.java b/core/java/android/hardware/input/KeyGlyphMap.java
index 6a16502..f82d1cf 100644
--- a/core/java/android/hardware/input/KeyGlyphMap.java
+++ b/core/java/android/hardware/input/KeyGlyphMap.java
@@ -34,6 +34,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
/**
* This class provides access to device specific key glyphs, modifier glyphs and device specific
@@ -107,7 +108,54 @@
/**
* Defines a key combination that includes a keycode and modifier state.
*/
- public record KeyCombination(int modifierState, int keycode) {}
+ public static class KeyCombination implements Parcelable {
+ private final int mModifierState;
+ private final int mKeycode;
+
+ public KeyCombination(int modifierState, int keycode) {
+ this.mModifierState = modifierState;
+ this.mKeycode = keycode;
+ }
+
+ public KeyCombination(Parcel in) {
+ this(in.readInt(), in.readInt());
+ }
+
+ public static final Creator<KeyCombination> CREATOR = new Creator<>() {
+ @Override
+ public KeyCombination createFromParcel(Parcel in) {
+ return new KeyCombination(in);
+ }
+
+ @Override
+ public KeyCombination[] newArray(int size) {
+ return new KeyCombination[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
+ dest.writeInt(mModifierState);
+ dest.writeInt(mKeycode);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof KeyCombination that)) return false;
+ return mModifierState == that.mModifierState && mKeycode == that.mKeycode;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mModifierState, mKeycode);
+ }
+ }
/**
* Returns keycodes generated from the functional row defined for the keyboard.
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 1206855..e6982b9 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -143,6 +143,13 @@
}
flag {
+ name: "enable_customizable_input_gestures"
+ namespace: "input"
+ description: "Enables keyboard shortcut customization support"
+ bug: "365064144"
+}
+
+flag {
name: "override_power_key_behavior_in_focused_window"
namespace: "input_native"
description: "Allows privileged focused windows to capture power key events."
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 41f344a..92608d0 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -21,6 +21,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.NonNull;
@@ -37,6 +38,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.hardware.usb.flags.Flags;
import android.hardware.usb.gadget.GadgetFunction;
import android.hardware.usb.gadget.UsbSpeed;
import android.os.Binder;
@@ -509,7 +511,8 @@
*
* @hide
*/
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(Flags.FLAG_EXPOSE_USB_SPEED_SYSTEM_API)
+ @SystemApi
public static final int USB_DATA_TRANSFER_RATE_UNKNOWN = -1;
/**
@@ -517,7 +520,8 @@
*
* @hide
*/
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(Flags.FLAG_EXPOSE_USB_SPEED_SYSTEM_API)
+ @SystemApi
public static final int USB_DATA_TRANSFER_RATE_LOW_SPEED = 2;
/**
@@ -525,7 +529,8 @@
*
* @hide
*/
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(Flags.FLAG_EXPOSE_USB_SPEED_SYSTEM_API)
+ @SystemApi
public static final int USB_DATA_TRANSFER_RATE_FULL_SPEED = 12;
/**
@@ -533,7 +538,8 @@
*
* @hide
*/
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(Flags.FLAG_EXPOSE_USB_SPEED_SYSTEM_API)
+ @SystemApi
public static final int USB_DATA_TRANSFER_RATE_HIGH_SPEED = 480;
/**
@@ -541,7 +547,8 @@
*
* @hide
*/
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(Flags.FLAG_EXPOSE_USB_SPEED_SYSTEM_API)
+ @SystemApi
public static final int USB_DATA_TRANSFER_RATE_5G = 5 * 1024;
/**
@@ -549,7 +556,8 @@
*
* @hide
*/
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(Flags.FLAG_EXPOSE_USB_SPEED_SYSTEM_API)
+ @SystemApi
public static final int USB_DATA_TRANSFER_RATE_10G = 10 * 1024;
/**
@@ -557,7 +565,8 @@
*
* @hide
*/
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(Flags.FLAG_EXPOSE_USB_SPEED_SYSTEM_API)
+ @SystemApi
public static final int USB_DATA_TRANSFER_RATE_20G = 20 * 1024;
/**
@@ -565,7 +574,8 @@
*
* @hide
*/
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(Flags.FLAG_EXPOSE_USB_SPEED_SYSTEM_API)
+ @SystemApi
public static final int USB_DATA_TRANSFER_RATE_40G = 40 * 1024;
/**
@@ -1292,7 +1302,8 @@
*
* @hide
*/
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(Flags.FLAG_EXPOSE_USB_SPEED_SYSTEM_API)
+ @SystemApi
@RequiresPermission(Manifest.permission.MANAGE_USB)
public int getUsbBandwidthMbps() {
int usbSpeed;
diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
index 40e5ffb..3b7a9e9 100644
--- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -24,3 +24,10 @@
description: "Feature flag to enable interface name as a parameter for device filter"
bug: "312828160"
}
+
+flag {
+ name: "expose_usb_speed_system_api"
+ namespace: "usb"
+ description: "Feature flag to enable exposing usb speed system api"
+ bug: "373653182"
+}
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index 5b30624..1adefe5 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -10,8 +10,26 @@
}
flag {
+ name: "mainline_vcn_module_api"
+ namespace: "vcn"
+ description: "Expose APIs from VCN for mainline migration"
+ is_exported: true
+ bug: "376339506"
+}
+
+flag {
name: "safe_mode_timeout_config"
namespace: "vcn"
description: "Feature flag for adjustable safe mode timeout"
bug: "317406085"
+}
+
+flag {
+ name: "fix_config_garbage_collection"
+ namespace: "vcn"
+ description: "Handle race condition in subscription change"
+ bug: "370862489"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
new file mode 100644
index 0000000..3e5ac6f
--- /dev/null
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -0,0 +1,2508 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Process;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+import android.ravenwood.annotation.RavenwoodRedirect;
+import android.ravenwood.annotation.RavenwoodRedirectionClass;
+import android.util.Log;
+import android.util.Printer;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Low-level class holding the list of messages to be dispatched by a
+ * {@link Looper}. Messages are not added directly to a MessageQueue,
+ * but rather through {@link Handler} objects associated with the Looper.
+ *
+ * <p>You can retrieve the MessageQueue for the current thread with
+ * {@link Looper#myQueue() Looper.myQueue()}.
+ */
+@RavenwoodKeepWholeClass
+@RavenwoodRedirectionClass("MessageQueue_host")
+public final class MessageQueue {
+ private static final String TAG_L = "LegacyMessageQueue";
+ private static final String TAG_C = "ConcurrentMessageQueue";
+ private static final boolean DEBUG = false;
+ private static final boolean TRACE = false;
+
+ // True if the message queue can be quit.
+ @UnsupportedAppUsage
+ private final boolean mQuitAllowed;
+
+ @UnsupportedAppUsage
+ @SuppressWarnings("unused")
+ private long mPtr; // used by native code
+
+ @UnsupportedAppUsage
+ Message mMessages;
+ private Message mLast;
+ @UnsupportedAppUsage
+ private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
+ private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
+ private IdleHandler[] mPendingIdleHandlers;
+ private boolean mLegacyQuitting;
+
+ // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
+ private boolean mLegacyBlocked;
+
+ // Tracks the number of async message. We use this in enqueueMessage() to avoid searching the
+ // queue for async messages when inserting a message at the tail.
+ private int mLegacyAsyncMessageCount;
+
+ // The next barrier token.
+ // Barriers are indicated by messages with a null target whose arg1 field carries the token.
+ @UnsupportedAppUsage
+ private int mLegacyNextBarrierToken;
+
+ /*
+ * Select between two implementations of message queue. The legacy implementation is used
+ * by default as it provides maximum compatibility with applications and tests that
+ * reach into MessageQueue via the mMessages field. The concurrent implemmentation is used for
+ * system processes and provides a higher level of concurrency and higher enqueue throughput
+ * than the legacy implementation.
+ */
+ private static boolean sForceConcurrent = false;
+
+ @RavenwoodRedirect
+ private native static long nativeInit();
+ @RavenwoodRedirect
+ private native static void nativeDestroy(long ptr);
+ @UnsupportedAppUsage
+ @RavenwoodRedirect
+ private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
+ @RavenwoodRedirect
+ private native static void nativeWake(long ptr);
+ @RavenwoodRedirect
+ private native static boolean nativeIsPolling(long ptr);
+ @RavenwoodRedirect
+ private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
+
+ MessageQueue(boolean quitAllowed) {
+ if (!sForceConcurrent) {
+ sForceConcurrent = Process.myUid() < Process.FIRST_APPLICATION_UID;
+ }
+ mQuitAllowed = quitAllowed;
+ mPtr = nativeInit();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ // Disposes of the underlying message queue.
+ // Must only be called on the looper thread or the finalizer.
+ private void dispose() {
+ if (mPtr != 0) {
+ nativeDestroy(mPtr);
+ mPtr = 0;
+ }
+ }
+
+ private class MatchDeliverableMessages extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.when <= when) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchDeliverableMessages mMatchDeliverableMessages =
+ new MatchDeliverableMessages();
+ /**
+ * Returns true if the looper has no pending messages which are due to be processed.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @return True if the looper is idle.
+ */
+ public boolean isIdle() {
+ if (sForceConcurrent) {
+ final long now = SystemClock.uptimeMillis();
+
+ if (stackHasMessages(null, 0, null, null, now, mMatchDeliverableMessages, false)) {
+ return false;
+ }
+
+ MessageNode msgNode = null;
+ MessageNode asyncMsgNode = null;
+
+ if (!mPriorityQueue.isEmpty()) {
+ try {
+ msgNode = mPriorityQueue.first();
+ } catch (NoSuchElementException e) { }
+ }
+
+ if (!mAsyncPriorityQueue.isEmpty()) {
+ try {
+ asyncMsgNode = mAsyncPriorityQueue.first();
+ } catch (NoSuchElementException e) { }
+ }
+
+ if ((msgNode != null && msgNode.getWhen() <= now)
+ || (asyncMsgNode != null && asyncMsgNode.getWhen() <= now)) {
+ return false;
+ }
+
+ return true;
+ } else {
+ synchronized (this) {
+ final long now = SystemClock.uptimeMillis();
+ return mMessages == null || now < mMessages.when;
+ }
+ }
+ }
+
+ /**
+ * Add a new {@link IdleHandler} to this message queue. This may be
+ * removed automatically for you by returning false from
+ * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
+ * invoked, or explicitly removing it with {@link #removeIdleHandler}.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @param handler The IdleHandler to be added.
+ */
+ public void addIdleHandler(@NonNull IdleHandler handler) {
+ if (handler == null) {
+ throw new NullPointerException("Can't add a null IdleHandler");
+ }
+ if (sForceConcurrent) {
+ synchronized (mIdleHandlersLock) {
+ mIdleHandlers.add(handler);
+ }
+ } else {
+ synchronized (this) {
+ mIdleHandlers.add(handler);
+ }
+ }
+ }
+
+ /**
+ * Remove an {@link IdleHandler} from the queue that was previously added
+ * with {@link #addIdleHandler}. If the given object is not currently
+ * in the idle list, nothing is done.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @param handler The IdleHandler to be removed.
+ */
+ public void removeIdleHandler(@NonNull IdleHandler handler) {
+ if (sForceConcurrent) {
+ synchronized (mIdleHandlersLock) {
+ mIdleHandlers.remove(handler);
+ }
+ } else {
+ synchronized (this) {
+ mIdleHandlers.remove(handler);
+ }
+ }
+ }
+
+ /**
+ * Returns whether this looper's thread is currently polling for more work to do.
+ * This is a good signal that the loop is still alive rather than being stuck
+ * handling a callback. Note that this method is intrinsically racy, since the
+ * state of the loop can change before you get the result back.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @return True if the looper is currently polling for events.
+ * @hide
+ */
+ public boolean isPolling() {
+ if (sForceConcurrent) {
+ // If the loop is quitting then it must not be idling.
+ // We can assume mPtr != 0 when sQuitting is false.
+ return !((boolean) sQuitting.getVolatile(this)) && nativeIsPolling(mPtr);
+ } else {
+ synchronized (this) {
+ return isPollingLocked();
+ }
+ }
+ }
+
+ private boolean isPollingLocked() {
+ // If the loop is quitting then it must not be idling.
+ // We can assume mPtr != 0 when mLegacyQuitting is false.
+ return !mLegacyQuitting && nativeIsPolling(mPtr);
+ }
+
+ /**
+ * Adds a file descriptor listener to receive notification when file descriptor
+ * related events occur.
+ * <p>
+ * If the file descriptor has already been registered, the specified events
+ * and listener will replace any that were previously associated with it.
+ * It is not possible to set more than one listener per file descriptor.
+ * </p><p>
+ * It is important to always unregister the listener when the file descriptor
+ * is no longer of use.
+ * </p>
+ *
+ * @param fd The file descriptor for which a listener will be registered.
+ * @param events The set of events to receive: a combination of the
+ * {@link OnFileDescriptorEventListener#EVENT_INPUT},
+ * {@link OnFileDescriptorEventListener#EVENT_OUTPUT}, and
+ * {@link OnFileDescriptorEventListener#EVENT_ERROR} event masks. If the requested
+ * set of events is zero, then the listener is unregistered.
+ * @param listener The listener to invoke when file descriptor events occur.
+ *
+ * @see OnFileDescriptorEventListener
+ * @see #removeOnFileDescriptorEventListener
+ */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd,
+ @OnFileDescriptorEventListener.Events int events,
+ @NonNull OnFileDescriptorEventListener listener) {
+ if (fd == null) {
+ throw new IllegalArgumentException("fd must not be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ if (sForceConcurrent) {
+ synchronized (mFileDescriptorRecordsLock) {
+ updateOnFileDescriptorEventListenerLocked(fd, events, listener);
+ }
+ } else {
+ synchronized (this) {
+ updateOnFileDescriptorEventListenerLocked(fd, events, listener);
+ }
+ }
+ }
+
+ /**
+ * Removes a file descriptor listener.
+ * <p>
+ * This method does nothing if no listener has been registered for the
+ * specified file descriptor.
+ * </p>
+ *
+ * @param fd The file descriptor whose listener will be unregistered.
+ *
+ * @see OnFileDescriptorEventListener
+ * @see #addOnFileDescriptorEventListener
+ */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) {
+ if (fd == null) {
+ throw new IllegalArgumentException("fd must not be null");
+ }
+ if (sForceConcurrent) {
+ synchronized (mFileDescriptorRecordsLock) {
+ updateOnFileDescriptorEventListenerLocked(fd, 0, null);
+ }
+ } else {
+ synchronized (this) {
+ updateOnFileDescriptorEventListenerLocked(fd, 0, null);
+ }
+ }
+ }
+
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events,
+ OnFileDescriptorEventListener listener) {
+ final int fdNum = fd.getInt$();
+
+ int index = -1;
+ FileDescriptorRecord record = null;
+ if (mFileDescriptorRecords != null) {
+ index = mFileDescriptorRecords.indexOfKey(fdNum);
+ if (index >= 0) {
+ record = mFileDescriptorRecords.valueAt(index);
+ if (record != null && record.mEvents == events) {
+ return;
+ }
+ }
+ }
+
+ if (events != 0) {
+ events |= OnFileDescriptorEventListener.EVENT_ERROR;
+ if (record == null) {
+ if (mFileDescriptorRecords == null) {
+ mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>();
+ }
+ record = new FileDescriptorRecord(fd, events, listener);
+ mFileDescriptorRecords.put(fdNum, record);
+ } else {
+ record.mListener = listener;
+ record.mEvents = events;
+ record.mSeq += 1;
+ }
+ nativeSetFileDescriptorEvents(mPtr, fdNum, events);
+ } else if (record != null) {
+ record.mEvents = 0;
+ mFileDescriptorRecords.removeAt(index);
+ nativeSetFileDescriptorEvents(mPtr, fdNum, 0);
+ }
+ }
+
+ // Called from native code.
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private int dispatchEvents(int fd, int events) {
+ // Get the file descriptor record and any state that might change.
+ final FileDescriptorRecord record;
+ final int oldWatchedEvents;
+ final OnFileDescriptorEventListener listener;
+ final int seq;
+ if (sForceConcurrent) {
+ synchronized (mFileDescriptorRecordsLock) {
+ record = mFileDescriptorRecords.get(fd);
+ if (record == null) {
+ return 0; // spurious, no listener registered
+ }
+
+ oldWatchedEvents = record.mEvents;
+ events &= oldWatchedEvents; // filter events based on current watched set
+ if (events == 0) {
+ return oldWatchedEvents; // spurious, watched events changed
+ }
+
+ listener = record.mListener;
+ seq = record.mSeq;
+ }
+ } else {
+ synchronized (this) {
+ record = mFileDescriptorRecords.get(fd);
+ if (record == null) {
+ return 0; // spurious, no listener registered
+ }
+
+ oldWatchedEvents = record.mEvents;
+ events &= oldWatchedEvents; // filter events based on current watched set
+ if (events == 0) {
+ return oldWatchedEvents; // spurious, watched events changed
+ }
+
+ listener = record.mListener;
+ seq = record.mSeq;
+ }
+ }
+ // Invoke the listener outside of the lock.
+ int newWatchedEvents = listener.onFileDescriptorEvents(
+ record.mDescriptor, events);
+ if (newWatchedEvents != 0) {
+ newWatchedEvents |= OnFileDescriptorEventListener.EVENT_ERROR;
+ }
+
+ // Update the file descriptor record if the listener changed the set of
+ // events to watch and the listener itself hasn't been updated since.
+ if (newWatchedEvents != oldWatchedEvents) {
+ synchronized (this) {
+ int index = mFileDescriptorRecords.indexOfKey(fd);
+ if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
+ && record.mSeq == seq) {
+ record.mEvents = newWatchedEvents;
+ if (newWatchedEvents == 0) {
+ mFileDescriptorRecords.removeAt(index);
+ }
+ }
+ }
+ }
+
+ // Return the new set of events to watch for native code to take care of.
+ return newWatchedEvents;
+ }
+
+ private static final AtomicLong mMessagesDelivered = new AtomicLong();
+
+ /* This is only read/written from the Looper thread. For use with Concurrent MQ */
+ private int mNextPollTimeoutMillis;
+ private boolean mMessageDirectlyQueued;
+ private Message nextMessage() {
+ int i = 0;
+
+ while (true) {
+ if (DEBUG) {
+ Log.d(TAG_C, "nextMessage loop #" + i);
+ i++;
+ }
+
+ mDrainingLock.lock();
+ mNextIsDrainingStack = true;
+ mDrainingLock.unlock();
+
+ /*
+ * Set our state to active, drain any items from the stack into our priority queues
+ */
+ StackNode oldTop;
+ oldTop = swapAndSetStackStateActive();
+ drainStack(oldTop);
+
+ mDrainingLock.lock();
+ mNextIsDrainingStack = false;
+ mDrainCompleted.signalAll();
+ mDrainingLock.unlock();
+
+ /*
+ * The objective of this next block of code is to:
+ * - find a message to return (if any is ready)
+ * - find a next message we would like to return, after scheduling.
+ * - we make our scheduling decision based on this next message (if it exists).
+ *
+ * We have two queues to juggle and the presence of barriers throws an additional
+ * wrench into our plans.
+ *
+ * The last wrinkle is that remove() may delete items from underneath us. If we hit
+ * that case, we simply restart the loop.
+ */
+
+ /* Get the first node from each queue */
+ Iterator<MessageNode> queueIter = mPriorityQueue.iterator();
+ MessageNode msgNode = iterateNext(queueIter);
+ Iterator<MessageNode> asyncQueueIter = mAsyncPriorityQueue.iterator();
+ MessageNode asyncMsgNode = iterateNext(asyncQueueIter);
+
+ if (DEBUG) {
+ if (msgNode != null) {
+ Message msg = msgNode.mMessage;
+ Log.d(TAG_C, "Next found node what: " + msg.what + " when: " + msg.when
+ + " seq: " + msgNode.mInsertSeq + "barrier: "
+ + msgNode.isBarrier() + " now: " + SystemClock.uptimeMillis());
+ }
+ if (asyncMsgNode != null) {
+ Message msg = asyncMsgNode.mMessage;
+ Log.d(TAG_C, "Next found async node what: " + msg.what + " when: " + msg.when
+ + " seq: " + asyncMsgNode.mInsertSeq + "barrier: "
+ + asyncMsgNode.isBarrier() + " now: "
+ + SystemClock.uptimeMillis());
+ }
+ }
+
+ /*
+ * the node which we will return, null if none are ready
+ */
+ MessageNode found = null;
+ /*
+ * The node from which we will determine our next wakeup time.
+ * Null indicates there is no next message ready. If we found a node,
+ * we can leave this null as Looper will call us again after delivering
+ * the message.
+ */
+ MessageNode next = null;
+
+ long now = SystemClock.uptimeMillis();
+ /*
+ * If we have a barrier we should return the async node (if it exists and is ready)
+ */
+ if (msgNode != null && msgNode.isBarrier()) {
+ if (asyncMsgNode != null && now >= asyncMsgNode.getWhen()) {
+ found = asyncMsgNode;
+ } else {
+ next = asyncMsgNode;
+ }
+ } else { /* No barrier. */
+ MessageNode earliest;
+ /*
+ * If we have two messages, pick the earliest option from either queue.
+ * Otherwise grab whichever node is non-null. If both are null we'll fall through.
+ */
+ earliest = pickEarliestNode(msgNode, asyncMsgNode);
+
+ if (earliest != null) {
+ if (now >= earliest.getWhen()) {
+ found = earliest;
+ } else {
+ next = earliest;
+ }
+ }
+ }
+
+ if (DEBUG) {
+ if (found != null) {
+ Message msg = found.mMessage;
+ Log.d(TAG_C, " Will deliver node what: " + msg.what + " when: " + msg.when
+ + " seq: " + found.mInsertSeq + " barrier: " + found.isBarrier()
+ + " async: " + found.isAsync() + " now: "
+ + SystemClock.uptimeMillis());
+ } else {
+ Log.d(TAG_C, "No node to deliver");
+ }
+ if (next != null) {
+ Message msg = next.mMessage;
+ Log.d(TAG_C, "Next node what: " + msg.what + " when: " + msg.when + " seq: "
+ + next.mInsertSeq + " barrier: " + next.isBarrier() + " async: "
+ + next.isAsync()
+ + " now: " + SystemClock.uptimeMillis());
+ } else {
+ Log.d(TAG_C, "No next node");
+ }
+ }
+
+ /*
+ * If we have a found message, we will get called again so there's no need to set state.
+ * In that case we can leave our state as ACTIVE.
+ *
+ * Otherwise we should determine how to park the thread.
+ */
+ StateNode nextOp = sStackStateActive;
+ if (found == null) {
+ if (next == null) {
+ /* No message to deliver, sleep indefinitely */
+ mNextPollTimeoutMillis = -1;
+ nextOp = sStackStateParked;
+ if (DEBUG) {
+ Log.d(TAG_C, "nextMessage next state is StackStateParked");
+ }
+ } else {
+ /* Message not ready, or we found one to deliver already, set a timeout */
+ long nextMessageWhen = next.getWhen();
+ if (nextMessageWhen > now) {
+ mNextPollTimeoutMillis = (int) Math.min(nextMessageWhen - now,
+ Integer.MAX_VALUE);
+ } else {
+ mNextPollTimeoutMillis = 0;
+ }
+
+ mStackStateTimedPark.mWhenToWake = now + mNextPollTimeoutMillis;
+ nextOp = mStackStateTimedPark;
+ if (DEBUG) {
+ Log.d(TAG_C, "nextMessage next state is StackStateTimedParked timeout ms "
+ + mNextPollTimeoutMillis + " mWhenToWake: "
+ + mStackStateTimedPark.mWhenToWake + " now " + now);
+ }
+ }
+ }
+
+ /*
+ * Try to swap our state from Active back to Park or TimedPark. If we raced with
+ * enqueue, loop back around to pick up any new items.
+ */
+ if (sState.compareAndSet(this, sStackStateActive, nextOp)) {
+ mMessageCounts.clearCounts();
+ if (found != null) {
+ if (!removeFromPriorityQueue(found)) {
+ /*
+ * RemoveMessages() might be able to pull messages out from under us
+ * However we can detect that here and just loop around if it happens.
+ */
+ continue;
+ }
+
+ if (TRACE) {
+ Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+ }
+ return found.mMessage;
+ }
+ return null;
+ }
+ }
+ }
+
+ private Message nextConcurrent() {
+ final long ptr = mPtr;
+ if (ptr == 0) {
+ return null;
+ }
+
+ mNextPollTimeoutMillis = 0;
+ int pendingIdleHandlerCount = -1; // -1 only during first iteration
+ while (true) {
+ if (mNextPollTimeoutMillis != 0) {
+ Binder.flushPendingCommands();
+ }
+
+ mMessageDirectlyQueued = false;
+ nativePollOnce(ptr, mNextPollTimeoutMillis);
+
+ Message msg = nextMessage();
+ if (msg != null) {
+ msg.markInUse();
+ return msg;
+ }
+
+ if ((boolean) sQuitting.getVolatile(this)) {
+ return null;
+ }
+
+ synchronized (mIdleHandlersLock) {
+ // If first time idle, then get the number of idlers to run.
+ // Idle handles only run if the queue is empty or if the first message
+ // in the queue (possibly a barrier) is due to be handled in the future.
+ if (pendingIdleHandlerCount < 0
+ && isIdle()) {
+ pendingIdleHandlerCount = mIdleHandlers.size();
+ }
+ if (pendingIdleHandlerCount <= 0) {
+ // No idle handlers to run. Loop and wait some more.
+ continue;
+ }
+
+ if (mPendingIdleHandlers == null) {
+ mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
+ }
+ mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
+ }
+
+ // Run the idle handlers.
+ // We only ever reach this code block during the first iteration.
+ for (int i = 0; i < pendingIdleHandlerCount; i++) {
+ final IdleHandler idler = mPendingIdleHandlers[i];
+ mPendingIdleHandlers[i] = null; // release the reference to the handler
+
+ boolean keep = false;
+ try {
+ keep = idler.queueIdle();
+ } catch (Throwable t) {
+ Log.wtf(TAG_C, "IdleHandler threw exception", t);
+ }
+
+ if (!keep) {
+ synchronized (mIdleHandlersLock) {
+ mIdleHandlers.remove(idler);
+ }
+ }
+ }
+
+ // Reset the idle handler count to 0 so we do not run them again.
+ pendingIdleHandlerCount = 0;
+
+ // While calling an idle handler, a new message could have been delivered
+ // so go back and look again for a pending message without waiting.
+ mNextPollTimeoutMillis = 0;
+ }
+ }
+
+ @UnsupportedAppUsage
+ Message next() {
+ if (sForceConcurrent) {
+ return nextConcurrent();
+ }
+
+ // Return here if the message loop has already quit and been disposed.
+ // This can happen if the application tries to restart a looper after quit
+ // which is not supported.
+ final long ptr = mPtr;
+ if (ptr == 0) {
+ return null;
+ }
+
+ int pendingIdleHandlerCount = -1; // -1 only during first iteration
+ int nextPollTimeoutMillis = 0;
+ for (;;) {
+ if (nextPollTimeoutMillis != 0) {
+ Binder.flushPendingCommands();
+ }
+
+ nativePollOnce(ptr, nextPollTimeoutMillis);
+
+ synchronized (this) {
+ // Try to retrieve the next message. Return if found.
+ final long now = SystemClock.uptimeMillis();
+ Message prevMsg = null;
+ Message msg = mMessages;
+ if (msg != null && msg.target == null) {
+ // Stalled by a barrier. Find the next asynchronous message in the queue.
+ do {
+ prevMsg = msg;
+ msg = msg.next;
+ } while (msg != null && !msg.isAsynchronous());
+ }
+ if (msg != null) {
+ if (now < msg.when) {
+ // Next message is not ready. Set a timeout to wake up when it is ready.
+ nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
+ } else {
+ // Got a message.
+ mLegacyBlocked = false;
+ if (prevMsg != null) {
+ prevMsg.next = msg.next;
+ if (prevMsg.next == null) {
+ mLast = prevMsg;
+ }
+ } else {
+ mMessages = msg.next;
+ if (msg.next == null) {
+ mLast = null;
+ }
+ }
+ msg.next = null;
+ if (DEBUG) Log.v(TAG_L, "Returning message: " + msg);
+ msg.markInUse();
+ if (msg.isAsynchronous()) {
+ mLegacyAsyncMessageCount--;
+ }
+ if (TRACE) {
+ Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+ }
+ return msg;
+ }
+ } else {
+ // No more messages.
+ nextPollTimeoutMillis = -1;
+ }
+
+ // Process the quit message now that all pending messages have been handled.
+ if (mLegacyQuitting) {
+ dispose();
+ return null;
+ }
+
+ // If first time idle, then get the number of idlers to run.
+ // Idle handles only run if the queue is empty or if the first message
+ // in the queue (possibly a barrier) is due to be handled in the future.
+ if (pendingIdleHandlerCount < 0
+ && (mMessages == null || now < mMessages.when)) {
+ pendingIdleHandlerCount = mIdleHandlers.size();
+ }
+ if (pendingIdleHandlerCount <= 0) {
+ // No idle handlers to run. Loop and wait some more.
+ mLegacyBlocked = true;
+ continue;
+ }
+
+ if (mPendingIdleHandlers == null) {
+ mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
+ }
+ mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
+ }
+
+ // Run the idle handlers.
+ // We only ever reach this code block during the first iteration.
+ for (int i = 0; i < pendingIdleHandlerCount; i++) {
+ final IdleHandler idler = mPendingIdleHandlers[i];
+ mPendingIdleHandlers[i] = null; // release the reference to the handler
+
+ boolean keep = false;
+ try {
+ keep = idler.queueIdle();
+ } catch (Throwable t) {
+ Log.wtf(TAG_L, "IdleHandler threw exception", t);
+ }
+
+ if (!keep) {
+ synchronized (this) {
+ mIdleHandlers.remove(idler);
+ }
+ }
+ }
+
+ // Reset the idle handler count to 0 so we do not run them again.
+ pendingIdleHandlerCount = 0;
+
+ // While calling an idle handler, a new message could have been delivered
+ // so go back and look again for a pending message without waiting.
+ nextPollTimeoutMillis = 0;
+ }
+ }
+
+ void quit(boolean safe) {
+ if (!mQuitAllowed) {
+ throw new IllegalStateException("Main thread not allowed to quit.");
+ }
+
+ if (sForceConcurrent) {
+ synchronized (mIdleHandlersLock) {
+ if (sQuitting.compareAndSet(this, false, true)) {
+ if (safe) {
+ removeAllFutureMessages();
+ } else {
+ removeAllMessages();
+ }
+
+ // We can assume mPtr != 0 because sQuitting was previously false.
+ nativeWake(mPtr);
+ }
+ }
+ } else {
+ synchronized (this) {
+ if (mLegacyQuitting) {
+ return;
+ }
+ mLegacyQuitting = true;
+
+ if (safe) {
+ removeAllFutureMessagesLocked();
+ } else {
+ removeAllMessagesLocked();
+ }
+
+ // We can assume mPtr != 0 because mLegacyQuitting was previously false.
+ nativeWake(mPtr);
+ }
+ }
+ }
+
+ /**
+ * Posts a synchronization barrier to the Looper's message queue.
+ *
+ * Message processing occurs as usual until the message queue encounters the
+ * synchronization barrier that has been posted. When the barrier is encountered,
+ * later synchronous messages in the queue are stalled (prevented from being executed)
+ * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
+ * the token that identifies the synchronization barrier.
+ *
+ * This method is used to immediately postpone execution of all subsequently posted
+ * synchronous messages until a condition is met that releases the barrier.
+ * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
+ * and continue to be processed as usual.
+ *
+ * This call must be always matched by a call to {@link #removeSyncBarrier} with
+ * the same token to ensure that the message queue resumes normal operation.
+ * Otherwise the application will probably hang!
+ *
+ * @return A token that uniquely identifies the barrier. This token must be
+ * passed to {@link #removeSyncBarrier} to release the barrier.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @TestApi
+ public int postSyncBarrier() {
+ return postSyncBarrier(SystemClock.uptimeMillis());
+ }
+
+ private int postSyncBarrier(long when) {
+ // Enqueue a new sync barrier token.
+ // We don't need to wake the queue because the purpose of a barrier is to stall it.
+ if (sForceConcurrent) {
+ final int token = mNextBarrierToken.getAndIncrement();
+ final Message msg = Message.obtain();
+
+ msg.markInUse();
+ msg.arg1 = token;
+
+ if (!enqueueMessageUnchecked(msg, when)) {
+ Log.wtf(TAG_C, "Unexpected error while adding sync barrier!");
+ return -1;
+ }
+
+ return token;
+ }
+
+ synchronized (this) {
+ final int token = mLegacyNextBarrierToken++;
+ final Message msg = Message.obtain();
+ msg.markInUse();
+ msg.when = when;
+ msg.arg1 = token;
+
+ if (Flags.messageQueueTailTracking() && mLast != null && mLast.when <= when) {
+ /* Message goes to tail of list */
+ mLast.next = msg;
+ mLast = msg;
+ msg.next = null;
+ return token;
+ }
+
+ Message prev = null;
+ Message p = mMessages;
+ if (when != 0) {
+ while (p != null && p.when <= when) {
+ prev = p;
+ p = p.next;
+ }
+ }
+
+ if (p == null) {
+ /* We reached the tail of the list, or list is empty. */
+ mLast = msg;
+ }
+
+ if (prev != null) { // invariant: p == prev.next
+ msg.next = p;
+ prev.next = msg;
+ } else {
+ msg.next = p;
+ mMessages = msg;
+ }
+ return token;
+ }
+ }
+
+ private class MatchBarrierToken extends MessageCompare {
+ int mBarrierToken;
+
+ MatchBarrierToken(int token) {
+ super();
+ mBarrierToken = token;
+ }
+
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == null && m.arg1 == mBarrierToken) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Removes a synchronization barrier.
+ *
+ * @param token The synchronization barrier token that was returned by
+ * {@link #postSyncBarrier}.
+ *
+ * @throws IllegalStateException if the barrier was not found.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @TestApi
+ public void removeSyncBarrier(int token) {
+ // Remove a sync barrier token from the queue.
+ // If the queue is no longer stalled by a barrier then wake it.
+ if (sForceConcurrent) {
+ boolean removed;
+ MessageNode first;
+ final MatchBarrierToken matchBarrierToken = new MatchBarrierToken(token);
+
+ try {
+ /* Retain the first element to see if we are currently stuck on a barrier. */
+ first = mPriorityQueue.first();
+ } catch (NoSuchElementException e) {
+ /* The queue is empty */
+ first = null;
+ }
+
+ removed = findOrRemoveMessages(null, 0, null, null, 0, matchBarrierToken, true);
+ if (removed && first != null) {
+ Message m = first.mMessage;
+ if (m.target == null && m.arg1 == token) {
+ /* Wake up next() in case it was sleeping on this barrier. */
+ nativeWake(mPtr);
+ }
+ } else if (!removed) {
+ throw new IllegalStateException("The specified message queue synchronization "
+ + " barrier token has not been posted or has already been removed.");
+ }
+ return;
+ }
+
+ synchronized (this) {
+ Message prev = null;
+ Message p = mMessages;
+ while (p != null && (p.target != null || p.arg1 != token)) {
+ prev = p;
+ p = p.next;
+ }
+ if (p == null) {
+ throw new IllegalStateException("The specified message queue synchronization "
+ + " barrier token has not been posted or has already been removed.");
+ }
+ final boolean needWake;
+ if (prev != null) {
+ prev.next = p.next;
+ if (prev.next == null) {
+ mLast = prev;
+ }
+ needWake = false;
+ } else {
+ mMessages = p.next;
+ if (mMessages == null) {
+ mLast = null;
+ }
+ needWake = mMessages == null || mMessages.target != null;
+ }
+ p.recycleUnchecked();
+
+ // If the loop is quitting then it is already awake.
+ // We can assume mPtr != 0 when mLegacyQuitting is false.
+ if (needWake && !mLegacyQuitting) {
+ nativeWake(mPtr);
+ }
+ }
+ }
+
+ boolean enqueueMessage(Message msg, long when) {
+ if (msg.target == null) {
+ throw new IllegalArgumentException("Message must have a target.");
+ }
+
+ if (sForceConcurrent) {
+ if (msg.isInUse()) {
+ throw new IllegalStateException(msg + " This message is already in use.");
+ }
+
+ return enqueueMessageUnchecked(msg, when);
+ }
+
+ synchronized (this) {
+ if (msg.isInUse()) {
+ throw new IllegalStateException(msg + " This message is already in use.");
+ }
+
+ if (mLegacyQuitting) {
+ IllegalStateException e = new IllegalStateException(
+ msg.target + " sending message to a Handler on a dead thread");
+ Log.w(TAG_L, e.getMessage(), e);
+ msg.recycle();
+ return false;
+ }
+
+ msg.markInUse();
+ msg.when = when;
+ Message p = mMessages;
+ boolean needWake;
+ if (p == null || when == 0 || when < p.when) {
+ // New head, wake up the event queue if blocked.
+ msg.next = p;
+ mMessages = msg;
+ needWake = mLegacyBlocked;
+ if (p == null) {
+ mLast = mMessages;
+ }
+ } else {
+ // Message is to be inserted at tail or middle of queue. Usually we don't have to
+ // wake up the event queue unless there is a barrier at the head of the queue and
+ // the message is the earliest asynchronous message in the queue.
+ needWake = mLegacyBlocked && p.target == null && msg.isAsynchronous();
+
+ // For readability, we split this portion of the function into two blocks based on
+ // whether tail tracking is enabled. This has a minor implication for the case
+ // where tail tracking is disabled. See the comment below.
+ if (Flags.messageQueueTailTracking()) {
+ if (when >= mLast.when) {
+ needWake = needWake && mLegacyAsyncMessageCount == 0;
+ msg.next = null;
+ mLast.next = msg;
+ mLast = msg;
+ } else {
+ // Inserted within the middle of the queue.
+ Message prev;
+ for (;;) {
+ prev = p;
+ p = p.next;
+ if (p == null || when < p.when) {
+ break;
+ }
+ if (needWake && p.isAsynchronous()) {
+ needWake = false;
+ }
+ }
+ if (p == null) {
+ /* Inserting at tail of queue */
+ mLast = msg;
+ }
+ msg.next = p; // invariant: p == prev.next
+ prev.next = msg;
+ }
+ } else {
+ Message prev;
+ for (;;) {
+ prev = p;
+ p = p.next;
+ if (p == null || when < p.when) {
+ break;
+ }
+ if (needWake && p.isAsynchronous()) {
+ needWake = false;
+ }
+ }
+ msg.next = p; // invariant: p == prev.next
+ prev.next = msg;
+
+ /*
+ * If this block is executing then we have a build without tail tracking -
+ * specifically: Flags.messageQueueTailTracking() == false. This is determined
+ * at build time so the flag won't change on us during runtime.
+ *
+ * Since we don't want to pepper the code with extra checks, we only check
+ * for tail tracking when we might use mLast. Otherwise, we continue to update
+ * mLast as the tail of the list.
+ *
+ * In this case however we are not maintaining mLast correctly. Since we never
+ * use it, this is fine. However, we run the risk of leaking a reference.
+ * So set mLast to null in this case to avoid any Message leaks. The other
+ * sites will never use the value so we are safe against null pointer derefs.
+ */
+ mLast = null;
+ }
+ }
+
+ if (msg.isAsynchronous()) {
+ mLegacyAsyncMessageCount++;
+ }
+
+ // We can assume mPtr != 0 because mLegacyQuitting is false.
+ if (needWake) {
+ nativeWake(mPtr);
+ }
+ }
+ return true;
+ }
+
+ private static class MatchHandlerWhatAndObject extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.what == what && (object == null || m.obj == object)) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerWhatAndObject mMatchHandlerWhatAndObject =
+ new MatchHandlerWhatAndObject();
+ boolean hasMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return false;
+ }
+ if (sForceConcurrent) {
+ return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject,
+ false);
+ }
+ synchronized (this) {
+ Message p = mMessages;
+ while (p != null) {
+ if (p.target == h && p.what == what && (object == null || p.obj == object)) {
+ return true;
+ }
+ p = p.next;
+ }
+ return false;
+ }
+ }
+
+ private static class MatchHandlerWhatAndObjectEquals extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerWhatAndObjectEquals mMatchHandlerWhatAndObjectEquals =
+ new MatchHandlerWhatAndObjectEquals();
+ boolean hasEqualMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return false;
+ }
+ if (sForceConcurrent) {
+ return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals,
+ false);
+
+ }
+ synchronized (this) {
+ Message p = mMessages;
+ while (p != null) {
+ if (p.target == h && p.what == what && (object == null || object.equals(p.obj))) {
+ return true;
+ }
+ p = p.next;
+ }
+ return false;
+ }
+ }
+
+ private static class MatchHandlerRunnableAndObject extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.callback == r && (object == null || m.obj == object)) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerRunnableAndObject mMatchHandlerRunnableAndObject =
+ new MatchHandlerRunnableAndObject();
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ boolean hasMessages(Handler h, Runnable r, Object object) {
+ if (h == null) {
+ return false;
+ }
+ if (sForceConcurrent) {
+ return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject,
+ false);
+ }
+
+ synchronized (this) {
+ Message p = mMessages;
+ while (p != null) {
+ if (p.target == h && p.callback == r && (object == null || p.obj == object)) {
+ return true;
+ }
+ p = p.next;
+ }
+ return false;
+ }
+ }
+
+ private static class MatchHandler extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandler mMatchHandler = new MatchHandler();
+ boolean hasMessages(Handler h) {
+ if (h == null) {
+ return false;
+ }
+ if (sForceConcurrent) {
+ return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false);
+ }
+ synchronized (this) {
+ Message p = mMessages;
+ while (p != null) {
+ if (p.target == h) {
+ return true;
+ }
+ p = p.next;
+ }
+ return false;
+ }
+ }
+
+ void removeMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return;
+ }
+ if (sForceConcurrent) {
+ findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true);
+ return;
+ }
+ synchronized (this) {
+ Message p = mMessages;
+
+ // Remove all messages at front.
+ while (p != null && p.target == h && p.what == what
+ && (object == null || p.obj == object)) {
+ Message n = p.next;
+ mMessages = n;
+ if (p.isAsynchronous()) {
+ mLegacyAsyncMessageCount--;
+ }
+ p.recycleUnchecked();
+ p = n;
+ }
+
+ if (p == null) {
+ mLast = mMessages;
+ }
+
+ // Remove all messages after front.
+ while (p != null) {
+ Message n = p.next;
+ if (n != null) {
+ if (n.target == h && n.what == what
+ && (object == null || n.obj == object)) {
+ Message nn = n.next;
+ if (n.isAsynchronous()) {
+ mLegacyAsyncMessageCount--;
+ }
+ n.recycleUnchecked();
+ p.next = nn;
+ if (p.next == null) {
+ mLast = p;
+ }
+ continue;
+ }
+ }
+ p = n;
+ }
+ }
+ }
+
+ void removeEqualMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return;
+ }
+
+ if (sForceConcurrent) {
+ findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true);
+ return;
+ }
+
+ synchronized (this) {
+ Message p = mMessages;
+
+ // Remove all messages at front.
+ while (p != null && p.target == h && p.what == what
+ && (object == null || object.equals(p.obj))) {
+ Message n = p.next;
+ mMessages = n;
+ if (p.isAsynchronous()) {
+ mLegacyAsyncMessageCount--;
+ }
+ p.recycleUnchecked();
+ p = n;
+ }
+
+ if (p == null) {
+ mLast = mMessages;
+ }
+
+ // Remove all messages after front.
+ while (p != null) {
+ Message n = p.next;
+ if (n != null) {
+ if (n.target == h && n.what == what
+ && (object == null || object.equals(n.obj))) {
+ Message nn = n.next;
+ if (n.isAsynchronous()) {
+ mLegacyAsyncMessageCount--;
+ }
+ n.recycleUnchecked();
+ p.next = nn;
+ if (p.next == null) {
+ mLast = p;
+ }
+ continue;
+ }
+ }
+ p = n;
+ }
+ }
+ }
+
+ void removeMessages(Handler h, Runnable r, Object object) {
+ if (h == null || r == null) {
+ return;
+ }
+
+ if (sForceConcurrent) {
+ findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true);
+ return;
+ }
+ synchronized (this) {
+ Message p = mMessages;
+
+ // Remove all messages at front.
+ while (p != null && p.target == h && p.callback == r
+ && (object == null || p.obj == object)) {
+ Message n = p.next;
+ mMessages = n;
+ if (p.isAsynchronous()) {
+ mLegacyAsyncMessageCount--;
+ }
+ p.recycleUnchecked();
+ p = n;
+ }
+
+ if (p == null) {
+ mLast = mMessages;
+ }
+
+ // Remove all messages after front.
+ while (p != null) {
+ Message n = p.next;
+ if (n != null) {
+ if (n.target == h && n.callback == r
+ && (object == null || n.obj == object)) {
+ Message nn = n.next;
+ if (n.isAsynchronous()) {
+ mLegacyAsyncMessageCount--;
+ }
+ n.recycleUnchecked();
+ p.next = nn;
+ if (p.next == null) {
+ mLast = p;
+ }
+ continue;
+ }
+ }
+ p = n;
+ }
+ }
+ }
+
+ private static class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerRunnableAndObjectEquals mMatchHandlerRunnableAndObjectEquals =
+ new MatchHandlerRunnableAndObjectEquals();
+ void removeEqualMessages(Handler h, Runnable r, Object object) {
+ if (h == null || r == null) {
+ return;
+ }
+
+ if (sForceConcurrent) {
+ findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true);
+ return;
+ }
+ synchronized (this) {
+ Message p = mMessages;
+
+ // Remove all messages at front.
+ while (p != null && p.target == h && p.callback == r
+ && (object == null || object.equals(p.obj))) {
+ Message n = p.next;
+ mMessages = n;
+ if (p.isAsynchronous()) {
+ mLegacyAsyncMessageCount--;
+ }
+ p.recycleUnchecked();
+ p = n;
+ }
+
+ if (p == null) {
+ mLast = mMessages;
+ }
+
+ // Remove all messages after front.
+ while (p != null) {
+ Message n = p.next;
+ if (n != null) {
+ if (n.target == h && n.callback == r
+ && (object == null || object.equals(n.obj))) {
+ Message nn = n.next;
+ if (n.isAsynchronous()) {
+ mLegacyAsyncMessageCount--;
+ }
+ n.recycleUnchecked();
+ p.next = nn;
+ if (p.next == null) {
+ mLast = p;
+ }
+ continue;
+ }
+ }
+ p = n;
+ }
+ }
+ }
+
+ private static class MatchHandlerAndObject extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && (object == null || m.obj == object)) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerAndObject mMatchHandlerAndObject = new MatchHandlerAndObject();
+ void removeCallbacksAndMessages(Handler h, Object object) {
+ if (h == null) {
+ return;
+ }
+
+ if (sForceConcurrent) {
+ findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true);
+ return;
+ }
+ synchronized (this) {
+ Message p = mMessages;
+
+ // Remove all messages at front.
+ while (p != null && p.target == h
+ && (object == null || p.obj == object)) {
+ Message n = p.next;
+ mMessages = n;
+ if (p.isAsynchronous()) {
+ mLegacyAsyncMessageCount--;
+ }
+ p.recycleUnchecked();
+ p = n;
+ }
+
+ if (p == null) {
+ mLast = mMessages;
+ }
+
+ // Remove all messages after front.
+ while (p != null) {
+ Message n = p.next;
+ if (n != null) {
+ if (n.target == h && (object == null || n.obj == object)) {
+ Message nn = n.next;
+ if (n.isAsynchronous()) {
+ mLegacyAsyncMessageCount--;
+ }
+ n.recycleUnchecked();
+ p.next = nn;
+ if (p.next == null) {
+ mLast = p;
+ }
+ continue;
+ }
+ }
+ p = n;
+ }
+ }
+ }
+
+ private static class MatchHandlerAndObjectEquals extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && (object == null || object.equals(m.obj))) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerAndObjectEquals mMatchHandlerAndObjectEquals =
+ new MatchHandlerAndObjectEquals();
+ void removeCallbacksAndEqualMessages(Handler h, Object object) {
+ if (h == null) {
+ return;
+ }
+
+ if (sForceConcurrent) {
+ findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true);
+ return;
+ }
+ synchronized (this) {
+ Message p = mMessages;
+
+ // Remove all messages at front.
+ while (p != null && p.target == h
+ && (object == null || object.equals(p.obj))) {
+ Message n = p.next;
+ mMessages = n;
+ if (p.isAsynchronous()) {
+ mLegacyAsyncMessageCount--;
+ }
+ p.recycleUnchecked();
+ p = n;
+ }
+
+ if (p == null) {
+ mLast = mMessages;
+ }
+
+ // Remove all messages after front.
+ while (p != null) {
+ Message n = p.next;
+ if (n != null) {
+ if (n.target == h && (object == null || object.equals(n.obj))) {
+ Message nn = n.next;
+ if (n.isAsynchronous()) {
+ mLegacyAsyncMessageCount--;
+ }
+ n.recycleUnchecked();
+ p.next = nn;
+ if (p.next == null) {
+ mLast = p;
+ }
+ continue;
+ }
+ }
+ p = n;
+ }
+ }
+ }
+
+ private void removeAllMessagesLocked() {
+ Message p = mMessages;
+ while (p != null) {
+ Message n = p.next;
+ p.recycleUnchecked();
+ p = n;
+ }
+ mMessages = null;
+ mLast = null;
+ mLegacyAsyncMessageCount = 0;
+ }
+
+ private void removeAllFutureMessagesLocked() {
+ final long now = SystemClock.uptimeMillis();
+ Message p = mMessages;
+ if (p != null) {
+ if (p.when > now) {
+ removeAllMessagesLocked();
+ } else {
+ Message n;
+ for (;;) {
+ n = p.next;
+ if (n == null) {
+ return;
+ }
+ if (n.when > now) {
+ break;
+ }
+ p = n;
+ }
+ p.next = null;
+ mLast = p;
+
+ do {
+ p = n;
+ n = p.next;
+ if (p.isAsynchronous()) {
+ mLegacyAsyncMessageCount--;
+ }
+ p.recycleUnchecked();
+ } while (n != null);
+ }
+ }
+ }
+
+ private static class MatchAllMessages extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ return true;
+ }
+ }
+ private final MatchAllMessages mMatchAllMessages = new MatchAllMessages();
+ private void removeAllMessages() {
+ findOrRemoveMessages(null, -1, null, null, 0, mMatchAllMessages, true);
+ }
+
+ private static class MatchAllFutureMessages extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.when > when) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchAllFutureMessages mMatchAllFutureMessages = new MatchAllFutureMessages();
+ private void removeAllFutureMessages() {
+ findOrRemoveMessages(null, -1, null, null, SystemClock.uptimeMillis(),
+ mMatchAllFutureMessages, true);
+ }
+
+ @NeverCompile
+ private void printPriorityQueueNodes() {
+ Iterator<MessageNode> iterator = mPriorityQueue.iterator();
+
+ Log.d(TAG_C, "* Dump priority queue");
+ while (iterator.hasNext()) {
+ MessageNode msgNode = iterator.next();
+ Log.d(TAG_C, "** MessageNode what: " + msgNode.mMessage.what + " when "
+ + msgNode.mMessage.when + " seq: " + msgNode.mInsertSeq);
+ }
+ }
+
+ @NeverCompile
+ private int dumpPriorityQueue(ConcurrentSkipListSet<MessageNode> queue, Printer pw,
+ String prefix, Handler h, int n) {
+ int count = 0;
+ long now = SystemClock.uptimeMillis();
+
+ for (MessageNode msgNode : queue) {
+ Message msg = msgNode.mMessage;
+ if (h == null || h == msg.target) {
+ pw.println(prefix + "Message " + (n + count) + ": " + msg.toString(now));
+ }
+ count++;
+ }
+ return count;
+ }
+
+ @NeverCompile
+ void dump(Printer pw, String prefix, Handler h) {
+ if (sForceConcurrent) {
+ long now = SystemClock.uptimeMillis();
+ int n = 0;
+
+ pw.println(prefix + "(MessageQueue is using Concurrent implementation)");
+
+ StackNode node = (StackNode) sState.getVolatile(this);
+ while (node != null) {
+ if (node.isMessageNode()) {
+ Message msg = ((MessageNode) node).mMessage;
+ if (h == null || h == msg.target) {
+ pw.println(prefix + "Message " + n + ": " + msg.toString(now));
+ }
+ node = ((MessageNode) node).mNext;
+ } else {
+ pw.println(prefix + "State: " + node);
+ node = null;
+ }
+ n++;
+ }
+
+ pw.println(prefix + "PriorityQueue Messages: ");
+ n += dumpPriorityQueue(mPriorityQueue, pw, prefix, h, n);
+ pw.println(prefix + "AsyncPriorityQueue Messages: ");
+ n += dumpPriorityQueue(mAsyncPriorityQueue, pw, prefix, h, n);
+
+ pw.println(prefix + "(Total messages: " + n + ", polling=" + isPolling()
+ + ", quitting=" + (boolean) sQuitting.getVolatile(this) + ")");
+ return;
+ }
+
+ synchronized (this) {
+ pw.println(prefix + "(MessageQueue is using Legacy implementation)");
+ long now = SystemClock.uptimeMillis();
+ int n = 0;
+ for (Message msg = mMessages; msg != null; msg = msg.next) {
+ if (h == null || h == msg.target) {
+ pw.println(prefix + "Message " + n + ": " + msg.toString(now));
+ }
+ n++;
+ }
+ pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()
+ + ", quitting=" + mLegacyQuitting + ")");
+ }
+ }
+
+ @NeverCompile
+ private int dumpPriorityQueue(ConcurrentSkipListSet<MessageNode> queue,
+ ProtoOutputStream proto) {
+ int count = 0;
+
+ for (MessageNode msgNode : queue) {
+ Message msg = msgNode.mMessage;
+ msg.dumpDebug(proto, MessageQueueProto.MESSAGES);
+ count++;
+ }
+ return count;
+ }
+
+ @NeverCompile
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ if (sForceConcurrent) {
+ final long messageQueueToken = proto.start(fieldId);
+
+ StackNode node = (StackNode) sState.getVolatile(this);
+ while (node.isMessageNode()) {
+ Message msg = ((MessageNode) node).mMessage;
+ msg.dumpDebug(proto, MessageQueueProto.MESSAGES);
+ node = ((MessageNode) node).mNext;
+ }
+
+ dumpPriorityQueue(mPriorityQueue, proto);
+ dumpPriorityQueue(mAsyncPriorityQueue, proto);
+
+ proto.write(MessageQueueProto.IS_POLLING_LOCKED, isPolling());
+ proto.write(MessageQueueProto.IS_QUITTING, (boolean) sQuitting.getVolatile(this));
+ proto.end(messageQueueToken);
+ return;
+ }
+
+ final long messageQueueToken = proto.start(fieldId);
+ synchronized (this) {
+ for (Message msg = mMessages; msg != null; msg = msg.next) {
+ msg.dumpDebug(proto, MessageQueueProto.MESSAGES);
+ }
+ proto.write(MessageQueueProto.IS_POLLING_LOCKED, isPollingLocked());
+ proto.write(MessageQueueProto.IS_QUITTING, mLegacyQuitting);
+ }
+ proto.end(messageQueueToken);
+ }
+
+ /**
+ * Callback interface for discovering when a thread is going to block
+ * waiting for more messages.
+ */
+ public static interface IdleHandler {
+ /**
+ * Called when the message queue has run out of messages and will now
+ * wait for more. Return true to keep your idle handler active, false
+ * to have it removed. This may be called if there are still messages
+ * pending in the queue, but they are all scheduled to be dispatched
+ * after the current time.
+ */
+ boolean queueIdle();
+ }
+
+ /**
+ * A listener which is invoked when file descriptor related events occur.
+ */
+ public interface OnFileDescriptorEventListener {
+ /**
+ * File descriptor event: Indicates that the file descriptor is ready for input
+ * operations, such as reading.
+ * <p>
+ * The listener should read all available data from the file descriptor
+ * then return <code>true</code> to keep the listener active or <code>false</code>
+ * to remove the listener.
+ * </p><p>
+ * In the case of a socket, this event may be generated to indicate
+ * that there is at least one incoming connection that the listener
+ * should accept.
+ * </p><p>
+ * This event will only be generated if the {@link #EVENT_INPUT} event mask was
+ * specified when the listener was added.
+ * </p>
+ */
+ public static final int EVENT_INPUT = 1 << 0;
+
+ /**
+ * File descriptor event: Indicates that the file descriptor is ready for output
+ * operations, such as writing.
+ * <p>
+ * The listener should write as much data as it needs. If it could not
+ * write everything at once, then it should return <code>true</code> to
+ * keep the listener active. Otherwise, it should return <code>false</code>
+ * to remove the listener then re-register it later when it needs to write
+ * something else.
+ * </p><p>
+ * This event will only be generated if the {@link #EVENT_OUTPUT} event mask was
+ * specified when the listener was added.
+ * </p>
+ */
+ public static final int EVENT_OUTPUT = 1 << 1;
+
+ /**
+ * File descriptor event: Indicates that the file descriptor encountered a
+ * fatal error.
+ * <p>
+ * File descriptor errors can occur for various reasons. One common error
+ * is when the remote peer of a socket or pipe closes its end of the connection.
+ * </p><p>
+ * This event may be generated at any time regardless of whether the
+ * {@link #EVENT_ERROR} event mask was specified when the listener was added.
+ * </p>
+ */
+ public static final int EVENT_ERROR = 1 << 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "EVENT_" }, value = {
+ EVENT_INPUT,
+ EVENT_OUTPUT,
+ EVENT_ERROR
+ })
+ public @interface Events {}
+
+ /**
+ * Called when a file descriptor receives events.
+ *
+ * @param fd The file descriptor.
+ * @param events The set of events that occurred: a combination of the
+ * {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks.
+ * @return The new set of events to watch, or 0 to unregister the listener.
+ *
+ * @see #EVENT_INPUT
+ * @see #EVENT_OUTPUT
+ * @see #EVENT_ERROR
+ */
+ @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events);
+ }
+
+ private static final class FileDescriptorRecord {
+ public final FileDescriptor mDescriptor;
+ public int mEvents;
+ public OnFileDescriptorEventListener mListener;
+ public int mSeq;
+
+ public FileDescriptorRecord(FileDescriptor descriptor,
+ int events, OnFileDescriptorEventListener listener) {
+ mDescriptor = descriptor;
+ mEvents = events;
+ mListener = listener;
+ }
+ }
+
+ /**
+ * ConcurrentMessageQueue specific classes methods and variables
+ */
+ /* Helper to choose the correct queue to insert into. */
+ private void insertIntoPriorityQueue(MessageNode msgNode) {
+ if (msgNode.isAsync()) {
+ mAsyncPriorityQueue.add(msgNode);
+ } else {
+ mPriorityQueue.add(msgNode);
+ }
+ }
+
+ private boolean removeFromPriorityQueue(MessageNode msgNode) {
+ if (msgNode.isAsync()) {
+ return mAsyncPriorityQueue.remove(msgNode);
+ } else {
+ return mPriorityQueue.remove(msgNode);
+ }
+ }
+
+ private MessageNode pickEarliestNode(MessageNode nodeA, MessageNode nodeB) {
+ if (nodeA != null && nodeB != null) {
+ if (nodeA.compareTo(nodeB) < 0) {
+ return nodeA;
+ }
+ return nodeB;
+ }
+
+ return nodeA != null ? nodeA : nodeB;
+ }
+
+ private MessageNode iterateNext(Iterator<MessageNode> iter) {
+ if (iter.hasNext()) {
+ try {
+ return iter.next();
+ } catch (NoSuchElementException e) {
+ /* The queue is empty - this can happen if we race with remove */
+ }
+ }
+ return null;
+ }
+
+ /* Move any non-cancelled messages into the priority queue */
+ private void drainStack(StackNode oldTop) {
+ while (oldTop.isMessageNode()) {
+ MessageNode oldTopMessageNode = (MessageNode) oldTop;
+ if (oldTopMessageNode.removeFromStack()) {
+ insertIntoPriorityQueue(oldTopMessageNode);
+ }
+ MessageNode inserted = oldTopMessageNode;
+ oldTop = oldTopMessageNode.mNext;
+ /*
+ * removeMessages can walk this list while we are consuming it.
+ * Set our next pointer to null *after* we add the message to our
+ * priority queue. This way removeMessages() will always find the
+ * message, either in our list or in the priority queue.
+ */
+ inserted.mNext = null;
+ }
+ }
+
+ /* Set the stack state to Active, return a list of nodes to walk. */
+ private StackNode swapAndSetStackStateActive() {
+ while (true) {
+ /* Set stack state to Active, get node list to walk later */
+ StackNode current = (StackNode) sState.getVolatile(this);
+ if (current == sStackStateActive
+ || sState.compareAndSet(this, current, sStackStateActive)) {
+ return current;
+ }
+ }
+ }
+ private StateNode getStateNode(StackNode node) {
+ if (node.isMessageNode()) {
+ return ((MessageNode) node).mBottomOfStack;
+ }
+ return (StateNode) node;
+ }
+
+ private void waitForDrainCompleted() {
+ mDrainingLock.lock();
+ while (mNextIsDrainingStack) {
+ mDrainCompleted.awaitUninterruptibly();
+ }
+ mDrainingLock.unlock();
+ }
+
+ @IntDef(value = {
+ STACK_NODE_MESSAGE,
+ STACK_NODE_ACTIVE,
+ STACK_NODE_PARKED,
+ STACK_NODE_TIMEDPARK})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface StackNodeType {}
+
+ /*
+ * Stack node types. STACK_NODE_MESSAGE indicates a node containing a message.
+ * The other types indicate what state our Looper thread is in. The bottom of
+ * the stack is always a single state node. Message nodes are added on top.
+ */
+ private static final int STACK_NODE_MESSAGE = 0;
+ /*
+ * Active state indicates that next() is processing messages
+ */
+ private static final int STACK_NODE_ACTIVE = 1;
+ /*
+ * Parked state indicates that the Looper thread is sleeping indefinitely (nothing to deliver)
+ */
+ private static final int STACK_NODE_PARKED = 2;
+ /*
+ * Timed Park state indicates that the Looper thread is sleeping, waiting for a message
+ * deadline
+ */
+ private static final int STACK_NODE_TIMEDPARK = 3;
+
+ /* Describes a node in the Treiber stack */
+ static class StackNode {
+ @StackNodeType
+ private final int mType;
+
+ StackNode(@StackNodeType int type) {
+ mType = type;
+ }
+
+ @StackNodeType
+ final int getNodeType() {
+ return mType;
+ }
+
+ final boolean isMessageNode() {
+ return mType == STACK_NODE_MESSAGE;
+ }
+ }
+
+ static final class MessageNode extends StackNode implements Comparable<MessageNode> {
+ private final Message mMessage;
+ volatile StackNode mNext;
+ StateNode mBottomOfStack;
+ boolean mWokeUp;
+ final long mInsertSeq;
+ private static final VarHandle sRemovedFromStack;
+ private volatile boolean mRemovedFromStackValue;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ sRemovedFromStack = l.findVarHandle(MessageQueue.MessageNode.class,
+ "mRemovedFromStackValue", boolean.class);
+ } catch (Exception e) {
+ Log.wtf(TAG_C, "VarHandle lookup failed with exception: " + e);
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ MessageNode(@NonNull Message message, long insertSeq) {
+ super(STACK_NODE_MESSAGE);
+ mMessage = message;
+ mInsertSeq = insertSeq;
+ }
+
+ long getWhen() {
+ return mMessage.when;
+ }
+
+ boolean isRemovedFromStack() {
+ return mRemovedFromStackValue;
+ }
+
+ boolean removeFromStack() {
+ return sRemovedFromStack.compareAndSet(this, false, true);
+ }
+
+ boolean isAsync() {
+ return mMessage.isAsynchronous();
+ }
+
+ boolean isBarrier() {
+ return mMessage.target == null;
+ }
+
+ @Override
+ public int compareTo(@NonNull MessageNode messageNode) {
+ Message other = messageNode.mMessage;
+
+ int compared = Long.compare(mMessage.when, other.when);
+ if (compared == 0) {
+ compared = Long.compare(mInsertSeq, messageNode.mInsertSeq);
+ }
+ return compared;
+ }
+ }
+
+ static class StateNode extends StackNode {
+ StateNode(int type) {
+ super(type);
+ }
+ }
+
+ static final class TimedParkStateNode extends StateNode {
+ long mWhenToWake;
+
+ TimedParkStateNode() {
+ super(STACK_NODE_TIMEDPARK);
+ }
+ }
+
+ private static final StateNode sStackStateActive = new StateNode(STACK_NODE_ACTIVE);
+ private static final StateNode sStackStateParked = new StateNode(STACK_NODE_PARKED);
+ private final TimedParkStateNode mStackStateTimedPark = new TimedParkStateNode();
+
+ /* This is the top of our treiber stack. */
+ private static final VarHandle sState;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ sState = l.findVarHandle(MessageQueue.class, "mStateValue",
+ MessageQueue.StackNode.class);
+ } catch (Exception e) {
+ Log.wtf(TAG_C, "VarHandle lookup failed with exception: " + e);
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ private volatile StackNode mStateValue = sStackStateParked;
+ private final ConcurrentSkipListSet<MessageNode> mPriorityQueue =
+ new ConcurrentSkipListSet<MessageNode>();
+ private final ConcurrentSkipListSet<MessageNode> mAsyncPriorityQueue =
+ new ConcurrentSkipListSet<MessageNode>();
+
+ /*
+ * This helps us ensure that messages with the same timestamp are inserted in FIFO order.
+ * Increments on each insert, starting at 0. MessageNode.compareTo() will compare sequences
+ * when delivery timestamps are identical.
+ */
+ private static final VarHandle sNextInsertSeq;
+ private volatile long mNextInsertSeqValue = 0;
+ /*
+ * The exception to the FIFO order rule is sendMessageAtFrontOfQueue().
+ * Those messages must be in LIFO order.
+ * Decrements on each front of queue insert.
+ */
+ private static final VarHandle sNextFrontInsertSeq;
+ private volatile long mNextFrontInsertSeqValue = -1;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ sNextInsertSeq = l.findVarHandle(MessageQueue.class, "mNextInsertSeqValue",
+ long.class);
+ sNextFrontInsertSeq = l.findVarHandle(MessageQueue.class, "mNextFrontInsertSeqValue",
+ long.class);
+ } catch (Exception e) {
+ Log.wtf(TAG_C, "VarHandle lookup failed with exception: " + e);
+ throw new ExceptionInInitializerError(e);
+ }
+
+ }
+
+ /*
+ * Tracks the number of queued and cancelled messages in our stack.
+ *
+ * On item cancellation, determine whether to wake next() to flush tombstoned messages.
+ * We track queued and cancelled counts as two ints packed into a single long.
+ */
+ private static final class MessageCounts {
+ private static VarHandle sCounts;
+ private volatile long mCountsValue = 0;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ sCounts = l.findVarHandle(MessageQueue.MessageCounts.class, "mCountsValue",
+ long.class);
+ } catch (Exception e) {
+ Log.wtf(TAG_C, "VarHandle lookup failed with exception: " + e);
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ /* We use a special value to indicate when next() has been woken for flush. */
+ private static final long AWAKE = Long.MAX_VALUE;
+ /*
+ * Minimum number of messages in the stack which we need before we consider flushing
+ * tombstoned items.
+ */
+ private static final int MESSAGE_FLUSH_THRESHOLD = 10;
+
+ private static int numQueued(long val) {
+ return (int) (val >>> Integer.SIZE);
+ }
+
+ private static int numCancelled(long val) {
+ return (int) val;
+ }
+
+ private static long combineCounts(int queued, int cancelled) {
+ return ((long) queued << Integer.SIZE) | (long) cancelled;
+ }
+
+ public void incrementQueued() {
+ while (true) {
+ long oldVal = mCountsValue;
+ int queued = numQueued(oldVal);
+ int cancelled = numCancelled(oldVal);
+ /* Use Math.max() to avoid overflow of queued count */
+ long newVal = combineCounts(Math.max(queued + 1, queued), cancelled);
+
+ /* Don't overwrite 'AWAKE' state */
+ if (oldVal == AWAKE || sCounts.compareAndSet(this, oldVal, newVal)) {
+ break;
+ }
+ }
+ }
+
+ public boolean incrementCancelled() {
+ while (true) {
+ long oldVal = mCountsValue;
+ if (oldVal == AWAKE) {
+ return false;
+ }
+ int queued = numQueued(oldVal);
+ int cancelled = numCancelled(oldVal);
+ boolean needsPurge = queued > MESSAGE_FLUSH_THRESHOLD
+ && (queued >> 1) < cancelled;
+ long newVal;
+ if (needsPurge) {
+ newVal = AWAKE;
+ } else {
+ newVal = combineCounts(queued,
+ Math.max(cancelled + 1, cancelled));
+ }
+
+ if (sCounts.compareAndSet(this, oldVal, newVal)) {
+ return needsPurge;
+ }
+ }
+ }
+
+ public void clearCounts() {
+ mCountsValue = 0;
+ }
+ }
+
+ private final MessageCounts mMessageCounts = new MessageCounts();
+
+ private final Object mIdleHandlersLock = new Object();
+ private final Object mFileDescriptorRecordsLock = new Object();
+
+ private static final VarHandle sQuitting;
+ private boolean mQuittingValue = false;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ sQuitting = l.findVarHandle(MessageQueue.class, "mQuittingValue", boolean.class);
+ } catch (Exception e) {
+ Log.wtf(TAG_C, "VarHandle lookup failed with exception: " + e);
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ // The next barrier token.
+ // Barriers are indicated by messages with a null target whose arg1 field carries the token.
+ private final AtomicInteger mNextBarrierToken = new AtomicInteger(1);
+
+ /* Protects mNextIsDrainingStack */
+ private final ReentrantLock mDrainingLock = new ReentrantLock();
+ private boolean mNextIsDrainingStack = false;
+ private final Condition mDrainCompleted = mDrainingLock.newCondition();
+
+ private boolean enqueueMessageUnchecked(@NonNull Message msg, long when) {
+ if ((boolean) sQuitting.getVolatile(this)) {
+ IllegalStateException e = new IllegalStateException(
+ msg.target + " sending message to a Handler on a dead thread");
+ Log.w(TAG_C, e.getMessage(), e);
+ msg.recycleUnchecked();
+ return false;
+ }
+
+ long seq = when != 0 ? ((long) sNextInsertSeq.getAndAdd(this, 1L) + 1L)
+ : ((long) sNextFrontInsertSeq.getAndAdd(this, -1L) - 1L);
+ /* TODO: Add a MessageNode member to Message so we can avoid this allocation */
+ MessageNode node = new MessageNode(msg, seq);
+ msg.when = when;
+ msg.markInUse();
+
+ if (DEBUG) {
+ Log.d(TAG_C, "Insert message what: " + msg.what + " when: " + msg.when + " seq: "
+ + node.mInsertSeq + " barrier: " + node.isBarrier() + " async: "
+ + node.isAsync() + " now: " + SystemClock.uptimeMillis());
+ }
+
+ final Looper myLooper = Looper.myLooper();
+ /* If we are running on the looper thread we can add directly to the priority queue */
+ if (myLooper != null && myLooper.getQueue() == this) {
+ node.removeFromStack();
+ insertIntoPriorityQueue(node);
+ /*
+ * We still need to do this even though we are the current thread,
+ * otherwise next() may sleep indefinitely.
+ */
+ if (!mMessageDirectlyQueued) {
+ mMessageDirectlyQueued = true;
+ nativeWake(mPtr);
+ }
+ return true;
+ }
+
+ while (true) {
+ StackNode old = (StackNode) sState.getVolatile(this);
+ boolean wakeNeeded;
+ boolean inactive;
+
+ node.mNext = old;
+ switch (old.getNodeType()) {
+ case STACK_NODE_ACTIVE:
+ /*
+ * The worker thread is currently active and will process any elements added to
+ * the stack before parking again.
+ */
+ node.mBottomOfStack = (StateNode) old;
+ inactive = false;
+ node.mWokeUp = true;
+ wakeNeeded = false;
+ break;
+
+ case STACK_NODE_PARKED:
+ node.mBottomOfStack = (StateNode) old;
+ inactive = true;
+ node.mWokeUp = true;
+ wakeNeeded = true;
+ break;
+
+ case STACK_NODE_TIMEDPARK:
+ node.mBottomOfStack = (StateNode) old;
+ inactive = true;
+ wakeNeeded = mStackStateTimedPark.mWhenToWake >= node.getWhen();
+ node.mWokeUp = wakeNeeded;
+ break;
+
+ default:
+ MessageNode oldMessage = (MessageNode) old;
+
+ node.mBottomOfStack = oldMessage.mBottomOfStack;
+ int bottomType = node.mBottomOfStack.getNodeType();
+ inactive = bottomType >= STACK_NODE_PARKED;
+ wakeNeeded = (bottomType == STACK_NODE_TIMEDPARK
+ && mStackStateTimedPark.mWhenToWake >= node.getWhen()
+ && !oldMessage.mWokeUp);
+ node.mWokeUp = oldMessage.mWokeUp || wakeNeeded;
+ break;
+ }
+ if (sState.compareAndSet(this, old, node)) {
+ if (inactive) {
+ if (wakeNeeded) {
+ nativeWake(mPtr);
+ } else {
+ mMessageCounts.incrementQueued();
+ }
+ }
+ return true;
+ }
+ }
+ }
+
+ /*
+ * This class is used to find matches for hasMessages() and removeMessages()
+ */
+ private abstract static class MessageCompare {
+ public abstract boolean compareMessage(Message m, Handler h, int what, Object object,
+ Runnable r, long when);
+ }
+
+ private boolean stackHasMessages(Handler h, int what, Object object, Runnable r, long when,
+ MessageCompare compare, boolean removeMatches) {
+ boolean found = false;
+ StackNode top = (StackNode) sState.getVolatile(this);
+ StateNode bottom = getStateNode(top);
+
+ /*
+ * If the top node is a state node, there are no reachable messages.
+ * If it's anything other than Active, we can quit as we know that next() is not
+ * consuming items.
+ * If the top node is Active then we know that next() is currently consuming items.
+ * In that case we should wait next() has drained the stack.
+ */
+ if (top == bottom) {
+ if (bottom != sStackStateActive) {
+ return false;
+ }
+ waitForDrainCompleted();
+ return false;
+ }
+
+ /*
+ * We have messages that we may tombstone. Walk the stack until we hit the bottom or we
+ * hit a null pointer.
+ * If we hit the bottom, we are done.
+ * If we hit a null pointer, then the stack is being consumed by next() and we must cycle
+ * until the stack has been drained.
+ */
+ MessageNode p = (MessageNode) top;
+
+ while (true) {
+ if (compare.compareMessage(p.mMessage, h, what, object, r, when)) {
+ found = true;
+ if (DEBUG) {
+ Log.d(TAG_C, "stackHasMessages node matches");
+ }
+ if (removeMatches) {
+ if (p.removeFromStack()) {
+ p.mMessage.recycleUnchecked();
+ if (mMessageCounts.incrementCancelled()) {
+ nativeWake(mPtr);
+ }
+ }
+ } else {
+ return true;
+ }
+ }
+
+ StackNode n = p.mNext;
+ if (n == null) {
+ /* Next() is walking the stack, we must re-sample */
+ if (DEBUG) {
+ Log.d(TAG_C, "stackHasMessages next() is walking the stack, we must re-sample");
+ }
+ waitForDrainCompleted();
+ break;
+ }
+ if (!n.isMessageNode()) {
+ /* We reached the end of the stack */
+ return found;
+ }
+ p = (MessageNode) n;
+ }
+
+ return found;
+ }
+
+ private boolean priorityQueueHasMessage(ConcurrentSkipListSet<MessageNode> queue, Handler h,
+ int what, Object object, Runnable r, long when, MessageCompare compare,
+ boolean removeMatches) {
+ Iterator<MessageNode> iterator = queue.iterator();
+ boolean found = false;
+
+ while (iterator.hasNext()) {
+ MessageNode msg = iterator.next();
+
+ if (compare.compareMessage(msg.mMessage, h, what, object, r, when)) {
+ if (removeMatches) {
+ found = true;
+ if (queue.remove(msg)) {
+ msg.mMessage.recycleUnchecked();
+ }
+ } else {
+ return true;
+ }
+ }
+ }
+ return found;
+ }
+
+ private boolean findOrRemoveMessages(Handler h, int what, Object object, Runnable r, long when,
+ MessageCompare compare, boolean removeMatches) {
+ boolean foundInStack, foundInQueue;
+
+ foundInStack = stackHasMessages(h, what, object, r, when, compare, removeMatches);
+ foundInQueue = priorityQueueHasMessage(mPriorityQueue, h, what, object, r, when, compare,
+ removeMatches);
+ foundInQueue |= priorityQueueHasMessage(mAsyncPriorityQueue, h, what, object, r, when,
+ compare, removeMatches);
+
+ return foundInStack || foundInQueue;
+ }
+
+}
diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
index 8eaadde..0efa02f 100644
--- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
+++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
@@ -382,6 +382,18 @@
}
}
+ private class MatchDeliverableMessages extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.when <= when) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchDeliverableMessages mMatchDeliverableMessages =
+ new MatchDeliverableMessages();
/**
* Returns true if the looper has no pending messages which are due to be processed.
*
@@ -390,6 +402,12 @@
* @return True if the looper is idle.
*/
public boolean isIdle() {
+ final long now = SystemClock.uptimeMillis();
+
+ if (stackHasMessages(null, 0, null, null, now, mMatchDeliverableMessages, false)) {
+ return false;
+ }
+
MessageNode msgNode = null;
MessageNode asyncMsgNode = null;
@@ -405,7 +423,6 @@
} catch (NoSuchElementException e) { }
}
- final long now = SystemClock.uptimeMillis();
if ((msgNode != null && msgNode.getWhen() <= now)
|| (asyncMsgNode != null && asyncMsgNode.getWhen() <= now)) {
return false;
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 89a5e5d..69540c42 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -417,6 +417,14 @@
*/
@SystemApi
@FlaggedApi(android.crashrecovery.flags.Flags.FLAG_ENABLE_CRASHRECOVERY)
+ public static @NonNull File getDataSystemDeviceProtectedDirectory() {
+ return buildPath(getDataDirectory(), "system_de");
+ }
+
+ /** Use {@link #getDataSystemDeviceProtectedDirectory()} instead.
+ * {@hide}
+ */
+ @Deprecated
public static @NonNull File getDataSystemDeDirectory() {
return buildPath(getDataDirectory(), "system_de");
}
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 60a9e05..b5ecc14 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -20,7 +20,7 @@
import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH;
import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__INTERNAL_NON_EXPORTED_COMPONENT_MATCH;
import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH;
-import static com.android.window.flags.Flags.balStrictMode;
+import static com.android.window.flags.Flags.balStrictModeRo;
import android.animation.ValueAnimator;
import android.annotation.FlaggedApi;
@@ -912,7 +912,7 @@
if (targetSdk >= Build.VERSION_CODES.S) {
detectUnsafeIntentLaunch();
}
- if (balStrictMode() && targetSdk > Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ if (balStrictModeRo() && targetSdk > Build.VERSION_CODES.VANILLA_ICE_CREAM) {
detectBlockedBackgroundActivityLaunch();
}
@@ -1168,7 +1168,7 @@
* the home button while the app tries to start a new activity.
*/
@SuppressWarnings("BuilderSetStyle")
- @FlaggedApi(Flags.FLAG_BAL_STRICT_MODE)
+ @FlaggedApi(Flags.FLAG_BAL_STRICT_MODE_RO)
public @NonNull Builder detectBlockedBackgroundActivityLaunch() {
return enable(DETECT_VM_BACKGROUND_ACTIVITY_LAUNCH_ABORTED);
}
@@ -1180,7 +1180,7 @@
* This disables the effect of {@link #detectBlockedBackgroundActivityLaunch()}.
*/
@SuppressWarnings("BuilderSetStyle")
- @FlaggedApi(Flags.FLAG_BAL_STRICT_MODE)
+ @FlaggedApi(Flags.FLAG_BAL_STRICT_MODE_RO)
public @NonNull Builder ignoreBlockedBackgroundActivityLaunch() {
return disable(DETECT_VM_BACKGROUND_ACTIVITY_LAUNCH_ABORTED);
}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 1d654e13..7944be1 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -239,6 +239,13 @@
}
flag {
+ name: "use_frozen_aware_remote_callback_list"
+ namespace: "permissions"
+ description: "Whether to use the new frozen-aware RemoteCallbackList API for op noted callbacks."
+ bug: "361157077"
+}
+
+flag {
name: "wallet_role_icon_property_enabled"
is_exported: true
namespace: "wallet_integration"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8efbc9c..d19681c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6332,6 +6332,27 @@
public static final String SCREEN_FLASH_NOTIFICATION_COLOR =
"screen_flash_notification_color_global";
+
+ /**
+ * A semi-colon separated list of Bluetooth hearing devices' local ambient volume.
+ * Each entry is encoded as a key=value list, separated by commas. Ex:
+ *
+ * "addr=XX:XX:XX:00:11,ambient=20,group_ambient=30;addr=XX:XX:XX:00:22,ambient=50"
+ *
+ * The following keys are supported:
+ * <pre>
+ * addr (String)
+ * ambient (int)
+ * group_ambient (int)
+ * control_expanded (boolean)
+ * </pre>
+ *
+ * Each entry must contains "addr" attribute, otherwise it'll be ignored.
+ * @hide
+ */
+ public static final String HEARING_DEVICE_LOCAL_AMBIENT_VOLUME =
+ "hearing_device_local_ambient_volume";
+
/**
* IMPORTANT: If you add a new public settings you also have to add it to
* PUBLIC_SETTINGS below. If the new setting is hidden you have to add
@@ -6476,6 +6497,7 @@
PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE);
PRIVATE_SETTINGS.add(MOUSE_REVERSE_VERTICAL_SCROLLING);
PRIVATE_SETTINGS.add(MOUSE_SWAP_PRIMARY_BUTTON);
+ PRIVATE_SETTINGS.add(HEARING_DEVICE_LOCAL_AMBIENT_VOLUME);
}
/**
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index e173255..24328eb 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -24,12 +24,12 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
import static android.service.notification.ZenAdapters.peopleTypeToPrioritySenders;
import static android.service.notification.ZenAdapters.prioritySendersToPeopleType;
import static android.service.notification.ZenAdapters.zenPolicyConversationSendersToNotificationPolicy;
-import static android.service.notification.ZenModeConfig.EventInfo.REPLY_YES_OR_MAYBE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_ALARMS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_CALLS;
@@ -56,6 +56,7 @@
import android.app.Flags;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
+import android.app.backup.BackupRestoreEventLogger;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -957,8 +958,9 @@
}
}
- public static ZenModeConfig readXml(TypedXmlPullParser parser)
- throws XmlPullParserException, IOException {
+ public static ZenModeConfig readXml(TypedXmlPullParser parser,
+ @Nullable BackupRestoreEventLogger logger) throws XmlPullParserException, IOException {
+ int readRuleCount = 0;
int type = parser.getEventType();
if (type != XmlPullParser.START_TAG) return null;
String tag = parser.getName();
@@ -1048,6 +1050,8 @@
readManualRule = true;
if (rt.manualRule.zenPolicy == null) {
readManualRuleWithoutPolicy = true;
+ } else {
+ readRuleCount++;
}
} else if (AUTOMATIC_TAG.equals(tag)
|| (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag))) {
@@ -1062,6 +1066,7 @@
}
} else if (AUTOMATIC_TAG.equals(tag)) {
rt.automaticRules.put(id, automaticRule);
+ readRuleCount++;
}
}
} else if (STATE_TAG.equals(tag)) {
@@ -1085,8 +1090,17 @@
}
rt.manualRule.condition = new Condition(rt.manualRule.conditionId, "",
Condition.STATE_TRUE);
+ readRuleCount++;
}
}
+
+ if (!Flags.modesUi()){
+ readRuleCount++;
+ }
+
+ if (logger != null) {
+ logger.logItemsRestored(DATA_TYPE_ZEN_RULES, readRuleCount);
+ }
return rt;
}
}
@@ -1110,8 +1124,9 @@
* @throws IOException
*/
- public void writeXml(TypedXmlSerializer out, Integer version, boolean forBackup)
- throws IOException {
+ public void writeXml(TypedXmlSerializer out, Integer version, boolean forBackup,
+ @Nullable BackupRestoreEventLogger logger) throws IOException {
+ int writtenRuleCount = 0;
int xmlVersion = getCurrentXmlVersion();
out.startTag(null, ZEN_TAG);
out.attribute(null, ZEN_ATT_VERSION, version == null
@@ -1147,6 +1162,7 @@
writeRuleXml(manualRule, out, forBackup);
out.endTag(null, MANUAL_TAG);
}
+ writtenRuleCount++;
final int N = automaticRules.size();
for (int i = 0; i < N; i++) {
final String id = automaticRules.keyAt(i);
@@ -1155,6 +1171,7 @@
out.attribute(null, RULE_ATT_ID, id);
writeRuleXml(automaticRule, out, forBackup);
out.endTag(null, AUTOMATIC_TAG);
+ writtenRuleCount++;
}
if (Flags.modesApi() && !forBackup) {
for (int i = 0; i < deletedRules.size(); i++) {
@@ -1171,6 +1188,9 @@
out.endTag(null, STATE_TAG);
out.endTag(null, ZEN_TAG);
+ if (logger != null) {
+ logger.logItemsBackedUp(DATA_TYPE_ZEN_RULES, writtenRuleCount);
+ }
}
@NonNull
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 09b2201..e830d89 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -195,3 +195,10 @@
description: "Feature flag for adding a TYPE_DURATION to TtsSpan"
bug: "337103893"
}
+
+flag {
+ name: "deprecate_elegant_text_height_api"
+ namespace: "text"
+ description: "Deprecate the Paint#elegantTextHeight API and stick it to true"
+ bug: "349519475"
+}
diff --git a/core/java/android/tracing/TEST_MAPPING b/core/java/android/tracing/TEST_MAPPING
index b51d19d..545ba11 100644
--- a/core/java/android/tracing/TEST_MAPPING
+++ b/core/java/android/tracing/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "TracingTests"
},
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index e940e55bd..910e644 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -22,6 +22,7 @@
import static com.android.server.display.feature.flags.Flags.FLAG_HIGHEST_HDR_SDR_RATIO_API;
import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_HAS_ARR_SUPPORT;
+import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_GET_SUGGESTED_FRAME_RATE;
import android.Manifest;
import android.annotation.FlaggedApi;
@@ -1278,6 +1279,60 @@
}
}
+ @FlaggedApi(FLAG_ENABLE_GET_SUGGESTED_FRAME_RATE)
+ public static final int FRAME_RATE_CATEGORY_NORMAL = 0;
+ @FlaggedApi(FLAG_ENABLE_GET_SUGGESTED_FRAME_RATE)
+ public static final int FRAME_RATE_CATEGORY_HIGH = 1;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"FRAME_RATE_CATEGORY_"},
+ value = {
+ FRAME_RATE_CATEGORY_NORMAL,
+ FRAME_RATE_CATEGORY_HIGH
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FrameRateCategory {}
+
+ /**
+ * <p> Gets the display-defined frame rate given a descriptive frame rate category. </p>
+ *
+ * <p> For example, an animation that does not require fast render rates can use
+ * the {@link #FRAME_RATE_CATEGORY_NORMAL} to get the suggested frame rate.
+ * The suggested frame rate then can be used in the
+ * {@link Surface.FrameRateParams.Builder#setDesiredRateRange} for desiredMinRate.
+ *
+ * <pre>{@code
+ * float desiredMinRate = display.getSuggestedFrameRate(FRAME_RATE_CATEGORY_NORMAL);
+ * Surface.FrameRateParams params = new Surface.FrameRateParams.Builder().
+ * setDesiredRateRange(desiredMinRate, Float.MAX).build();
+ * surface.setFrameRate(params);
+ * }</pre>
+ * </p>
+ *
+ * @param category either {@link #FRAME_RATE_CATEGORY_NORMAL}
+ * or {@link #FRAME_RATE_CATEGORY_HIGH}
+ *
+ * @see Surface#setFrameRate(Surface.FrameRateParams)
+ * @see SurfaceControl.Transaction#setFrameRate(SurfaceControl, Surface.FrameRateParams)
+ * @throws IllegalArgumentException when category is not {@link #FRAME_RATE_CATEGORY_NORMAL}
+ * or {@link #FRAME_RATE_CATEGORY_HIGH}
+ */
+ @FlaggedApi(FLAG_ENABLE_GET_SUGGESTED_FRAME_RATE)
+ public float getSuggestedFrameRate(@FrameRateCategory int category) {
+ synchronized (mLock) {
+ updateDisplayInfoLocked();
+ if (category == FRAME_RATE_CATEGORY_HIGH) {
+ return mDisplayInfo.frameRateCategoryRate.getHigh();
+ } else if (category == FRAME_RATE_CATEGORY_NORMAL) {
+ return mDisplayInfo.frameRateCategoryRate.getNormal();
+ } else {
+ throw new IllegalArgumentException("Invalid FrameRateCategory provided");
+ }
+ }
+ }
+
/**
* <p> Returns true if the connected display can be switched into a mode with minimal
* post processing. </p>
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 26fce90..8f112f3 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -204,6 +204,12 @@
public boolean hasArrSupport;
/**
+ * Represents frame rate for the FrameRateCategory Normal and High.
+ * @see android.view.Display#getSuggestedFrameRate(int) for more details.
+ */
+ public FrameRateCategoryRate frameRateCategoryRate;
+
+ /**
* The default display mode.
*/
public int defaultModeId;
@@ -443,6 +449,7 @@
&& modeId == other.modeId
&& renderFrameRate == other.renderFrameRate
&& hasArrSupport == other.hasArrSupport
+ && Objects.equals(frameRateCategoryRate, other.frameRateCategoryRate)
&& defaultModeId == other.defaultModeId
&& userPreferredModeId == other.userPreferredModeId
&& Arrays.equals(supportedModes, other.supportedModes)
@@ -505,6 +512,7 @@
modeId = other.modeId;
renderFrameRate = other.renderFrameRate;
hasArrSupport = other.hasArrSupport;
+ frameRateCategoryRate = other.frameRateCategoryRate;
defaultModeId = other.defaultModeId;
userPreferredModeId = other.userPreferredModeId;
supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length);
@@ -562,6 +570,8 @@
modeId = source.readInt();
renderFrameRate = source.readFloat();
hasArrSupport = source.readBoolean();
+ frameRateCategoryRate = source.readParcelable(null,
+ android.view.FrameRateCategoryRate.class);
defaultModeId = source.readInt();
userPreferredModeId = source.readInt();
int nModes = source.readInt();
@@ -636,6 +646,7 @@
dest.writeInt(modeId);
dest.writeFloat(renderFrameRate);
dest.writeBoolean(hasArrSupport);
+ dest.writeParcelable(frameRateCategoryRate, flags);
dest.writeInt(defaultModeId);
dest.writeInt(userPreferredModeId);
dest.writeInt(supportedModes.length);
@@ -883,6 +894,8 @@
sb.append(renderFrameRate);
sb.append(", hasArrSupport ");
sb.append(hasArrSupport);
+ sb.append(", frameRateCategoryRate ");
+ sb.append(frameRateCategoryRate);
sb.append(", defaultMode ");
sb.append(defaultModeId);
sb.append(", userPreferredModeId ");
diff --git a/core/java/android/view/FrameRateCategoryRate.java b/core/java/android/view/FrameRateCategoryRate.java
new file mode 100644
index 0000000..3c674b8
--- /dev/null
+++ b/core/java/android/view/FrameRateCategoryRate.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class to create and manage FrameRateCategoryRate for
+ * categories Normal and High.
+ * @hide
+ */
+public class FrameRateCategoryRate implements Parcelable {
+
+ private final float mNormal;
+ private final float mHigh;
+
+ /**
+ * Creates a FrameRateCategoryRate with the provided rates
+ * for the categories Normal and High respectively;
+ *
+ * @param normal rate for the category Normal.
+ * @param high rate for the category High.
+ * @hide
+ */
+ public FrameRateCategoryRate(float normal, float high) {
+ this.mNormal = normal;
+ this.mHigh = high;
+ }
+
+ /**
+ * @return the value for the category normal;
+ * @hide
+ */
+ public float getNormal() {
+ return mNormal;
+ }
+
+ /**
+ * @return the value for the category high;
+ * @hide
+ */
+ public float getHigh() {
+ return mHigh;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof FrameRateCategoryRate)) {
+ return false;
+ }
+ FrameRateCategoryRate that = (FrameRateCategoryRate) o;
+ return mNormal == that.mNormal && mHigh == that.mHigh;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 31 * result + Float.hashCode(mNormal);
+ result = 31 * result + Float.hashCode(mHigh);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "FrameRateCategoryRate {"
+ + "normal=" + mNormal
+ + ", high=" + mHigh
+ + '}';
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(mNormal);
+ dest.writeFloat(mHigh);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<FrameRateCategoryRate> CREATOR =
+ new Creator<>() {
+ @Override
+ public FrameRateCategoryRate createFromParcel(Parcel in) {
+ return new FrameRateCategoryRate(in.readFloat(), in.readFloat());
+ }
+
+ @Override
+ public FrameRateCategoryRate[] newArray(int size) {
+ return new FrameRateCategoryRate[size];
+ }
+ };
+}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index ece2a60..0d55544 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -1798,6 +1798,7 @@
public int activeDisplayModeId;
public float renderFrameRate;
public boolean hasArrSupport;
+ public FrameRateCategoryRate frameRateCategoryRate;
public int[] supportedColorModes;
public int activeColorMode;
@@ -1816,6 +1817,7 @@
+ ", activeDisplayModeId=" + activeDisplayModeId
+ ", renderFrameRate=" + renderFrameRate
+ ", hasArrSupport=" + hasArrSupport
+ + ", frameRateCategoryRate=" + frameRateCategoryRate
+ ", supportedColorModes=" + Arrays.toString(supportedColorModes)
+ ", activeColorMode=" + activeColorMode
+ ", hdrCapabilities=" + hdrCapabilities
@@ -1836,13 +1838,15 @@
&& activeColorMode == that.activeColorMode
&& Objects.equals(hdrCapabilities, that.hdrCapabilities)
&& preferredBootDisplayMode == that.preferredBootDisplayMode
- && hasArrSupport == that.hasArrSupport;
+ && hasArrSupport == that.hasArrSupport
+ && Objects.equals(frameRateCategoryRate, that.frameRateCategoryRate);
}
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(supportedDisplayModes), activeDisplayModeId,
- renderFrameRate, activeColorMode, hdrCapabilities, hasArrSupport);
+ renderFrameRate, activeColorMode, hdrCapabilities, hasArrSupport,
+ frameRateCategoryRate);
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index d57a880..3ce6870 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1132,6 +1132,8 @@
// time for evaluating the interval between current time and
// the time when frame rate was set previously.
private static final int FRAME_RATE_SETTING_REEVALUATE_TIME = 100;
+ // timeout for surface replaced.
+ private static final int FRAME_RATE_SURFACE_REPLACED_TIME = 3000;
/*
* The variables below are used to update frame rate category
@@ -3831,6 +3833,9 @@
if (surfaceReplaced) {
mSurfaceReplaced = true;
mSurfaceSequenceId++;
+ mHandler.removeMessages(MSG_SURFACE_REPLACED_TIMEOUT);
+ mHandler.sendEmptyMessageDelayed(MSG_SURFACE_REPLACED_TIMEOUT,
+ FRAME_RATE_SURFACE_REPLACED_TIME);
}
if (alwaysConsumeSystemBarsChanged) {
mAttachInfo.mAlwaysConsumeSystemBars = mPendingAlwaysConsumeSystemBars;
@@ -6544,7 +6549,7 @@
}
Configuration globalConfig = mergedConfiguration.getGlobalConfiguration();
- final Configuration overrideConfig = mergedConfiguration.getOverrideConfiguration();
+ Configuration overrideConfig = mergedConfiguration.getOverrideConfiguration();
if (DEBUG_CONFIGURATION) Log.v(mTag,
"Applying new config to window " + mWindowAttributes.getTitle()
+ ", globalConfig: " + globalConfig
@@ -6553,7 +6558,9 @@
final CompatibilityInfo ci = mDisplay.getDisplayAdjustments().getCompatibilityInfo();
if (!ci.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) {
globalConfig = new Configuration(globalConfig);
+ overrideConfig = new Configuration(overrideConfig);
ci.applyToConfiguration(mNoncompatDensity, globalConfig);
+ ci.applyToConfiguration(mNoncompatDensity, overrideConfig);
}
synchronized (sConfigCallbacks) {
@@ -6694,6 +6701,7 @@
private static final int MSG_CHECK_INVALIDATION_IDLE = 40;
private static final int MSG_REFRESH_POINTER_ICON = 41;
private static final int MSG_FRAME_RATE_SETTING = 42;
+ private static final int MSG_SURFACE_REPLACED_TIMEOUT = 43;
final class ViewRootHandler extends Handler {
@Override
@@ -6765,6 +6773,8 @@
return "MSG_TOUCH_BOOST_TIMEOUT";
case MSG_FRAME_RATE_SETTING:
return "MSG_FRAME_RATE_SETTING";
+ case MSG_SURFACE_REPLACED_TIMEOUT:
+ return "MSG_SURFACE_REPLACED_TIMEOUT";
}
return super.getMessageName(message);
}
@@ -7036,6 +7046,9 @@
*/
mIsFrameRateBoosting = false;
mIsTouchBoosting = false;
+ if (!mDrawnThisFrame) {
+ setPreferredFrameRateCategory(FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ }
break;
case MSG_REFRESH_POINTER_ICON:
if (mPointerIconEvent == null) {
@@ -7047,6 +7060,9 @@
mPreferredFrameRate = 0;
mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
break;
+ case MSG_SURFACE_REPLACED_TIMEOUT:
+ mSurfaceReplaced = false;
+ break;
}
}
}
@@ -9323,7 +9339,7 @@
boolean insetsPending) throws RemoteException {
final WindowConfiguration winConfigFromAm = getConfiguration().windowConfiguration;
final WindowConfiguration winConfigFromWm =
- mLastReportedMergedConfiguration.getGlobalConfiguration().windowConfiguration;
+ mLastReportedMergedConfiguration.getMergedConfiguration().windowConfiguration;
final WindowConfiguration winConfig = getCompatWindowConfiguration();
final int measuredWidth = mMeasuredWidth;
final int measuredHeight = mMeasuredHeight;
@@ -13390,6 +13406,7 @@
private void removeVrrMessages() {
mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT);
mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
+ mHandler.removeMessages(MSG_SURFACE_REPLACED_TIMEOUT);
if (mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) {
mInvalidationIdleMessagePosted = false;
mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE);
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 820a1fb..ffa819f 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -40,6 +40,13 @@
}
flag {
+ name: "a11y_character_in_window_api"
+ namespace: "accessibility"
+ description: "Enables new extra data key for an AccessibilityService to request character bounds in unmagnified window coordinates."
+ bug: "375429616"
+}
+
+flag {
namespace: "accessibility"
name: "allow_shortcut_chooser_on_lockscreen"
description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen"
diff --git a/core/java/android/webkit/IWebViewUpdateService.aidl b/core/java/android/webkit/IWebViewUpdateService.aidl
index aeb746c..39092fe 100644
--- a/core/java/android/webkit/IWebViewUpdateService.aidl
+++ b/core/java/android/webkit/IWebViewUpdateService.aidl
@@ -72,16 +72,6 @@
PackageInfo getCurrentWebViewPackage();
/**
- * Used by Settings to determine whether multiprocess is enabled.
- */
- boolean isMultiProcessEnabled();
-
- /**
- * Used by Settings to enable/disable multiprocess.
- */
- void enableMultiProcess(boolean enable);
-
- /**
* Used by Settings to get the default WebView package.
*/
WebViewProviderInfo getDefaultWebViewPackage();
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 4c5802c..778a51e 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -28,7 +28,6 @@
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
-import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.Trace;
import android.util.SparseArray;
@@ -217,19 +216,7 @@
* Returns whether WebView should run in multiprocess mode.
*/
public boolean isMultiProcessEnabled() {
- if (Flags.updateServiceV2()) {
- return true;
- } else if (Flags.updateServiceIpcWrapper()) {
- // We don't want to support this method in the new wrapper because updateServiceV2 is
- // intended to ship in the same release (or sooner). It's only possible to disable it
- // with an obscure adb command, so just return true here too.
- return true;
- }
- try {
- return WebViewFactory.getUpdateService().isMultiProcessEnabled();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return true;
}
/**
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index e10a398..de303f8 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -16,8 +16,6 @@
package android.webkit;
-import static android.webkit.Flags.updateServiceV2;
-
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.UptimeMillisLong;
@@ -490,7 +488,7 @@
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
- if (updateServiceV2() && !isInstalledPackage(newPackageInfo)) {
+ if (!isInstalledPackage(newPackageInfo)) {
throw new MissingWebViewPackageException(
TextUtils.formatSimple(
"Current WebView Package (%s) is not installed for the current "
@@ -498,7 +496,7 @@
newPackageInfo.packageName));
}
- if (updateServiceV2() && !isEnabledPackage(newPackageInfo)) {
+ if (!isEnabledPackage(newPackageInfo)) {
throw new MissingWebViewPackageException(
TextUtils.formatSimple(
"Current WebView Package (%s) is not enabled for the current user",
diff --git a/core/java/android/webkit/WebViewUpdateService.java b/core/java/android/webkit/WebViewUpdateService.java
index 644d917..a26ba3f 100644
--- a/core/java/android/webkit/WebViewUpdateService.java
+++ b/core/java/android/webkit/WebViewUpdateService.java
@@ -16,14 +16,18 @@
package android.webkit;
+import android.annotation.FlaggedApi;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.RemoteException;
/**
+ * @deprecated Use the {@link WebViewUpdateManager} class instead.
* @hide
*/
+@FlaggedApi(Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER)
+@Deprecated
@SystemApi
public final class WebViewUpdateService {
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 668cd01..10d16af 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -16,8 +16,6 @@
package android.webkit;
-import static android.webkit.Flags.updateServiceV2;
-
import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.ChildZygoteProcess;
@@ -52,13 +50,6 @@
@GuardedBy("sLock")
private static PackageInfo sPackage;
- /**
- * Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote will
- * not be started. Should be removed entirely after we remove the updateServiceV2 flag.
- */
- @GuardedBy("sLock")
- private static boolean sMultiprocessEnabled = false;
-
public static ZygoteProcess getProcess() {
synchronized (sLock) {
if (sZygote != null) return sZygote;
@@ -76,40 +67,13 @@
public static boolean isMultiprocessEnabled() {
synchronized (sLock) {
- if (updateServiceV2()) {
- return sPackage != null;
- } else {
- return sMultiprocessEnabled && sPackage != null;
- }
- }
- }
-
- public static void setMultiprocessEnabled(boolean enabled) {
- if (updateServiceV2()) {
- throw new IllegalStateException(
- "setMultiprocessEnabled shouldn't be called if update_service_v2 flag is set.");
- }
- synchronized (sLock) {
- sMultiprocessEnabled = enabled;
-
- // When multi-process is disabled, kill the zygote. When it is enabled,
- // the zygote will be started when it is first needed in getProcess().
- if (!enabled) {
- stopZygoteLocked();
- }
+ return sPackage != null;
}
}
static void onWebViewProviderChanged(PackageInfo packageInfo) {
synchronized (sLock) {
sPackage = packageInfo;
-
- // If multi-process is not enabled, then do not start the zygote service.
- // Only check sMultiprocessEnabled if updateServiceV2 is not enabled.
- if (!updateServiceV2() && !sMultiprocessEnabled) {
- return;
- }
-
stopZygoteLocked();
}
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 9b6311f..e6eec37 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -20,6 +20,7 @@
import static android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO;
import static android.appwidget.flags.Flags.drawDataParcel;
import static android.appwidget.flags.Flags.remoteAdapterConversion;
+import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
import static android.util.proto.ProtoInputStream.NO_MORE_FIELDS;
import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
@@ -54,6 +55,10 @@
import android.content.Intent;
import android.content.IntentSender;
import android.content.ServiceConnection;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.ColorStateList;
@@ -79,14 +84,12 @@
import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.StrictMode;
import android.os.Trace;
import android.os.UserHandle;
-import android.system.Os;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
@@ -128,11 +131,8 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -8552,12 +8552,8 @@
* @hide
*/
public static final class ColorResources {
- // Set of valid colors resources.
- private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_neutral1_0;
- private static final int LAST_RESOURCE_COLOR_ID =
- android.R.color.system_error_1000;
- // Size, in bytes, of an entry in the array of colors in an ARSC file.
- private static final int ARSC_ENTRY_SIZE = 16;
+ private static final String OVERLAY_NAME = "remote_views_color_resources";
+ private static final String OVERLAY_TARGET_PACKAGE_NAME = "android";
private final ResourcesLoader mLoader;
private final SparseIntArray mColorMapping;
@@ -8591,44 +8587,6 @@
}
/**
- * Creates the compiled resources content from the asset stored in the APK.
- *
- * The asset is a compiled resource with the correct resources name and correct ids, only
- * the values are incorrect. The last value is at the very end of the file. The resources
- * are in an array, the array's entries are 16 bytes each. We use this to work out the
- * location of all the positions of the various resources.
- */
- @Nullable
- private static byte[] createCompiledResourcesContent(Context context,
- SparseIntArray colorResources) throws IOException {
- byte[] content;
- try (InputStream input = context.getResources().openRawResource(
- com.android.internal.R.raw.remote_views_color_resources)) {
- ByteArrayOutputStream rawContent = readFileContent(input);
- content = rawContent.toByteArray();
- }
- int valuesOffset =
- content.length - (LAST_RESOURCE_COLOR_ID & 0xffff) * ARSC_ENTRY_SIZE - 4;
- if (valuesOffset < 0) {
- Log.e(LOG_TAG, "ARSC file for theme colors is invalid.");
- return null;
- }
- for (int colorRes = FIRST_RESOURCE_COLOR_ID; colorRes <= LAST_RESOURCE_COLOR_ID;
- colorRes++) {
- // The last 2 bytes are the index in the color array.
- int index = colorRes & 0xffff;
- int offset = valuesOffset + index * ARSC_ENTRY_SIZE;
- int value = colorResources.get(colorRes, context.getColor(colorRes));
- // Write the 32 bit integer in little endian
- for (int b = 0; b < 4; b++) {
- content[offset + b] = (byte) (value & 0xff);
- value >>= 8;
- }
- }
- return content;
- }
-
- /**
* Adds a resource loader for theme colors to the given context.
*
* @param context Context of the view hosting the widget.
@@ -8639,31 +8597,38 @@
@Nullable
public static ColorResources create(Context context, SparseIntArray colorMapping) {
try {
- byte[] contentBytes = createCompiledResourcesContent(context, colorMapping);
- if (contentBytes == null) {
+ String owningPackage = context.getPackageName();
+ FabricatedOverlay overlay = new FabricatedOverlay.Builder(owningPackage,
+ OVERLAY_NAME, OVERLAY_TARGET_PACKAGE_NAME).build();
+
+ for (int i = 0; i < colorMapping.size(); i++) {
+ overlay.setResourceValue(
+ context.getResources().getResourceName(colorMapping.keyAt(i)),
+ TYPE_INT_COLOR_ARGB8, colorMapping.valueAt(i), null);
+ }
+ OverlayManager overlayManager = context.getSystemService(OverlayManager.class);
+ OverlayManagerTransaction.Builder transaction =
+ new OverlayManagerTransaction.Builder()
+ .registerFabricatedOverlay(overlay)
+ .setSelfTargeting(true);
+ overlayManager.commit(transaction.build());
+
+ OverlayInfo overlayInfo =
+ overlayManager.getOverlayInfosForTarget(OVERLAY_TARGET_PACKAGE_NAME)
+ .stream()
+ .filter(info -> TextUtils.equals(info.overlayName, OVERLAY_NAME)
+ && TextUtils.equals(info.packageName, owningPackage))
+ .findFirst()
+ .orElse(null);
+ if (overlayInfo == null) {
+ Log.e(LOG_TAG, "Failed to get overlay info ", new Throwable());
return null;
}
- FileDescriptor arscFile = null;
- try {
- arscFile = Os.memfd_create("remote_views_theme_colors.arsc", 0 /* flags */);
- // Note: This must not be closed through the OutputStream.
- try (OutputStream pipeWriter = new FileOutputStream(arscFile)) {
- pipeWriter.write(contentBytes);
-
- try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(arscFile)) {
- ResourcesLoader colorsLoader = new ResourcesLoader();
- colorsLoader.addProvider(ResourcesProvider
- .loadFromTable(pfd, null /* assetsProvider */));
- return new ColorResources(colorsLoader, colorMapping.clone());
- }
- }
- } finally {
- if (arscFile != null) {
- Os.close(arscFile);
- }
- }
- } catch (Exception ex) {
- Log.e(LOG_TAG, "Failed to setup the context for theme colors", ex);
+ ResourcesLoader colorsLoader = new ResourcesLoader();
+ colorsLoader.addProvider(ResourcesProvider.loadOverlay(overlayInfo));
+ return new ColorResources(colorsLoader, colorMapping.clone());
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Failed to add theme color overlay into loader", e);
}
return null;
}
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index 6cefc4d..4b7bacb 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -87,6 +87,13 @@
*/
public static final String KEY_GESTURE_FINISHED = "GestureFinished";
+ /**
+ * Touch gestured has transferred to embedded window, Shell should pilfer pointers so the
+ * embedded won't receive motion events.
+ * @hide
+ */
+ public static final String KEY_TOUCH_GESTURE_TRANSFERRED = "TouchGestureTransferred";
+
/**
* Defines the type of back destinations a back even can lead to. This is used to define the
@@ -119,7 +126,7 @@
@NonNull
private final Rect mTouchableRegion;
- private final boolean mAppProgressGenerationAllowed;
+ private boolean mAppProgressGenerationAllowed;
private final int mFocusedTaskId;
/**
@@ -253,6 +260,14 @@
}
/**
+ * Force disable app to intercept back progress event.
+ * @hide
+ */
+ public void disableAppProgressGenerationAllowed() {
+ mAppProgressGenerationAllowed = false;
+ }
+
+ /**
* Callback to be called when the back preview is finished in order to notify the server that
* it can clean up the resources created for the animation.
* @hide
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index 57aacff..a2201bb 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -71,6 +71,7 @@
bug: "339720406"
}
+# replaced by bal_strict_mode_ro
flag {
name: "bal_strict_mode"
namespace: "responsible_apis"
@@ -79,6 +80,14 @@
}
flag {
+ name: "bal_strict_mode_ro"
+ namespace: "responsible_apis"
+ description: "Strict mode flag"
+ bug: "324089586"
+ is_fixed_read_only: true
+}
+
+flag {
name: "bal_reduce_grace_period"
namespace: "responsible_apis"
description: "Changes to reduce or ideally remove the grace period exemption."
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index ed7a796..3a03508 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -348,3 +348,14 @@
bug: "372230928"
is_fixed_read_only: true
}
+
+flag {
+ name: "disallow_app_progress_embedded_window"
+ namespace: "windowing_frontend"
+ description: "Pilfer pointers when app transfer input gesture to embedded window."
+ bug: "365504126"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java
index 07e178c..38593b4 100644
--- a/core/java/com/android/internal/content/om/OverlayConfig.java
+++ b/core/java/com/android/internal/content/om/OverlayConfig.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackagePartitions;
+import android.content.res.AssetManager;
import android.os.Build;
import android.os.Trace;
import android.util.ArrayMap;
@@ -533,7 +534,7 @@
*/
@NonNull
public String[] createImmutableFrameworkIdmapsInZygote() {
- final String targetPath = "/system/framework/framework-res.apk";
+ final String targetPath = AssetManager.FRAMEWORK_APK_PATH;
final ArrayList<String> idmapPaths = new ArrayList<>();
final ArrayList<IdmapInvocation> idmapInvocations =
getImmutableFrameworkOverlayIdmapInvocations();
diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
index c462449..fa5cf2a 100644
--- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java
+++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
@@ -35,6 +35,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.parsing.FrameworkParsingPackageUtils;
+import android.content.res.AssetManager;
import android.os.FabricatedOverlayInfo;
import android.os.FabricatedOverlayInternal;
import android.os.FabricatedOverlayInternalEntry;
@@ -60,8 +61,8 @@
import java.util.Objects;
/**
- * This class provides the functionalities of registering an overlay, unregistering an overlay, and
- * getting the list of overlays information.
+ * This class provides the functionalities for managing self-targeting overlays, including
+ * registering an overlay, unregistering an overlay, and getting the list of overlays information.
*/
public class OverlayManagerImpl {
private static final String TAG = "OverlayManagerImpl";
@@ -234,14 +235,17 @@
Preconditions.checkArgument(!entryList.isEmpty(), "overlay entries shouldn't be empty");
final String overlayName = checkOverlayNameValid(overlayInternal.overlayName);
checkPackageName(overlayInternal.packageName);
- checkPackageName(overlayInternal.targetPackageName);
- Preconditions.checkStringNotEmpty(
- overlayInternal.targetOverlayable,
- "Target overlayable should be neither null nor empty string.");
+ Preconditions.checkStringNotEmpty(overlayInternal.targetPackageName);
final ApplicationInfo applicationInfo = mContext.getApplicationInfo();
- final String targetPackage = Preconditions.checkStringNotEmpty(
- applicationInfo.getBaseCodePath());
+ String targetPackage = null;
+ if (TextUtils.equals(overlayInternal.targetPackageName, "android")) {
+ targetPackage = AssetManager.FRAMEWORK_APK_PATH;
+ } else {
+ targetPackage = Preconditions.checkStringNotEmpty(
+ applicationInfo.getBaseCodePath());
+ }
+
final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION);
final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION);
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 2acda8a..e402ddf 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -249,6 +249,10 @@
return isExperimentEnabled("profilesystemserver");
}
+ private static boolean shouldProfileBootClasspath() {
+ return isExperimentEnabled("profilebootclasspath");
+ }
+
/**
* Performs Zygote process initialization. Loads and initializes commonly used classes.
*
@@ -352,7 +356,7 @@
// If we are profiling the boot image, reset the Jit counters after preloading the
// classes. We want to preload for performance, and we can use method counters to
// infer what clases are used after calling resetJitCounters, for profile purposes.
- if (isExperimentEnabled("profilebootclasspath")) {
+ if (shouldProfileBootClasspath()) {
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ResetJitCounters");
VMRuntime.resetJitCounters();
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
@@ -460,12 +464,28 @@
? String.join(":", systemServerClasspath, standaloneSystemServerJars)
: systemServerClasspath;
prepareSystemServerProfile(systemServerPaths);
+ try {
+ SystemProperties.set("debug.tracing.profile_system_server", "1");
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Failed to set debug.tracing.profile_system_server", e);
+ }
} catch (Exception e) {
Log.wtf(TAG, "Failed to set up system server profile", e);
}
}
}
+ // Zygote can't set system properties due to permission denied. We need to be in System
+ // Server to set system properties, so we do it here instead of the more natural place in
+ // preloadClasses.
+ if (shouldProfileBootClasspath()) {
+ try {
+ SystemProperties.set("debug.tracing.profile_boot_classpath", "1");
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Failed to set debug.tracing.profile_boot_classpath", e);
+ }
+ }
+
if (parsedArgs.mInvokeWith != null) {
String[] args = parsedArgs.mRemainingArgs;
// If we have a non-null system server class path, we'll have to duplicate the
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index 032ac42..c182b4a 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -421,6 +421,8 @@
@NonNull
private String[] mUsesStaticLibrariesSorted;
+ private Map<String, Boolean> mFeatureFlagState = new ArrayMap<>();
+
@NonNull
public static PackageImpl forParsing(@NonNull String packageName, @NonNull String baseCodePath,
@NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp,
@@ -2819,6 +2821,7 @@
queriesProviders = Collections.unmodifiableSet(queriesProviders);
mimeGroups = Collections.unmodifiableSet(mimeGroups);
mKnownActivityEmbeddingCerts = Collections.unmodifiableSet(mKnownActivityEmbeddingCerts);
+ mFeatureFlagState = Collections.unmodifiableMap(mFeatureFlagState);
}
@Override
@@ -3118,6 +3121,8 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
+ writeFeatureFlagState(dest);
+
sForBoolean.parcel(this.supportsSmallScreens, dest, flags);
sForBoolean.parcel(this.supportsNormalScreens, dest, flags);
sForBoolean.parcel(this.supportsLargeScreens, dest, flags);
@@ -3267,6 +3272,27 @@
dest.writeBoolean(this.mAllowCrossUidActivitySwitchFromBelow);
}
+ private void writeFeatureFlagState(@NonNull Parcel dest) {
+ // Use a string array to encode flag state. One string per flag in the form `<flag>=<value>`
+ // where value is 0 (disabled), 1 (enabled) or ? (unknown flag or value).
+ int featureFlagCount = this.mFeatureFlagState.size();
+ String[] featureFlagStateAsArray = new String[featureFlagCount];
+ var entryIterator = this.mFeatureFlagState.entrySet().iterator();
+ for (int i = 0; i < featureFlagCount; i++) {
+ var entry = entryIterator.next();
+ Boolean flagValue = entry.getValue();
+ if (flagValue == null) {
+ featureFlagStateAsArray[i] = entry.getKey() + "=?";
+ } else if (flagValue.booleanValue()) {
+ featureFlagStateAsArray[i] = entry.getKey() + "=1";
+ } else {
+ featureFlagStateAsArray[i] = entry.getKey() + "=0";
+ }
+
+ }
+ dest.writeStringArray(featureFlagStateAsArray);
+ }
+
public PackageImpl(Parcel in) {
this(in, /* callback */ null);
}
@@ -3275,6 +3301,9 @@
mCallback = callback;
// We use the boot classloader for all classes that we load.
final ClassLoader boot = Object.class.getClassLoader();
+
+ readFeatureFlagState(in);
+
this.supportsSmallScreens = sForBoolean.unparcel(in);
this.supportsNormalScreens = sForBoolean.unparcel(in);
this.supportsLargeScreens = sForBoolean.unparcel(in);
@@ -3440,6 +3469,27 @@
// to mutate this instance before it's finalized.
}
+ private void readFeatureFlagState(@NonNull Parcel in) {
+ // See comment in writeFeatureFlagState() for encoding of flag state.
+ String[] featureFlagStateAsArray = in.createStringArray();
+ for (String s : featureFlagStateAsArray) {
+ int sepIndex = s.lastIndexOf('=');
+ if (sepIndex >= 0 && sepIndex == s.length() - 2) {
+ String flagPackageAndName = s.substring(0, sepIndex);
+ char c = s.charAt(sepIndex + 1);
+ Boolean flagValue = null;
+ if (c == '1') {
+ flagValue = Boolean.TRUE;
+ } else if (c == '0') {
+ flagValue = Boolean.FALSE;
+ } else if (c != '?') {
+ continue;
+ }
+ this.mFeatureFlagState.put(flagPackageAndName, flagValue);
+ }
+ }
+ }
+
@NonNull
public static final Creator<PackageImpl> CREATOR = new Creator<PackageImpl>() {
@Override
@@ -3660,6 +3710,18 @@
return mBaseAppDataDeviceProtectedDirForSystemUser;
}
+ @Override
+ public PackageImpl addFeatureFlag(
+ @NonNull String flagPackageAndName,
+ @Nullable Boolean flagValue) {
+ mFeatureFlagState.put(flagPackageAndName, flagValue);
+ return this;
+ }
+
+ public Map<String, Boolean> getFeatureFlagState() {
+ return mFeatureFlagState;
+ }
+
/**
* Flags used for a internal bitset. These flags should never be persisted or exposed outside
* of this class. It is expected that PackageCacher explicitly clears itself whenever the
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index 8faaf95..70b7953 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -33,6 +33,7 @@
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
import com.android.modules.utils.TypedXmlPullParser;
import org.xmlpull.v1.XmlPullParser;
@@ -199,7 +200,7 @@
* @return the current value of the given Aconfig flag, or null if there is no such flag
*/
@Nullable
- private Boolean getFlagValue(@NonNull String flagPackageAndName) {
+ public Boolean getFlagValue(@NonNull String flagPackageAndName) {
Boolean value = mFlagValues.get(flagPackageAndName);
if (DEBUG) {
Slog.v(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
@@ -209,10 +210,13 @@
/**
* Check if the element in {@code parser} should be skipped because of the feature flag.
+ * @param pkg The package being parsed
* @param parser XML parser object currently parsing an element
* @return true if the element is disabled because of its feature flag
*/
- public boolean skipCurrentElement(@NonNull XmlResourceParser parser) {
+ public boolean skipCurrentElement(
+ @NonNull ParsingPackage pkg,
+ @NonNull XmlResourceParser parser) {
if (!Flags.manifestFlagging()) {
return false;
}
@@ -227,18 +231,21 @@
featureFlag = featureFlag.substring(1).strip();
}
final Boolean flagValue = getFlagValue(featureFlag);
+ boolean shouldSkip = false;
if (flagValue == null) {
Slog.w(LOG_TAG, "Skipping element " + parser.getName()
+ " due to unknown feature flag " + featureFlag);
- return true;
- }
- // Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated)
- if (flagValue == negated) {
+ shouldSkip = true;
+ } else if (flagValue == negated) {
+ // Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated)
Slog.i(LOG_TAG, "Skipping element " + parser.getName()
+ " behind feature flag " + featureFlag + " = " + flagValue);
- return true;
+ shouldSkip = true;
}
- return false;
+ if (android.content.pm.Flags.includeFeatureFlagsInPackageCacher()) {
+ pkg.addFeatureFlag(featureFlag, flagValue);
+ }
+ return shouldSkip;
}
/**
diff --git a/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
index 8858f94..335dedd 100644
--- a/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
@@ -61,7 +61,7 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(pkg, parser)) {
continue;
}
diff --git a/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
index bb01581..5f48d16 100644
--- a/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
+++ b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
@@ -54,7 +54,7 @@
return input.skip("install-constraints cannot be used by this package");
}
- ParseResult<Set<String>> prefixes = parseFingerprintPrefixes(input, res, parser);
+ ParseResult<Set<String>> prefixes = parseFingerprintPrefixes(input, pkg, res, parser);
if (prefixes.isSuccess()) {
if (validateFingerprintPrefixes(prefixes.getResult())) {
return input.success(pkg);
@@ -68,7 +68,7 @@
}
private static ParseResult<Set<String>> parseFingerprintPrefixes(
- ParseInput input, Resources res, XmlResourceParser parser)
+ ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser)
throws XmlPullParserException, IOException {
Set<String> prefixes = new ArraySet<>();
int type;
@@ -81,7 +81,7 @@
}
return input.success(prefixes);
} else if (type == XmlPullParser.START_TAG) {
- if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(pkg, parser)) {
continue;
}
if (parser.getName().equals(TAG_FINGERPRINT_PREFIX)) {
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
index 55baa53..a35150b 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
@@ -393,7 +393,7 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(pkg, parser)) {
continue;
}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
index da48b23..39d7af6 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
@@ -99,7 +99,7 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(pkg, parser)) {
continue;
}
@@ -200,7 +200,7 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(pkg, parser)) {
continue;
}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
index 6af2a29..3726b42 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
@@ -174,7 +174,7 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(pkg, parser)) {
continue;
}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
index c68ea2d..9601813 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
@@ -138,7 +138,7 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+ if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(pkg, parser)) {
continue;
}
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index 5d185af..341beed 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -123,6 +123,9 @@
ParsingPackage addQueriesProvider(String authority);
+ /** Adds a feature flag (`android:featureFlag` attribute) encountered in the manifest. */
+ ParsingPackage addFeatureFlag(@NonNull String flagPackageAndName, @Nullable Boolean flagValue);
+
/** Sets a process name -> {@link ParsedProcess} map coming from the <processes> tag. */
ParsingPackage setProcesses(@NonNull Map<String, ParsedProcess> processes);
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 787006e..bb733f2 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -763,7 +763,7 @@
if (outerDepth + 1 < parser.getDepth() || type != XmlPullParser.START_TAG) {
continue;
}
- if (sAconfigFlags.skipCurrentElement(parser)) {
+ if (sAconfigFlags.skipCurrentElement(pkg, parser)) {
continue;
}
@@ -842,7 +842,7 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (sAconfigFlags.skipCurrentElement(parser)) {
+ if (sAconfigFlags.skipCurrentElement(pkg, parser)) {
continue;
}
@@ -988,7 +988,7 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (sAconfigFlags.skipCurrentElement(parser)) {
+ if (sAconfigFlags.skipCurrentElement(pkg, parser)) {
continue;
}
@@ -1610,7 +1610,7 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (sAconfigFlags.skipCurrentElement(parser)) {
+ if (sAconfigFlags.skipCurrentElement(pkg, parser)) {
continue;
}
@@ -1853,7 +1853,7 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (sAconfigFlags.skipCurrentElement(parser)) {
+ if (sAconfigFlags.skipCurrentElement(pkg, parser)) {
continue;
}
if (parser.getName().equals("intent")) {
@@ -2202,7 +2202,7 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (sAconfigFlags.skipCurrentElement(parser)) {
+ if (sAconfigFlags.skipCurrentElement(pkg, parser)) {
continue;
}
@@ -2620,7 +2620,7 @@
}
}
- ParseResult<String[]> certResult = parseAdditionalCertificates(input, res, parser);
+ ParseResult<String[]> certResult = parseAdditionalCertificates(input, pkg, res, parser);
if (certResult.isError()) {
return input.error(certResult);
}
@@ -2674,7 +2674,8 @@
// Fot apps targeting O-MR1 we require explicit enumeration of all certs.
String[] additionalCertSha256Digests = EmptyArray.STRING;
if (pkg.getTargetSdkVersion() >= Build.VERSION_CODES.O_MR1) {
- ParseResult<String[]> certResult = parseAdditionalCertificates(input, res, parser);
+ ParseResult<String[]> certResult =
+ parseAdditionalCertificates(input, pkg, res, parser);
if (certResult.isError()) {
return input.error(certResult);
}
@@ -2782,7 +2783,7 @@
}
private static ParseResult<String[]> parseAdditionalCertificates(ParseInput input,
- Resources resources, XmlResourceParser parser)
+ ParsingPackage pkg, Resources resources, XmlResourceParser parser)
throws XmlPullParserException, IOException {
String[] certSha256Digests = EmptyArray.STRING;
final int depth = parser.getDepth();
@@ -2793,7 +2794,7 @@
if (type != XmlPullParser.START_TAG) {
continue;
}
- if (sAconfigFlags.skipCurrentElement(parser)) {
+ if (sAconfigFlags.skipCurrentElement(pkg, parser)) {
continue;
}
diff --git a/core/java/com/android/internal/protolog/TEST_MAPPING b/core/java/com/android/internal/protolog/TEST_MAPPING
index b51d19d..545ba11 100644
--- a/core/java/com/android/internal/protolog/TEST_MAPPING
+++ b/core/java/com/android/internal/protolog/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "TracingTests"
},
diff --git a/services/core/java/com/android/server/FgThread.java b/core/java/com/android/server/FgThread.java
similarity index 99%
rename from services/core/java/com/android/server/FgThread.java
rename to core/java/com/android/server/FgThread.java
index b4b6e3f..f54ee5e 100644
--- a/services/core/java/com/android/server/FgThread.java
+++ b/core/java/com/android/server/FgThread.java
@@ -30,6 +30,8 @@
* relatively long-running operations like saving state to disk (in addition to
* simply being a background priority), which can cause operations scheduled on it
* to be delayed for a user-noticeable amount of time.
+ *
+ * @hide
*/
public final class FgThread extends ServiceThread {
private static final long SLOW_DISPATCH_THRESHOLD_MS = 100;
diff --git a/services/core/java/com/android/server/ServiceThread.java b/core/java/com/android/server/ServiceThread.java
similarity index 98%
rename from services/core/java/com/android/server/ServiceThread.java
rename to core/java/com/android/server/ServiceThread.java
index 6d8e49c..0eff1fc 100644
--- a/services/core/java/com/android/server/ServiceThread.java
+++ b/core/java/com/android/server/ServiceThread.java
@@ -24,6 +24,8 @@
/**
* Special handler thread that we create for system services that require their own loopers.
+ *
+ * @hide
*/
public class ServiceThread extends HandlerThread {
private static final String TAG = "ServiceThread";
diff --git a/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java b/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
similarity index 99%
rename from services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
rename to core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
index 1526230..e8aeb86 100644
--- a/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
+++ b/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
@@ -63,6 +63,8 @@
* <p>Optionally, two permissions may be specified: (1) a caller permission - any service that does
* not require callers to hold this permission is rejected (2) a service permission - any service
* whose package does not hold this permission is rejected.
+ *
+ * @hide
*/
public final class CurrentUserServiceSupplier extends BroadcastReceiver implements
ServiceSupplier<CurrentUserServiceSupplier.BoundServiceInfo> {
diff --git a/services/core/java/com/android/server/servicewatcher/OWNERS b/core/java/com/android/server/servicewatcher/OWNERS
similarity index 100%
rename from services/core/java/com/android/server/servicewatcher/OWNERS
rename to core/java/com/android/server/servicewatcher/OWNERS
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java b/core/java/com/android/server/servicewatcher/ServiceWatcher.java
similarity index 92%
rename from services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
rename to core/java/com/android/server/servicewatcher/ServiceWatcher.java
index 5636718..831ff67 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
+++ b/core/java/com/android/server/servicewatcher/ServiceWatcher.java
@@ -16,6 +16,10 @@
package com.android.server.servicewatcher;
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_NOT_FOREGROUND;
+import static android.content.Context.BIND_NOT_VISIBLE;
+
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
@@ -52,6 +56,8 @@
* whether any particular {@link BinderOperation} will succeed. Clients must ensure they do not rely
* on this, and instead use {@link ServiceListener} notifications as necessary to recover from
* failures.
+ *
+ * @hide
*/
public interface ServiceWatcher {
@@ -144,6 +150,10 @@
protected final @Nullable String mAction;
protected final int mUid;
protected final ComponentName mComponentName;
+ private final int mFlags;
+
+ private static final int DEFAULT_FLAGS =
+ BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE;
protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
this(action, resolveInfo.serviceInfo.applicationInfo.uid,
@@ -151,9 +161,14 @@
}
protected BoundServiceInfo(String action, int uid, ComponentName componentName) {
+ this(action, uid, componentName, DEFAULT_FLAGS);
+ }
+
+ protected BoundServiceInfo(String action, int uid, ComponentName componentName, int flags) {
mAction = action;
mUid = uid;
mComponentName = Objects.requireNonNull(componentName);
+ mFlags = flags;
}
/** Returns the action associated with this bound service. */
@@ -171,6 +186,11 @@
return UserHandle.getUserId(mUid);
}
+ /** Returns flags used when binding the service. */
+ public int getFlags() {
+ return mFlags;
+ }
+
@Override
public final boolean equals(Object o) {
if (this == o) {
@@ -183,12 +203,13 @@
BoundServiceInfo that = (BoundServiceInfo) o;
return mUid == that.mUid
&& Objects.equals(mAction, that.mAction)
- && mComponentName.equals(that.mComponentName);
+ && mComponentName.equals(that.mComponentName)
+ && mFlags == that.mFlags;
}
@Override
public final int hashCode() {
- return Objects.hash(mAction, mUid, mComponentName);
+ return Objects.hash(mAction, mUid, mComponentName, mFlags);
}
@Override
@@ -256,4 +277,4 @@
* Dumps ServiceWatcher information.
*/
void dump(PrintWriter pw);
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
similarity index 97%
rename from services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
rename to core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
index b178269..ccbab9f 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
+++ b/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -16,10 +16,6 @@
package com.android.server.servicewatcher;
-import static android.content.Context.BIND_AUTO_CREATE;
-import static android.content.Context.BIND_NOT_FOREGROUND;
-import static android.content.Context.BIND_NOT_VISIBLE;
-
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
@@ -46,6 +42,8 @@
* Implementation of ServiceWatcher. Keeping the implementation separate from the interface allows
* us to store the generic relationship between the service supplier and the service listener, while
* hiding the generics from clients, simplifying the API.
+ *
+ * @hide
*/
class ServiceWatcherImpl<TBoundServiceInfo extends BoundServiceInfo> implements ServiceWatcher,
ServiceChangedListener {
@@ -212,7 +210,7 @@
mBoundServiceInfo.getComponentName());
try {
if (!mContext.bindServiceAsUser(bindIntent, this,
- BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
+ mBoundServiceInfo.getFlags(),
mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
mRebinder = this::bind;
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index f162b74..7fefe17 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -117,6 +117,7 @@
jfieldID activeDisplayModeId;
jfieldID renderFrameRate;
jfieldID hasArrSupport;
+ jfieldID frameRateCategoryRate;
jfieldID supportedColorModes;
jfieldID activeColorMode;
jfieldID hdrCapabilities;
@@ -292,6 +293,11 @@
jfieldID frameNumber;
} gStalledTransactionInfoClassInfo;
+static struct {
+ jclass clazz;
+ jmethodID ctor;
+} gFrameRateCategoryRateClassInfo;
+
constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) {
switch (colorMode) {
case ui::ColorMode::DISPLAY_P3:
@@ -1388,6 +1394,13 @@
return object;
}
+static jobject convertFrameRateCategoryRateToJavaObject(
+ JNIEnv* env, const ui::FrameRateCategoryRate& frameRateCategoryRate) {
+ return env->NewObject(gFrameRateCategoryRateClassInfo.clazz,
+ gFrameRateCategoryRateClassInfo.ctor, frameRateCategoryRate.getNormal(),
+ frameRateCategoryRate.getHigh());
+}
+
static jobject convertDisplayModeToJavaObject(JNIEnv* env, const ui::DisplayMode& config) {
jobject object = env->NewObject(gDisplayModeClassInfo.clazz, gDisplayModeClassInfo.ctor);
env->SetIntField(object, gDisplayModeClassInfo.id, config.id);
@@ -1456,6 +1469,8 @@
info.activeDisplayModeId);
env->SetFloatField(object, gDynamicDisplayInfoClassInfo.renderFrameRate, info.renderFrameRate);
env->SetBooleanField(object, gDynamicDisplayInfoClassInfo.hasArrSupport, info.hasArrSupport);
+ env->SetObjectField(object, gDynamicDisplayInfoClassInfo.frameRateCategoryRate,
+ convertFrameRateCategoryRateToJavaObject(env, info.frameRateCategoryRate));
jintArray colorModesArray = env->NewIntArray(info.supportedColorModes.size());
if (colorModesArray == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
@@ -2666,6 +2681,15 @@
GetFieldIDOrDie(env, dynamicInfoClazz, "renderFrameRate", "F");
gDynamicDisplayInfoClassInfo.hasArrSupport =
GetFieldIDOrDie(env, dynamicInfoClazz, "hasArrSupport", "Z");
+
+ gDynamicDisplayInfoClassInfo.frameRateCategoryRate =
+ GetFieldIDOrDie(env, dynamicInfoClazz, "frameRateCategoryRate",
+ "Landroid/view/FrameRateCategoryRate;");
+ jclass frameRateCategoryRateClazz = FindClassOrDie(env, "android/view/FrameRateCategoryRate");
+ gFrameRateCategoryRateClassInfo.clazz = MakeGlobalRefOrDie(env, frameRateCategoryRateClazz);
+ gFrameRateCategoryRateClassInfo.ctor =
+ GetMethodIDOrDie(env, frameRateCategoryRateClazz, "<init>", "(FF)V");
+
gDynamicDisplayInfoClassInfo.supportedColorModes =
GetFieldIDOrDie(env, dynamicInfoClazz, "supportedColorModes", "[I");
gDynamicDisplayInfoClassInfo.activeColorMode =
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 561d351..97682c8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7023,7 +7023,6 @@
<!-- Allows an application to set the advanced features on BiometricDialog (SystemUI), including
logo, logo description, and content view with more options button.
<p>Not for use by third-party applications.
- @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")
-->
<permission android:name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED"
android:protectionLevel="signature|privileged" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 522dcfa..db75206 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -820,17 +820,17 @@
<!-- The gap between segments in the notification progress bar -->
<dimen name="notification_progress_segSeg_gap">2dp</dimen>
<!-- The gap between a segment and a point in the notification progress bar -->
- <dimen name="notification_progress_segPoint_gap">4dp</dimen>
+ <dimen name="notification_progress_segPoint_gap">8dp</dimen>
<!-- The dash gap of the notification progress bar segments -->
- <dimen name="notification_progress_segments_dash_gap">9dp</dimen>
+ <dimen name="notification_progress_segments_dash_gap">8dp</dimen>
<!-- The dash width of the notification progress bar segments -->
<dimen name="notification_progress_segments_dash_width">3dp</dimen>
<!-- The height of the notification progress bar segments -->
<dimen name="notification_progress_segments_height">6dp</dimen>
<!-- The radius of the notification progress bar points -->
- <dimen name="notification_progress_points_radius">10dp</dimen>
+ <dimen name="notification_progress_points_radius">6dp</dimen>
<!-- The corner radius of the notification progress bar points drawn as rects -->
- <dimen name="notification_progress_points_corner_radius">4dp</dimen>
+ <dimen name="notification_progress_points_corner_radius">2dp</dimen>
<!-- The inset of the notification progress bar points drawn as rects -->
<dimen name="notification_progress_points_inset">0dp</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0d4870b..d3ef07ca 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5337,7 +5337,9 @@
<!-- Zen mode - name of default automatic calendar time-based rule that is triggered every night (when sleeping). [CHAR LIMIT=40] -->
<string name="zen_mode_default_every_night_name">Sleeping</string>
- <!-- Zen mode - Trigger description of the rule, indicating which app owns it. [CHAR_LIMIT=100] -->
+ <!-- Implicit zen mode - Name of the rule, indicating which app owns it. [CHAR_LIMIT=30] -->
+ <string name="zen_mode_implicit_name">Do Not Disturb (<xliff:g id="app_name" example="Gmail">%1$s</xliff:g>)</string>
+ <!-- Implicit zen mode - Trigger description of the rule, indicating which app owns it. [CHAR_LIMIT=100] -->
<string name="zen_mode_implicit_trigger_description">Managed by <xliff:g id="app_name">%1$s</xliff:g></string>
<!-- Zen mode - Condition summary when a rule is activated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] -->
<string name="zen_mode_implicit_activated">On</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 51c405ae..b32d4cf 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2652,6 +2652,7 @@
<java-symbol type="string" name="zen_mode_default_weekends_name" />
<java-symbol type="string" name="zen_mode_default_events_name" />
<java-symbol type="string" name="zen_mode_default_every_night_name" />
+ <java-symbol type="string" name="zen_mode_implicit_name" />
<java-symbol type="string" name="zen_mode_implicit_trigger_description" />
<java-symbol type="string" name="zen_mode_implicit_activated" />
<java-symbol type="string" name="zen_mode_implicit_deactivated" />
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index bdc4d25..0f8dc13 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -20,13 +20,26 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import android.annotation.Nullable;
import android.app.compat.CompatChanges;
+import android.hardware.broadcastradio.Alert;
+import android.hardware.broadcastradio.AlertArea;
+import android.hardware.broadcastradio.AlertCategory;
+import android.hardware.broadcastradio.AlertCertainty;
+import android.hardware.broadcastradio.AlertInfo;
+import android.hardware.broadcastradio.AlertMessageType;
+import android.hardware.broadcastradio.AlertSeverity;
+import android.hardware.broadcastradio.AlertStatus;
+import android.hardware.broadcastradio.AlertUrgency;
import android.hardware.broadcastradio.AmFmBandRange;
import android.hardware.broadcastradio.AmFmRegionConfig;
import android.hardware.broadcastradio.ConfigFlag;
+import android.hardware.broadcastradio.Coordinate;
import android.hardware.broadcastradio.DabTableEntry;
+import android.hardware.broadcastradio.Geocode;
import android.hardware.broadcastradio.IdentifierType;
import android.hardware.broadcastradio.Metadata;
+import android.hardware.broadcastradio.Polygon;
import android.hardware.broadcastradio.ProgramFilter;
import android.hardware.broadcastradio.ProgramIdentifier;
import android.hardware.broadcastradio.ProgramInfo;
@@ -37,10 +50,13 @@
import android.hardware.radio.Flags;
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioAlert;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import android.hardware.radio.UniqueProgramIdentifier;
import android.os.ServiceSpecificException;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -148,6 +164,9 @@
private static final ProgramIdentifier TEST_HAL_HD_STATION_LOCATION_ID =
AidlTestUtils.makeHalIdentifier(IdentifierType.HD_STATION_LOCATION,
TEST_HD_LOCATION_VALUE);
+ private static final ProgramIdentifier TEST_HAL_HD_FM_FREQUENCY_ID =
+ AidlTestUtils.makeHalIdentifier(IdentifierType.AMFM_FREQUENCY_KHZ,
+ TEST_HD_FREQUENCY_VALUE);
private static final UniqueProgramIdentifier TEST_DAB_UNIQUE_ID = new UniqueProgramIdentifier(
TEST_DAB_SELECTOR);
@@ -173,6 +192,57 @@
private static final Metadata TEST_HAL_HD_SUBCHANNELS = Metadata.hdSubChannelsAvailable(
TEST_HD_SUBCHANNELS);
+ private static final int TEST_STATUS = RadioAlert.STATUS_ACTUAL;
+ private static final int TEST_HAL_STATUS = AlertStatus.ACTUAL;
+ private static final int TEST_TYPE = RadioAlert.MESSAGE_TYPE_ALERT;
+ private static final int TEST_HAL_TYPE = AlertMessageType.ALERT;
+ private static final int[] TEST_CATEGORY_ARRAY = new int[]{RadioAlert.CATEGORY_CBRNE,
+ RadioAlert.CATEGORY_GEO};
+ private static final int[] TEST_HAL_CATEGORY_LIST = new int[]{AlertCategory.CBRNE,
+ AlertCategory.GEO};
+ private static final int TEST_URGENCY = RadioAlert.URGENCY_FUTURE;
+ private static final int TEST_HAL_URGENCY = AlertUrgency.FUTURE;
+ private static final int TEST_SEVERITY = RadioAlert.SEVERITY_MINOR;
+ private static final int TEST_HAL_SEVERITY = AlertSeverity.MINOR;
+ private static final int TEST_CERTAINTY = RadioAlert.CERTAINTY_UNLIKELY;
+ private static final int TEST_HAL_CERTAINTY = AlertCertainty.UNLIKELY;
+ private static final String TEST_DESCRIPTION_MESSAGE = "Test Alert Description Message.";
+ private static final String TEST_GEOCODE_VALUE_NAME = "ZIP";
+ private static final String TEST_GEOCODE_VALUE_1 = "10001";
+ private static final String TEST_GEOCODE_VALUE_2 = "10002";
+ private static final double TEST_POLYGON_LATITUDE_START = -38.47;
+ private static final double TEST_POLYGON_LONGITUDE_START = -120.14;
+ private static final RadioAlert.Coordinate TEST_POLYGON_COORDINATE_START =
+ new RadioAlert.Coordinate(TEST_POLYGON_LATITUDE_START, TEST_POLYGON_LONGITUDE_START);
+ private static final List<RadioAlert.Coordinate> TEST_COORDINATES = List.of(
+ TEST_POLYGON_COORDINATE_START, new RadioAlert.Coordinate(38.34, -119.95),
+ new RadioAlert.Coordinate(38.52, -119.74), new RadioAlert.Coordinate(38.62, -119.89),
+ TEST_POLYGON_COORDINATE_START);
+ private static final RadioAlert.Polygon TEST_POLYGON = new RadioAlert.Polygon(TEST_COORDINATES);
+ private static final Polygon TEST_HAL_POLYGON = createHalPolygon(TEST_COORDINATES);
+ private static final RadioAlert.Geocode TEST_GEOCODE_1 = new RadioAlert.Geocode(
+ TEST_GEOCODE_VALUE_NAME, TEST_GEOCODE_VALUE_1);
+ private static final RadioAlert.Geocode TEST_GEOCODE_2 = new RadioAlert.Geocode(
+ TEST_GEOCODE_VALUE_NAME, TEST_GEOCODE_VALUE_2);
+ private static final RadioAlert.AlertArea TEST_AREA = new RadioAlert.AlertArea(
+ List.of(TEST_POLYGON), List.of(TEST_GEOCODE_1, TEST_GEOCODE_2));
+ private static final AlertArea TEST_HAL_ALERT_AREA = createHalAlertArea(
+ new Polygon[]{TEST_HAL_POLYGON}, new Geocode[]{
+ createHalGeocode(TEST_GEOCODE_VALUE_NAME, TEST_GEOCODE_VALUE_1),
+ createHalGeocode(TEST_GEOCODE_VALUE_NAME, TEST_GEOCODE_VALUE_2)});
+ private static final String TEST_LANGUAGE = "en-US";
+
+ private static final RadioAlert.AlertInfo TEST_ALERT_INFO_1 = new RadioAlert.AlertInfo(
+ TEST_CATEGORY_ARRAY, TEST_URGENCY, TEST_SEVERITY, TEST_CERTAINTY,
+ TEST_DESCRIPTION_MESSAGE, List.of(TEST_AREA), TEST_LANGUAGE);
+ private static final AlertInfo TEST_HAL_ALERT_INFO = createHalAlertInfo(
+ TEST_HAL_CATEGORY_LIST, TEST_HAL_URGENCY, TEST_HAL_SEVERITY, TEST_HAL_CERTAINTY,
+ TEST_DESCRIPTION_MESSAGE, new AlertArea[]{TEST_HAL_ALERT_AREA}, TEST_LANGUAGE);
+ private static final RadioAlert TEST_ALERT = new RadioAlert(TEST_STATUS, TEST_TYPE,
+ List.of(TEST_ALERT_INFO_1));
+ private static final Alert TEST_HAL_ALERT = createHalAlert(TEST_HAL_STATUS, TEST_HAL_TYPE,
+ new AlertInfo[]{TEST_HAL_ALERT_INFO});
+
@Rule
public final Expect expect = Expect.create();
@Rule
@@ -576,6 +646,42 @@
}
@Test
+ @EnableFlags(Flags.FLAG_HD_RADIO_EMERGENCY_ALERT_SYSTEM)
+ public void programInfoFromHalProgramInfo_withAlertMessageAndFlagEnabled() {
+ android.hardware.broadcastradio.ProgramSelector halHdSelector =
+ AidlTestUtils.makeHalSelector(TEST_HAL_HD_STATION_EXT_ID,
+ new ProgramIdentifier[]{});
+ ProgramInfo halHdProgramInfo = AidlTestUtils.makeHalProgramInfo(halHdSelector,
+ TEST_HAL_HD_STATION_EXT_ID, TEST_HAL_HD_FM_FREQUENCY_ID, TEST_SIGNAL_QUALITY,
+ new ProgramIdentifier[]{}, new Metadata[]{});
+ halHdProgramInfo.emergencyAlert = TEST_HAL_ALERT;
+
+ RadioManager.ProgramInfo programInfo =
+ ConversionUtils.programInfoFromHalProgramInfo(halHdProgramInfo);
+
+ expect.withMessage("Alert of converted HD program info with alert and enabled flag")
+ .that(programInfo.getAlert()).isEqualTo(TEST_ALERT);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_HD_RADIO_EMERGENCY_ALERT_SYSTEM)
+ public void programInfoFromHalProgramInfo_withAlertMessageAndFlagDisabled() {
+ android.hardware.broadcastradio.ProgramSelector halHdSelector =
+ AidlTestUtils.makeHalSelector(TEST_HAL_HD_STATION_EXT_ID,
+ new ProgramIdentifier[]{});
+ ProgramInfo halHdProgramInfo = AidlTestUtils.makeHalProgramInfo(halHdSelector,
+ TEST_HAL_HD_STATION_EXT_ID, TEST_HAL_HD_FM_FREQUENCY_ID, TEST_SIGNAL_QUALITY,
+ new ProgramIdentifier[]{}, new Metadata[]{});
+ halHdProgramInfo.emergencyAlert = TEST_HAL_ALERT;
+
+ RadioManager.ProgramInfo programInfo =
+ ConversionUtils.programInfoFromHalProgramInfo(halHdProgramInfo);
+
+ expect.withMessage("Alert of converted HD program info with alert and disabled flag")
+ .that(programInfo.getAlert()).isNull();
+ }
+
+ @Test
public void tunedProgramInfoFromHalProgramInfo_withInvalidDabProgramInfo() {
android.hardware.broadcastradio.ProgramSelector invalidHalDabSelector =
AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
@@ -851,7 +957,7 @@
}
@Test
- public void radioMetadataFromHalMetadata_withHdMedatadataAndFlagEnabled() {
+ public void radioMetadataFromHalMetadata_withHdMetadataAndFlagEnabled() {
mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
String genreValue = "genreTest";
String commentShortDescriptionValue = "commentShortDescriptionTest";
@@ -973,6 +1079,57 @@
.isEmpty();
}
+ @Test
+ @EnableFlags(Flags.FLAG_HD_RADIO_EMERGENCY_ALERT_SYSTEM)
+ public void radioAlertFromHalAlert() {
+ RadioAlert convertedAlert = ConversionUtils.radioAlertFromHalAlert(TEST_HAL_ALERT);
+
+ expect.withMessage("Converted alert").that(convertedAlert)
+ .isEqualTo(TEST_ALERT);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HD_RADIO_EMERGENCY_ALERT_SYSTEM)
+ public void radioAlertFromHalAlert_withLowThanFourCoordinates() {
+ Polygon invalidPolygon = createHalPolygon(List.of(
+ TEST_POLYGON_COORDINATE_START, new RadioAlert.Coordinate(38.34, -119.95),
+ TEST_POLYGON_COORDINATE_START));
+ AlertInfo halAlertInfo = createHalAlertInfo(TEST_HAL_CATEGORY_LIST, TEST_HAL_URGENCY,
+ TEST_HAL_SEVERITY, TEST_HAL_CERTAINTY, TEST_DESCRIPTION_MESSAGE,
+ new AlertArea[]{createHalAlertArea(new Polygon[]{invalidPolygon},
+ new Geocode[]{})}, TEST_LANGUAGE);
+ Alert halAlert = createHalAlert(TEST_HAL_STATUS, TEST_HAL_TYPE,
+ new AlertInfo[]{halAlertInfo });
+
+ RadioAlert convertedAlert = ConversionUtils.radioAlertFromHalAlert(halAlert);
+
+ expect.withMessage("Empty polygon list with less than 4 coordinates")
+ .that(convertedAlert.getInfoList().get(0).getAreas().get(0).getPolygons())
+ .isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HD_RADIO_EMERGENCY_ALERT_SYSTEM)
+ public void radioAlertFromHalAlert_withDifferentFirstAndLastCoordinate() {
+ Polygon invalidPolygon = createHalPolygon(List.of(
+ TEST_POLYGON_COORDINATE_START, new RadioAlert.Coordinate(38.34, -119.95),
+ new RadioAlert.Coordinate(38.52, -119.74),
+ new RadioAlert.Coordinate(38.62, -119.89),
+ new RadioAlert.Coordinate(38.42, -120.14)));
+ AlertInfo halAlertInfo = createHalAlertInfo(TEST_HAL_CATEGORY_LIST, TEST_HAL_URGENCY,
+ TEST_HAL_SEVERITY, TEST_HAL_CERTAINTY, TEST_DESCRIPTION_MESSAGE,
+ new AlertArea[]{createHalAlertArea(new Polygon[]{invalidPolygon},
+ new Geocode[]{})}, TEST_LANGUAGE);
+ Alert halAlert = createHalAlert(TEST_HAL_STATUS, TEST_HAL_TYPE,
+ new AlertInfo[]{halAlertInfo});
+
+ RadioAlert convertedAlert = ConversionUtils.radioAlertFromHalAlert(halAlert);
+
+ expect.withMessage("Empty polygon list with different first and last coordinates")
+ .that(convertedAlert.getInfoList().get(0).getAreas().get(0).getPolygons())
+ .isEmpty();
+ }
+
private static RadioManager.ModuleProperties createModuleProperties() {
AmFmRegionConfig amFmConfig = createAmFmRegionConfig();
DabTableEntry[] dabTableEntries = new DabTableEntry[]{
@@ -1028,14 +1185,62 @@
return halProperties;
}
- private ProgramSelector.Identifier createHdStationLocationIdWithFlagEnabled() {
+ private static ProgramSelector.Identifier createHdStationLocationIdWithFlagEnabled() {
return new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_HD_STATION_LOCATION,
TEST_HD_LOCATION_VALUE);
}
- private ProgramSelector createHdSelectorWithFlagEnabled() {
+ private static ProgramSelector createHdSelectorWithFlagEnabled() {
return new ProgramSelector(ProgramSelector.PROGRAM_TYPE_FM_HD, TEST_HD_STATION_EXT_ID,
new ProgramSelector.Identifier[]{createHdStationLocationIdWithFlagEnabled()},
/* vendorIds= */ null);
}
+
+ private static Alert createHalAlert(int status, int messageType, AlertInfo[] alertInfos) {
+ Alert halAlert = new Alert();
+ halAlert.status = status;
+ halAlert.messageType = messageType;
+ halAlert.infoArray = alertInfos;
+ return halAlert;
+ }
+
+ private static AlertInfo createHalAlertInfo(int[] categoryArray, int urgency, int severity,
+ int certainty, String description, AlertArea[] areas, @Nullable String language) {
+ AlertInfo info = new AlertInfo();
+ info.categoryArray = categoryArray;
+ info.urgency = urgency;
+ info.severity = severity;
+ info.certainty = certainty;
+ info.description = description;
+ info.areas = areas;
+ info.language = language;
+ return info;
+ }
+
+ private static AlertArea createHalAlertArea(Polygon[] polygons, Geocode[] geocodes) {
+ AlertArea area = new AlertArea();
+ area.polygons = polygons;
+ area.geocodes = geocodes;
+ return area;
+ }
+
+ private static Polygon createHalPolygon(List<RadioAlert.Coordinate> coordinates) {
+ Coordinate[] halCoordinates = new Coordinate[coordinates.size()];
+ for (int idx = 0; idx < coordinates.size(); idx++) {
+ Coordinate halCoordinate = new Coordinate();
+ halCoordinate.latitude = coordinates.get(idx).getLatitude();
+ halCoordinate.longitude = coordinates.get(idx).getLongitude();
+ halCoordinates[idx] = halCoordinate;
+ }
+ Polygon polygon = new Polygon();
+ polygon.coordinates = halCoordinates;
+ return polygon;
+ }
+
+ private static Geocode createHalGeocode(String valueName, String value) {
+ Geocode halGeocode = new Geocode();
+ halGeocode.valueName = valueName;
+ halGeocode.value = value;
+ return halGeocode;
+ }
}
diff --git a/core/tests/coretests/AppThatUsesAppOps/src/android/app/appops/appthatusesappops/AppOpsUserService.kt b/core/tests/coretests/AppThatUsesAppOps/src/android/app/appops/appthatusesappops/AppOpsUserService.kt
index 48053c1..c5f07ff 100644
--- a/core/tests/coretests/AppThatUsesAppOps/src/android/app/appops/appthatusesappops/AppOpsUserService.kt
+++ b/core/tests/coretests/AppThatUsesAppOps/src/android/app/appops/appthatusesappops/AppOpsUserService.kt
@@ -22,7 +22,9 @@
import android.app.Service
import android.app.SyncNotedAppOp
import android.content.Intent
+import android.os.Handler
import android.os.IBinder
+import android.os.Looper
import android.util.Log
import com.android.frameworks.coretests.aidl.IAppOpsUserClient
import com.android.frameworks.coretests.aidl.IAppOpsUserService
@@ -71,6 +73,7 @@
override fun onBind(intent: Intent?): IBinder {
return object : IAppOpsUserService.Stub() {
private val appOpsManager = getSystemService(AppOpsManager::class.java)!!
+ private val handler = Handler(Looper.getMainLooper())
// Collected note-op calls inside of this process
private val noted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>()
@@ -182,6 +185,18 @@
}
}
+ override fun callFreezeAndNoteSyncOp(client: IAppOpsUserClient) {
+ handler.post {
+ client.freezeAndNoteSyncOp()
+ }
+ }
+
+ override fun assertEmptyAsyncNoted() {
+ forwardThrowableFrom {
+ assertThat(asyncNoted).isEmpty()
+ }
+ }
+
override fun callApiThatNotesAsyncOpNativelyAndCheckCustomMessage(
client: IAppOpsUserClient
) {
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IAppOpsUserClient.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IAppOpsUserClient.aidl
index 68b393c0..11300b0 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IAppOpsUserClient.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IAppOpsUserClient.aidl
@@ -20,6 +20,7 @@
void noteSyncOpNative();
void noteNonPermissionSyncOpNative();
oneway void noteSyncOpOnewayNative();
+ void freezeAndNoteSyncOp();
void noteSyncOpOtherUidNative();
void noteAsyncOpNative();
void noteAsyncOpNativeWithCustomMessage();
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IAppOpsUserService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IAppOpsUserService.aidl
index f5673c4..c6dc7b0 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IAppOpsUserService.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IAppOpsUserService.aidl
@@ -25,4 +25,6 @@
void callApiThatNotesSyncOpOtherUidNativelyAndCheckLog(in IAppOpsUserClient client);
void callApiThatNotesAsyncOpNativelyAndCheckCustomMessage(in IAppOpsUserClient client);
void callApiThatNotesAsyncOpNativelyAndCheckLog(in IAppOpsUserClient client);
+ void callFreezeAndNoteSyncOp(in IAppOpsUserClient client);
+ void assertEmptyAsyncNoted();
}
diff --git a/core/tests/coretests/src/android/app/AppOpsLoggingTest.kt b/core/tests/coretests/src/android/app/AppOpsLoggingTest.kt
index a10d6a9..ff4e532 100644
--- a/core/tests/coretests/src/android/app/AppOpsLoggingTest.kt
+++ b/core/tests/coretests/src/android/app/AppOpsLoggingTest.kt
@@ -33,6 +33,9 @@
import android.os.Looper
import android.os.Process
import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import com.android.frameworks.coretests.aidl.IAppOpsUserClient
@@ -41,9 +44,15 @@
import org.junit.After
import org.junit.Assert.fail
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import java.util.concurrent.CompletableFuture
+import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.TimeUnit.MILLISECONDS
+import java.util.concurrent.TimeUnit
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.TimeSource
private const val LOG_TAG = "AppOpsLoggingTest"
@@ -71,8 +80,11 @@
private var wasLocationEnabled = false
+ @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
private lateinit var testService: IAppOpsUserService
private lateinit var serviceConnection: ServiceConnection
+ private lateinit var freezingTestCompletion: CompletableFuture<Unit>
// Collected note-op calls inside of this process
private val noted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>()
@@ -123,6 +135,7 @@
context.bindService(serviceIntent, serviceConnection, BIND_AUTO_CREATE)
testService = newService.get(TIMEOUT_MILLIS, MILLISECONDS)
+ freezingTestCompletion = CompletableFuture<Unit>()
}
private fun clearCollectedNotedOps() {
@@ -253,6 +266,26 @@
}
}
+ @Test
+ @RequiresFlagsEnabled(android.os.Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK,
+ android.permission.flags.Flags.FLAG_USE_FROZEN_AWARE_REMOTE_CALLBACK_LIST)
+ fun dropAsyncOpNotedWhenFrozen() {
+ // Here's what the test does:
+ // 1. AppOpsLoggingTest calls AppOpsUserService
+ // 2. AppOpsUserService calls freezeAndNoteSyncOp in AppOpsLoggingTest
+ // 3. freezeAndNoteSyncOp freezes AppOpsUserService
+ // 4. freezeAndNoteSyncOp calls nativeNoteOp which leads to an async op noted callback
+ // 5. AppOpsService is expected to drop the callback (via RemoteCallbackList) since
+ // AppOpsUserService is frozen
+ // 6. freezeAndNoteSyncOp unfreezes AppOpsUserService
+ // 7. AppOpsLoggingTest calls AppOpsUserService.assertEmptyAsyncNoted
+ rethrowThrowableFrom {
+ testService.callFreezeAndNoteSyncOp(AppOpsUserClient(context))
+ freezingTestCompletion.get()
+ testService.assertEmptyAsyncNoted()
+ }
+ }
+
@After
fun removeNotedAppOpsCollector() {
appOpsManager.setOnOpNotedCallback(null, null)
@@ -263,6 +296,20 @@
context.unbindService(serviceConnection)
}
+ fun <T> waitForState(queue: LinkedBlockingQueue<T>, state: T, duration: Duration): T? {
+ val timeSource = TimeSource.Monotonic
+ val start = timeSource.markNow()
+ var remaining = duration
+ while (remaining.inWholeMilliseconds > 0) {
+ val v = queue.poll(remaining.inWholeMilliseconds, TimeUnit.MILLISECONDS)
+ if (v == state) {
+ return v
+ }
+ remaining -= timeSource.markNow() - start
+ }
+ return null
+ }
+
private inner class AppOpsUserClient(
context: Context
) : IAppOpsUserClient.Stub() {
@@ -285,6 +332,31 @@
nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), Binder.getCallingUid(), TEST_SERVICE_PKG)
}
+ override fun freezeAndNoteSyncOp() {
+ handler.post {
+ var stateChanges = LinkedBlockingQueue<Int>()
+ // Leave some time for any pending binder transactions to complete.
+ //
+ // TODO(327047060) Remove this sleep and instead make am freeze wait for binder
+ // transactions to complete
+ Thread.sleep(1000)
+ testService.asBinder().addFrozenStateChangeCallback {
+ _, state -> stateChanges.put(state)
+ }
+ InstrumentationRegistry.getInstrumentation().uiAutomation
+ .executeShellCommand("am freeze $TEST_SERVICE_PKG")
+ waitForState(stateChanges, IBinder.FrozenStateChangeCallback.STATE_FROZEN,
+ 1000.milliseconds)
+ nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), Binder.getCallingUid(),
+ TEST_SERVICE_PKG)
+ InstrumentationRegistry.getInstrumentation().uiAutomation
+ .executeShellCommand("am unfreeze $TEST_SERVICE_PKG")
+ waitForState(stateChanges, IBinder.FrozenStateChangeCallback.STATE_UNFROZEN,
+ 1000.milliseconds)
+ freezingTestCompletion.complete(Unit)
+ }
+ }
+
override fun noteSyncOpOtherUidNative() {
nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), myUid, myPackage)
}
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 483ebc2..fb1efa8 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -891,6 +891,65 @@
});
}
+ @Test
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
+ })
+ public void testTouchBoostReset() throws Throwable {
+ if (!ViewProperties.vrr_enabled().orElse(true)) {
+ return;
+ }
+ mActivityRule.runOnUiThread(() -> {
+ ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+ layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ mMovingView.setLayoutParams(layoutParams);
+ mMovingView.setOnClickListener((v) -> {});
+ });
+ waitForFrameRateCategoryToSettle();
+
+ int[] position = new int[2];
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.getLocationOnScreen(position);
+ position[0] += mMovingView.getWidth() / 2;
+ position[1] += mMovingView.getHeight() / 2;
+ });
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+ long now = SystemClock.uptimeMillis();
+ MotionEvent down = MotionEvent.obtain(
+ now, // downTime
+ now, // eventTime
+ MotionEvent.ACTION_DOWN, // action
+ position[0], // x
+ position[1], // y
+ 0 // metaState
+ );
+ down.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ instrumentation.sendPointerSync(down);
+ assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT, mViewRoot.getLastPreferredFrameRateCategory());
+
+ MotionEvent up = MotionEvent.obtain(
+ now, // downTime
+ now, // eventTime
+ MotionEvent.ACTION_UP, // action
+ position[0], // x
+ position[1], // y
+ 0 // metaState
+ );
+ up.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ instrumentation.sendPointerSync(up);
+
+ // Wait for idle timeout - 100 ms logner to avoid flaky
+ Thread.sleep(3100);
+
+ // Should not touch boost after the time out
+ assertEquals(false, mViewRoot.getIsTouchBoosting());
+ assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
+ mViewRoot.getLastPreferredFrameRateCategory());
+ }
+
+
@LargeTest
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
diff --git a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
index 40d0bef..28d6545 100644
--- a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
+++ b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
@@ -210,21 +210,6 @@
}
@Test
- public void registerOverlay_forAndroidPackage_shouldFail() {
- FabricatedOverlayInternal overlayInternal =
- createOverlayWithName(
- mOverlayName,
- SYSTEM_APP_OVERLAYABLE,
- "android",
- List.of(Pair.create("color/white", Pair.create(null, Color.BLACK))));
-
- assertThrows(
- "Wrong target package name",
- IllegalArgumentException.class,
- () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
- }
-
- @Test
public void getOverlayInfosForTarget_defaultShouldBeZero() {
List<OverlayInfo> overlayInfos =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 461a5ae..dfded73 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -102,6 +102,8 @@
private static volatile int sDefaultDensity = -1;
+ private long mId;
+
/**
* For backwards compatibility, allows the app layer to change the default
* density when running old apps.
@@ -152,18 +154,19 @@
Bitmap(long nativeBitmap, int width, int height, int density,
boolean requestPremultiplied, byte[] ninePatchChunk,
NinePatch.InsetStruct ninePatchInsets) {
- this(nativeBitmap, width, height, density, requestPremultiplied, ninePatchChunk,
+ this(0, nativeBitmap, width, height, density, requestPremultiplied, ninePatchChunk,
ninePatchInsets, true);
}
// called from JNI and Bitmap_Delegate.
- Bitmap(long nativeBitmap, int width, int height, int density,
+ Bitmap(long id, long nativeBitmap, int width, int height, int density,
boolean requestPremultiplied, byte[] ninePatchChunk,
NinePatch.InsetStruct ninePatchInsets, boolean fromMalloc) {
if (nativeBitmap == 0) {
throw new RuntimeException("internal error: native bitmap is 0");
}
+ mId = id;
mWidth = width;
mHeight = height;
mRequestPremultiplied = requestPremultiplied;
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 68d8ebb..b7a1c13 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -18,6 +18,8 @@
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_DEPRECATE_ELEGANT_TEXT_HEIGHT_API;
+
import android.annotation.ColorInt;
import android.annotation.ColorLong;
@@ -39,6 +41,7 @@
import android.text.SpannableString;
import android.text.SpannedString;
import android.text.TextUtils;
+import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.text.flags.Flags;
@@ -61,6 +64,7 @@
* geometries, text and bitmaps.
*/
public class Paint {
+ private static final String TAG = "Paint";
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private long mNativePaint;
@@ -1803,8 +1807,18 @@
/**
* Get the elegant metrics flag.
*
+ * Note:
+ * For applications target API 35 or later, this function returns true by default.
+ * For applications target API 36 or later, the function call will be ignored and the elegant
+ * text height is always enabled.
+ *
* @return true if elegant metrics are enabled for text drawing.
+ * @deprecated The underlying UI fonts are deprecated and will be removed from the system image.
+ * Applications supporting scripts with large vertical metrics should adapt their UI by using
+ * fonts designed with corresponding vertical metrics.
*/
+ @Deprecated
+ @FlaggedApi(FLAG_DEPRECATE_ELEGANT_TEXT_HEIGHT_API)
public boolean isElegantTextHeight() {
return nGetElegantTextHeight(mNativePaint) != ELEGANT_TEXT_HEIGHT_DISABLED;
}
@@ -1819,9 +1833,28 @@
* variants that have not been compacted to fit Latin-based vertical
* metrics, and also increases top and bottom bounds to provide more space.
*
+ * <p>
+ * Note:
+ * For applications target API 35 or later, the default value will be true by default.
+ * For applications target API 36 or later, the function call will be ignored and the elegant
+ * text height is always enabled.
+ *
* @param elegant set the paint's elegant metrics flag for drawing text.
+ * @deprecated This API will be no-op at some point in the future. The underlying UI fonts is
+ * deprecated and will be removed from the system image. Applications supporting scripts with
+ * large vertical metrics should adapt their UI by using fonts designed with corresponding
+ * vertical metrics.
*/
+ @Deprecated
+ @FlaggedApi(FLAG_DEPRECATE_ELEGANT_TEXT_HEIGHT_API)
public void setElegantTextHeight(boolean elegant) {
+ if (Flags.deprecateElegantTextHeightApi() && !elegant
+ && CompatChanges.isChangeEnabled(DEPRECATE_UI_FONT_ENFORCE)) {
+ if (!elegant) {
+ Log.w(TAG, "The elegant text height cannot be turned off.");
+ }
+ return;
+ }
nSetElegantTextHeight(mNativePaint,
elegant ? ELEGANT_TEXT_HEIGHT_ENABLED : ELEGANT_TEXT_HEIGHT_DISABLED);
}
@@ -1839,6 +1872,19 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
public static final long DEPRECATE_UI_FONT = 279646685L;
+ /**
+ * A change ID for deprecating UI fonts enforced.
+ *
+ * From API 36, the elegant text height will not be able to be overridden and always true if the
+ * app has a target SDK of API 36 or later.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = 36)
+ public static final long DEPRECATE_UI_FONT_ENFORCE = 349519475L;
+
+
private void resetElegantTextHeight() {
if (CompatChanges.isChangeEnabled(DEPRECATE_UI_FONT)) {
nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_UNSET);
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index 792e248..ed17fde 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -24,7 +24,6 @@
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.fonts.Font;
-import android.os.Build;
import com.android.internal.util.Preconditions;
import com.android.text.flags.Flags;
@@ -54,8 +53,6 @@
Typeface.class.getClassLoader(), nReleaseFunc());
}
- private static boolean sIsRobolectric = Build.FINGERPRINT.equals("robolectric");
-
private final long mLayoutPtr;
private final float mXOffset;
private final float mYOffset;
@@ -255,7 +252,7 @@
mXOffset = xOffset;
mYOffset = yOffset;
- if (!sIsRobolectric && Flags.typefaceRedesign()) {
+ if (Flags.typefaceRedesign()) {
int fontCount = nGetFontCount(layoutPtr);
mFonts = new ArrayList<>(fontCount);
for (int i = 0; i < fontCount; ++i) {
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/WindowAnimator.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/WindowAnimator.kt
index 0435085..91d66ea 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/WindowAnimator.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/WindowAnimator.kt
@@ -16,8 +16,9 @@
package com.android.wm.shell.shared.animation
-import android.animation.RectEvaluator
+import android.animation.PointFEvaluator
import android.animation.ValueAnimator
+import android.graphics.PointF
import android.graphics.Rect
import android.util.DisplayMetrics
import android.util.TypedValue
@@ -52,46 +53,56 @@
change: TransitionInfo.Change,
transaction: SurfaceControl.Transaction,
): ValueAnimator {
- val startBounds =
- createBounds(
+ val startPos =
+ getPosition(
displayMetrics,
- change.startAbsBounds,
+ change.endAbsBounds,
boundsAnimDef.startScale,
boundsAnimDef.startOffsetYDp,
)
val leash = change.leash
- val endBounds =
- createBounds(
+ val endPos =
+ getPosition(
displayMetrics,
- change.startAbsBounds,
+ change.endAbsBounds,
boundsAnimDef.endScale,
boundsAnimDef.endOffsetYDp,
)
- return ValueAnimator.ofObject(RectEvaluator(), startBounds, endBounds).apply {
+ return ValueAnimator.ofObject(PointFEvaluator(), startPos, endPos).apply {
duration = boundsAnimDef.durationMs
interpolator = boundsAnimDef.interpolator
addUpdateListener { animation ->
- val animBounds = animation.animatedValue as Rect
- val animScale = 1 - (1 - boundsAnimDef.endScale) * animation.animatedFraction
+ val animPos = animation.animatedValue as PointF
+ val animScale =
+ interpolate(
+ boundsAnimDef.startScale,
+ boundsAnimDef.endScale,
+ animation.animatedFraction
+ )
transaction
- .setPosition(leash, animBounds.left.toFloat(), animBounds.top.toFloat())
+ .setPosition(leash, animPos.x, animPos.y)
.setScale(leash, animScale, animScale)
.apply()
}
}
}
- private fun createBounds(
+ private fun interpolate(startVal: Float, endVal: Float, fraction: Float): Float {
+ require(fraction in 0.0f..1.0f)
+ return startVal + (endVal - startVal) * fraction
+ }
+
+ private fun getPosition(
displayMetrics: DisplayMetrics,
- origBounds: Rect,
+ bounds: Rect,
scale: Float,
offsetYDp: Float
- ) = Rect(origBounds).apply {
- check(scale in 0.0..1.0)
- // Scale the bounds down with an anchor in the center
- inset(
- (origBounds.width().toFloat() * (1 - scale) / 2).toInt(),
- (origBounds.height().toFloat() * (1 - scale) / 2).toInt(),
+ ) = PointF(bounds.left.toFloat(), bounds.top.toFloat()).apply {
+ check(scale in 0.0f..1.0f)
+ // Scale the bounds down with an anchor in the center
+ offset(
+ (bounds.width().toFloat() * (1 - scale) / 2),
+ (bounds.height().toFloat() * (1 - scale) / 2),
)
val offsetYPx =
TypedValue.applyDimension(
@@ -100,6 +111,6 @@
displayMetrics,
)
.toInt()
- offset(/* dx= */ 0, offsetYPx)
+ offset(/* dx= */ 0f, offsetYPx.toFloat())
}
}
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 ff3dc33..ae0485f 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
@@ -191,6 +191,16 @@
@Override
public void onResult(@Nullable Bundle result) {
mShellExecutor.execute(() -> {
+ if (mBackGestureStarted && result != null && result.getBoolean(
+ BackNavigationInfo.KEY_TOUCH_GESTURE_TRANSFERRED)) {
+ // Host app won't able to process motion event anymore, so pilfer
+ // pointers anyway.
+ if (mBackNavigationInfo != null) {
+ mBackNavigationInfo.disableAppProgressGenerationAllowed();
+ }
+ tryPilferPointers();
+ return;
+ }
if (!mBackGestureStarted || mPostCommitAnimationInProgress) {
// If an uninterruptible animation is already in progress, we should
// ignore this due to it may cause focus lost. (alpha = 0)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index c6cd320..615dad3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -88,6 +88,8 @@
import com.android.wm.shell.desktopmode.education.AppHandleEducationFilter;
import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository;
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer;
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializerImpl;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.GlobalDragListener;
import com.android.wm.shell.freeform.FreeformComponents;
@@ -840,8 +842,8 @@
@WMSingleton
@Provides
static ReturnToDragStartAnimator provideReturnToDragStartAnimator(
- Context context, InteractionJankMonitor interactionJankMonitor) {
- return new ReturnToDragStartAnimator(context, interactionJankMonitor);
+ InteractionJankMonitor interactionJankMonitor) {
+ return new ReturnToDragStartAnimator(interactionJankMonitor);
}
@WMSingleton
@@ -909,8 +911,12 @@
Context context,
ShellInit shellInit,
DesktopPersistentRepository desktopPersistentRepository,
- @ShellMainThread CoroutineScope mainScope) {
- return new DesktopRepository(context, shellInit, desktopPersistentRepository, mainScope);
+ DesktopRepositoryInitializer desktopRepositoryInitializer,
+ @ShellMainThread CoroutineScope mainScope
+ ) {
+ return new DesktopRepository(context, shellInit, desktopPersistentRepository,
+ desktopRepositoryInitializer,
+ mainScope);
}
@WMSingleton
@@ -1057,6 +1063,16 @@
return new DesktopPersistentRepository(context, bgScope);
}
+ @WMSingleton
+ @Provides
+ static DesktopRepositoryInitializer provideDesktopRepositoryInitializer(
+ Context context,
+ DesktopPersistentRepository desktopPersistentRepository,
+ @ShellMainThread CoroutineScope mainScope) {
+ return new DesktopRepositoryInitializerImpl(context, desktopPersistentRepository,
+ mainScope);
+ }
+
//
// Drag and drop
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 61de077..09e77fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR;
@@ -259,6 +260,7 @@
FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Desktop Mode Visual Indicator");
lp.setTrustedOverlay();
+ lp.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL;
final WindowlessWindowManager windowManager = new WindowlessWindowManager(
mTaskInfo.configuration, mLeash,
null /* hostInputToken */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 3864f1b..5648feb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -30,7 +30,7 @@
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
-import com.android.wm.shell.desktopmode.persistence.DesktopTaskState
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -46,6 +46,7 @@
private val context: Context,
shellInit: ShellInit,
private val persistentRepository: DesktopPersistentRepository,
+ private val repositoryInitializer: DesktopRepositoryInitializer,
@ShellMainThread private val mainCoroutineScope: CoroutineScope,
){
@@ -120,32 +121,7 @@
}
private fun initRepoFromPersistentStorage() {
- if (!Flags.enableDesktopWindowingPersistence()) return
- // TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized
- mainCoroutineScope.launch {
- val desktop = persistentRepository.readDesktop() ?: return@launch
-
- val maxTasks =
- DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
- ?: desktop.zOrderedTasksCount
-
- var visibleTasksCount = 0
- desktop.zOrderedTasksList
- // Reverse it so we initialize the repo from bottom to top.
- .reversed()
- .mapNotNull { taskId -> desktop.tasksByTaskIdMap[taskId] }
- .forEach { task ->
- if (task.desktopTaskState == DesktopTaskState.VISIBLE
- && visibleTasksCount < maxTasks
- ) {
- visibleTasksCount++
- addTask(desktop.displayId, task.taskId, isVisible = false)
- } else {
- addTask(desktop.displayId, task.taskId, isVisible = false)
- minimizeTask(desktop.displayId, task.taskId)
- }
- }
- }
+ repositoryInitializer.initialize(this)
}
/** Adds [activeTasksListener] to be notified of updates to active tasks. */
@@ -318,6 +294,9 @@
taskId, isVisible, displayId)
logD("VisibleTaskCount has changed from %d to %d", prevCount, newCount)
notifyVisibleTaskListeners(displayId, newCount)
+ if (Flags.enableDesktopWindowingPersistence()) {
+ updatePersistentRepository(displayId)
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 577c18c..133f069 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -54,6 +54,7 @@
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.widget.Toast
import android.window.DesktopModeFlags
import android.window.DesktopModeFlags.DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE
import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
@@ -877,7 +878,6 @@
taskSurface,
startBounds = currentDragBounds,
endBounds = containerBounds,
- isResizable = taskInfo.isResizeable
)
}
return
@@ -1012,7 +1012,6 @@
taskSurface,
startBounds = currentDragBounds,
endBounds = destinationBounds,
- isResizable = taskInfo.isResizeable,
)
}
return
@@ -1046,7 +1045,13 @@
taskSurface,
startBounds = currentDragBounds,
endBounds = dragStartBounds,
- isResizable = taskInfo.isResizeable,
+ doOnEnd = {
+ Toast.makeText(
+ context,
+ com.android.wm.shell.R.string.desktop_mode_non_resizable_snap_text,
+ Toast.LENGTH_SHORT
+ ).show()
+ },
)
} else {
val resizeTrigger = if (position == SnapPosition.LEFT) {
@@ -1984,17 +1989,32 @@
)
}
IndicatorType.NO_INDICATOR -> {
+ // Create a copy so that we can animate from the current bounds if we end up having
+ // to snap the surface back without a WCT change.
+ val destinationBounds = Rect(currentDragBounds)
// If task bounds are outside valid drag area, snap them inward
DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
- currentDragBounds,
+ destinationBounds,
validDragArea
)
- if (currentDragBounds == dragStartBounds) return
+ if (destinationBounds == dragStartBounds) {
+ // There's no actual difference between the start and end bounds, so while a
+ // WCT change isn't needed, the dragged surface still needs to be snapped back
+ // to its original location.
+ releaseVisualIndicator()
+ returnToDragStartAnimator.start(
+ taskInfo.taskId,
+ taskSurface,
+ startBounds = currentDragBounds,
+ endBounds = dragStartBounds,
+ )
+ return
+ }
// Update task bounds so that the task position will match the position of its leash
val wct = WindowContainerTransaction()
- wct.setBounds(taskInfo.token, currentDragBounds)
+ wct.setBounds(taskInfo.token, destinationBounds)
transitions.startTransition(TRANSIT_CHANGE, wct, null)
releaseVisualIndicator()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
index f4df42c..4e08d10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
@@ -19,28 +19,24 @@
import android.animation.Animator
import android.animation.RectEvaluator
import android.animation.ValueAnimator
-import android.content.Context
import android.graphics.Rect
import android.view.SurfaceControl
-import android.widget.Toast
import androidx.core.animation.addListener
import com.android.internal.jank.Cuj
import com.android.internal.jank.InteractionJankMonitor
-import com.android.wm.shell.R
import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener
import java.util.function.Supplier
/** Animates the task surface moving from its current drag position to its pre-drag position. */
class ReturnToDragStartAnimator(
- private val context: Context,
private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
private val interactionJankMonitor: InteractionJankMonitor
) {
private var boundsAnimator: Animator? = null
private lateinit var taskRepositionAnimationListener: OnTaskRepositionAnimationListener
- constructor(context: Context, interactionJankMonitor: InteractionJankMonitor) :
- this(context, Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
+ constructor(interactionJankMonitor: InteractionJankMonitor) :
+ this(Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
/** Sets a listener for the start and end of the reposition animation. */
fun setTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) {
@@ -53,7 +49,7 @@
taskSurface: SurfaceControl,
startBounds: Rect,
endBounds: Rect,
- isResizable: Boolean
+ doOnEnd: (() -> Unit)? = null,
) {
val tx = transactionSupplier.get()
@@ -87,13 +83,7 @@
.apply()
taskRepositionAnimationListener.onAnimationEnd(taskId)
boundsAnimator = null
- if (!isResizable) {
- Toast.makeText(
- context,
- R.string.desktop_mode_non_resizable_snap_text,
- Toast.LENGTH_SHORT
- ).show()
- }
+ doOnEnd?.invoke()
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
}
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
index 2d11e02..9e646f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
@@ -50,7 +50,9 @@
DataStoreFactory.create(
serializer = DesktopPersistentRepositoriesSerializer,
produceFile = { context.dataStoreFile(DESKTOP_REPOSITORIES_DATASTORE_FILE) },
- scope = bgCoroutineScope))
+ scope = bgCoroutineScope,
+ ),
+ )
/** Provides `dataStore.data` flow and handles exceptions thrown during collection */
private val dataStoreFlow: Flow<DesktopPersistentRepositories> =
@@ -116,7 +118,11 @@
val desktop =
getDesktop(currentRepository, desktopId)
.toBuilder()
- .updateTaskStates(visibleTasks, minimizedTasks)
+ .updateTaskStates(
+ visibleTasks,
+ minimizedTasks,
+ freeformTasksInZOrder,
+ )
.updateZOrder(freeformTasksInZOrder)
desktopPersistentRepositories
@@ -169,9 +175,21 @@
private fun Desktop.Builder.updateTaskStates(
visibleTasks: ArraySet<Int>,
- minimizedTasks: ArraySet<Int>
+ minimizedTasks: ArraySet<Int>,
+ freeformTasksInZOrder: ArrayList<Int>,
): Desktop.Builder {
clearTasksByTaskId()
+
+ // Handle the case where tasks are not marked as visible but are meant to be visible
+ // after reboot. E.g. User moves out of desktop when there are multiple tasks are
+ // visible, they will be marked as not visible afterwards. This ensures that they are
+ // still persisted as visible.
+ // TODO - b/350476823: Remove this logic once repository holds expanded tasks
+ if (freeformTasksInZOrder.size > visibleTasks.size + minimizedTasks.size &&
+ visibleTasks.isEmpty()
+ ) {
+ visibleTasks.addAll(freeformTasksInZOrder.filterNot { it in minimizedTasks })
+ }
putAllTasksByTaskId(
visibleTasks.associateWith {
createDesktopTask(it, state = DesktopTaskState.VISIBLE)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
copy to libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt
index 06592b1..771c3d1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt
@@ -14,13 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.qs.ui.viewmodel
+package com.android.wm.shell.desktopmode.persistence
-import com.android.systemui.kosmos.Kosmos
+import com.android.wm.shell.desktopmode.DesktopRepository
-val Kosmos.quickSettingsShadeUserActionsViewModel: QuickSettingsShadeUserActionsViewModel by
- Kosmos.Fixture {
- QuickSettingsShadeUserActionsViewModel(
- quickSettingsContainerViewModel = quickSettingsContainerViewModel,
- )
- }
+/** Interface for initializing the [DesktopRepository]. */
+fun interface DesktopRepositoryInitializer {
+ fun initialize(repository: DesktopRepository)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
new file mode 100644
index 0000000..fc4ed15
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode.persistence
+
+import android.content.Context
+import com.android.window.flags.Flags
+import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Initializes the [DesktopRepository] from the [DesktopPersistentRepository].
+ *
+ * This class is responsible for reading the [DesktopPersistentRepository] and initializing the
+ * [DesktopRepository] with the tasks that previously existed in desktop.
+ */
+class DesktopRepositoryInitializerImpl(
+ private val context: Context,
+ private val persistentRepository: DesktopPersistentRepository,
+ @ShellMainThread private val mainCoroutineScope: CoroutineScope,
+) : DesktopRepositoryInitializer {
+ override fun initialize(repository: DesktopRepository) {
+ if (!Flags.enableDesktopWindowingPersistence()) return
+ // TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized
+ mainCoroutineScope.launch {
+ val desktop = persistentRepository.readDesktop() ?: return@launch
+
+ val maxTasks =
+ DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
+ ?: desktop.zOrderedTasksCount
+
+ var visibleTasksCount = 0
+ desktop.zOrderedTasksList
+ // Reverse it so we initialize the repo from bottom to top.
+ .reversed()
+ .mapNotNull { taskId -> desktop.tasksByTaskIdMap[taskId] }
+ .forEach { task ->
+ if (task.desktopTaskState == DesktopTaskState.VISIBLE
+ && visibleTasksCount < maxTasks
+ ) {
+ visibleTasksCount++
+ repository.addTask(desktop.displayId, task.taskId, isVisible = false)
+ } else {
+ repository.addTask(desktop.displayId, task.taskId, isVisible = false)
+ repository.minimizeTask(desktop.displayId, task.taskId)
+ }
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 58337ec..e848b88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -27,6 +27,7 @@
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArrayMap;
+import android.util.DisplayMetrics;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -38,6 +39,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.animation.MinimizeAnimator;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
@@ -137,7 +139,7 @@
break;
case WindowManager.TRANSIT_TO_BACK:
transitionHandled |= startMinimizeTransition(
- transition, info.getType(), change);
+ transition, info.getType(), change, finishT, animations, onAnimFinish);
break;
case WindowManager.TRANSIT_CLOSE:
if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
@@ -206,7 +208,10 @@
private boolean startMinimizeTransition(
IBinder transition,
int type,
- TransitionInfo.Change change) {
+ TransitionInfo.Change change,
+ SurfaceControl.Transaction finishT,
+ ArrayList<Animator> animations,
+ Runnable onAnimFinish) {
if (!mPendingTransitionTokens.contains(transition)) {
return false;
}
@@ -215,7 +220,23 @@
if (type != Transitions.TRANSIT_MINIMIZE) {
return false;
}
- // TODO(b/361524575): Add minimize animations
+
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ SurfaceControl sc = change.getLeash();
+ finishT.hide(sc);
+ final DisplayMetrics displayMetrics =
+ mDisplayController
+ .getDisplayContext(taskInfo.displayId).getResources().getDisplayMetrics();
+ final Animator animator = MinimizeAnimator.create(
+ displayMetrics,
+ change,
+ t,
+ (anim) -> {
+ animations.remove(anim);
+ onAnimFinish.run();
+ return null;
+ });
+ animations.add(animator);
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
index a93ef12..3f9b0c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip2.animation;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -27,6 +28,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.shared.animation.Interpolators;
@@ -34,8 +36,7 @@
/**
* Animator that handles bounds animations for exit-via-expanding PIP.
*/
-public class PipExpandAnimator extends ValueAnimator
- implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
+public class PipExpandAnimator extends ValueAnimator {
@NonNull
private final SurfaceControl mLeash;
private final SurfaceControl.Transaction mStartTransaction;
@@ -58,12 +59,61 @@
// Bounds updated by the evaluator as animator is running.
private final Rect mAnimatedRect = new Rect();
- private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
private final RectEvaluator mRectEvaluator;
private final RectEvaluator mInsetEvaluator;
private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
+ private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ if (mAnimationStartCallback != null) {
+ mAnimationStartCallback.run();
+ }
+ if (mStartTransaction != null) {
+ mStartTransaction.apply();
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (mFinishTransaction != null) {
+ // finishTransaction might override some state (eg. corner radii) so we want to
+ // manually set the state to the end of the animation
+ mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash,
+ mSourceRectHint, mBaseBounds, mAnimatedRect, getInsets(1f),
+ false /* isInPipDirection */, 1f)
+ .round(mFinishTransaction, mLeash, false /* applyCornerRadius */)
+ .shadow(mFinishTransaction, mLeash, false /* applyCornerRadius */);
+ }
+ if (mAnimationEndCallback != null) {
+ mAnimationEndCallback.run();
+ }
+ }
+ };
+
+ private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener =
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ final float fraction = getAnimatedFraction();
+
+ // TODO (b/350801661): implement fixed rotation
+ Rect insets = getInsets(fraction);
+ mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint,
+ mBaseBounds, mAnimatedRect,
+ insets, false /* isInPipDirection */, fraction)
+ .round(tx, mLeash, false /* applyCornerRadius */)
+ .shadow(tx, mLeash, false /* applyCornerRadius */);
+ tx.apply();
+ }
+ };
+
public PipExpandAnimator(Context context,
@NonNull SurfaceControl leash,
SurfaceControl.Transaction startTransaction,
@@ -105,8 +155,8 @@
setObjectValues(startBounds, endBounds);
setEvaluator(mRectEvaluator);
setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- addListener(this);
- addUpdateListener(this);
+ addListener(mAnimatorListener);
+ addUpdateListener(mAnimatorUpdateListener);
}
public void setAnimationStartCallback(@NonNull Runnable runnable) {
@@ -117,58 +167,15 @@
mAnimationEndCallback = runnable;
}
- @Override
- public void onAnimationStart(@NonNull Animator animation) {
- if (mAnimationStartCallback != null) {
- mAnimationStartCallback.run();
- }
- if (mStartTransaction != null) {
- mStartTransaction.apply();
- }
- }
-
- @Override
- public void onAnimationEnd(@NonNull Animator animation) {
- if (mFinishTransaction != null) {
- // finishTransaction might override some state (eg. corner radii) so we want to
- // manually set the state to the end of the animation
- mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash, mSourceRectHint,
- mBaseBounds, mAnimatedRect, getInsets(1f),
- false /* isInPipDirection */, 1f)
- .round(mFinishTransaction, mLeash, false /* applyCornerRadius */)
- .shadow(mFinishTransaction, mLeash, false /* applyCornerRadius */);
- }
- if (mAnimationEndCallback != null) {
- mAnimationEndCallback.run();
- }
- }
-
- @Override
- public void onAnimationUpdate(@NonNull ValueAnimator animation) {
- final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
- final float fraction = getAnimatedFraction();
-
- // TODO (b/350801661): implement fixed rotation
-
- Rect insets = getInsets(fraction);
- mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint,
- mBaseBounds, mAnimatedRect, insets, false /* isInPipDirection */, fraction)
- .round(tx, mLeash, false /* applyCornerRadius */)
- .shadow(tx, mLeash, false /* applyCornerRadius */);
- tx.apply();
- }
-
private Rect getInsets(float fraction) {
final Rect startInsets = mSourceRectHintInsets;
final Rect endInsets = mZeroInsets;
return mInsetEvaluator.evaluate(fraction, startInsets, endInsets);
}
- // no-ops
-
- @Override
- public void onAnimationCancel(@NonNull Animator animation) {}
-
- @Override
- public void onAnimationRepeat(@NonNull Animator animation) {}
+ @VisibleForTesting
+ void setSurfaceControlTransactionFactory(
+ @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+ mSurfaceControlTransactionFactory = factory;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index 8f089dc..a7087ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -23,6 +23,7 @@
import android.graphics.Bitmap
import android.graphics.Rect
import android.os.IBinder
+import android.os.UserHandle
import android.util.Slog
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
@@ -126,7 +127,6 @@
resizeMetadata.getLeash(),
startBounds = currentBounds,
endBounds = destinationBounds,
- isResizable = taskInfo.isResizeable,
)
}
}
@@ -361,6 +361,8 @@
private lateinit var resizeVeilBitmap: Bitmap
private lateinit var resizeVeil: ResizeVeil
private val displayContext = displayController.getDisplayContext(taskInfo.displayId)
+ private val userContext =
+ context.createContextAsUser(UserHandle.of(taskInfo.userId), /* flags= */ 0)
fun initIfNeeded() {
if (!isInitialised) {
@@ -379,7 +381,7 @@
displayContext?.let {
createIconFactory(displayContext, R.dimen.desktop_mode_resize_veil_icon_size)
} ?: return
- val pm = context.getApplicationContext().getPackageManager()
+ val pm = userContext.getPackageManager()
val activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */)
val provider = IconProvider(displayContext)
val appIconDrawable = provider.getIcon(activityInfo)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index 9747b19..aabd973 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -43,6 +43,7 @@
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
@@ -50,10 +51,10 @@
import junit.framework.Assert.assertTrue
import kotlin.test.assertNotNull
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.setMain
@@ -94,6 +95,7 @@
@Mock lateinit var resizeTransitionHandler: ToggleResizeDesktopTaskTransitionHandler
@Mock lateinit var taskStackListener: TaskStackListenerImpl
@Mock lateinit var persistentRepository: DesktopPersistentRepository
+ @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var handler: DesktopActivityOrientationChangeHandler
@@ -116,7 +118,13 @@
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
taskRepository =
- DesktopRepository(context, shellInit, persistentRepository, testScope)
+ DesktopRepository(
+ context,
+ shellInit,
+ persistentRepository,
+ repositoryInitializer,
+ testScope
+ )
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
index 1c4b9bf..e05a0b5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
@@ -81,7 +81,7 @@
@Before
fun setUp() {
desktopRepository = DesktopRepository(
- context, ShellInit(TestShellExecutor()), mock(), mock()
+ context, ShellInit(TestShellExecutor()), mock(), mock(), mock()
)
whenever(mockDisplayController.getDisplayLayout(DEFAULT_DISPLAY))
.thenReturn(mockDisplayLayout)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 715b045..414c1a6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -30,6 +30,7 @@
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.sysui.ShellInit
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.fail
@@ -52,6 +53,7 @@
import org.mockito.Mockito.spy
import org.mockito.kotlin.any
import org.mockito.kotlin.never
+import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -68,6 +70,7 @@
@Mock private lateinit var testExecutor: ShellExecutor
@Mock private lateinit var persistentRepository: DesktopPersistentRepository
+ @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
@Before
fun setUp() {
@@ -75,7 +78,14 @@
datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- repo = DesktopRepository(context, shellInit, persistentRepository, datastoreScope)
+ repo =
+ DesktopRepository(
+ context,
+ shellInit,
+ persistentRepository,
+ repositoryInitializer,
+ datastoreScope
+ )
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
Desktop.getDefaultInstance()
)
@@ -218,6 +228,33 @@
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ fun updateTaskVisibility_multipleTasks_persistsVisibleTasks() =
+ runTest(StandardTestDispatcher()) {
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
+ repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true)
+
+ inOrder(persistentRepository).run {
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(arrayOf(1)),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = arrayListOf()
+ )
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(arrayOf(1, 2)),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = arrayListOf()
+ )
+ }
+ }
+
+ @Test
fun isOnlyVisibleNonClosingTask_singleVisibleClosingTask() {
repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.addClosingTask(DEFAULT_DISPLAY, 1)
@@ -605,7 +642,7 @@
minimizedTasks = ArraySet(),
freeformTasksInZOrder = arrayListOf(7, 6, 5)
)
- verify(persistentRepository)
+ verify(persistentRepository, times(2))
.addOrUpdateDesktop(
DEFAULT_USER_ID,
DEFAULT_DESKTOP_ID,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 051079c..fc89b0e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -110,6 +110,7 @@
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.recents.RecentTasksController
@@ -224,6 +225,7 @@
@Mock private lateinit var mockInputManager: InputManager
@Mock private lateinit var mockFocusTransitionObserver: FocusTransitionObserver
@Mock lateinit var motionEvent: MotionEvent
+ @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
private lateinit var mockitoSession: StaticMockitoSession
@Mock
@@ -264,7 +266,8 @@
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- taskRepository = DesktopRepository(context, shellInit, persistentRepository, testScope)
+ taskRepository =
+ DesktopRepository(context, shellInit, persistentRepository, repositoryInitializer, testScope)
desktopTasksLimiter =
DesktopTasksLimiter(
transitions,
@@ -3026,7 +3029,7 @@
eq(mockSurface),
eq(currentDragBounds),
eq(STABLE_BOUNDS),
- eq(true)
+ anyOrNull(),
)
}
@@ -3341,7 +3344,7 @@
eq(mockSurface),
eq(currentDragBounds),
eq(bounds),
- eq(true)
+ anyOrNull(),
)
verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
ResizeTrigger.SNAP_LEFT_MENU,
@@ -3398,7 +3401,7 @@
eq(mockSurface),
eq(currentDragBounds),
eq(preDragBounds),
- eq(false)
+ any(),
)
verify(desktopModeEventLogger, never()).logTaskResizingStarted(
any(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index d44728d..01b69ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -41,6 +41,7 @@
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
@@ -89,6 +90,7 @@
@Mock lateinit var handler: Handler
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var persistentRepository: DesktopPersistentRepository
+ @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
@@ -106,7 +108,13 @@
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
desktopTaskRepo =
- DesktopRepository(context, shellInit, persistentRepository, testScope)
+ DesktopRepository(
+ context,
+ shellInit,
+ persistentRepository,
+ repositoryInitializer,
+ testScope
+ )
desktopTasksLimiter =
DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT,
interactionJankMonitor, mContext, handler)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
index 7dbadc9..5419887 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
@@ -68,6 +68,7 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
@OptIn(ExperimentalCoroutinesApi::class)
+@Ignore("b/374328725")
class AppHandleEducationControllerTest : ShellTestCase() {
@JvmField
@Rule
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
new file mode 100644
index 0000000..9753429
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode.persistence
+
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.sysui.ShellInit
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.spy
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
+class DesktopRepositoryInitializerTest : ShellTestCase() {
+
+ private lateinit var repositoryInitializer: DesktopRepositoryInitializer
+ private lateinit var shellInit: ShellInit
+ private lateinit var datastoreScope: CoroutineScope
+
+ private lateinit var desktopRepository: DesktopRepository
+ private val persistentRepository = mock<DesktopPersistentRepository>()
+ private val testExecutor = mock<ShellExecutor>()
+
+ @Before
+ fun setUp() {
+ Dispatchers.setMain(StandardTestDispatcher())
+ shellInit = spy(ShellInit(testExecutor))
+ datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
+ repositoryInitializer =
+ DesktopRepositoryInitializerImpl(context, persistentRepository, datastoreScope)
+ desktopRepository =
+ DesktopRepository(
+ context, shellInit, persistentRepository, repositoryInitializer, datastoreScope)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ fun initWithPersistence_multipleTasks_addedCorrectly() =
+ runTest(StandardTestDispatcher()) {
+ val freeformTasksInZOrder = listOf(1, 2, 3)
+ whenever(persistentRepository.readDesktop(any(), any()))
+ .thenReturn(
+ Desktop.newBuilder()
+ .setDesktopId(1)
+ .addAllZOrderedTasks(freeformTasksInZOrder)
+ .putTasksByTaskId(
+ 1,
+ DesktopTask.newBuilder()
+ .setTaskId(1)
+ .setDesktopTaskState(DesktopTaskState.VISIBLE)
+ .build())
+ .putTasksByTaskId(
+ 2,
+ DesktopTask.newBuilder()
+ .setTaskId(2)
+ .setDesktopTaskState(DesktopTaskState.VISIBLE)
+ .build())
+ .putTasksByTaskId(
+ 3,
+ DesktopTask.newBuilder()
+ .setTaskId(3)
+ .setDesktopTaskState(DesktopTaskState.MINIMIZED)
+ .build())
+ .build())
+
+ repositoryInitializer.initialize(desktopRepository)
+
+ verify(persistentRepository).readDesktop(any(), any())
+ assertThat(desktopRepository.getActiveTasks(DEFAULT_DISPLAY))
+ .containsExactly(1, 2, 3)
+ .inOrder()
+ assertThat(desktopRepository.getExpandedTasksOrdered(DEFAULT_DISPLAY))
+ .containsExactly(1, 2)
+ .inOrder()
+ assertThat(desktopRepository.getMinimizedTasks(DEFAULT_DISPLAY)).containsExactly(3)
+ }
+
+ @After
+ fun tearDown() {
+ datastoreScope.cancel()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
new file mode 100644
index 0000000..e19a10a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.animation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test against {@link PipExpandAnimator}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipExpandAnimatorTest {
+
+ @Mock private Context mMockContext;
+
+ @Mock private Resources mMockResources;
+
+ @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
+
+ @Mock private SurfaceControl.Transaction mMockTransaction;
+
+ @Mock private SurfaceControl.Transaction mMockStartTransaction;
+
+ @Mock private SurfaceControl.Transaction mMockFinishTransaction;
+
+ @Mock private Runnable mMockStartCallback;
+
+ @Mock private Runnable mMockEndCallback;
+
+ private PipExpandAnimator mPipExpandAnimator;
+ private Rect mBaseBounds;
+ private Rect mStartBounds;
+ private Rect mEndBounds;
+ private Rect mSourceRectHint;
+ @Surface.Rotation private int mRotation;
+ private SurfaceControl mTestLeash;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getInteger(anyInt())).thenReturn(0);
+ when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
+ // No-op on the mMockTransaction
+ when(mMockTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockTransaction);
+ when(mMockTransaction.setCrop(any(SurfaceControl.class), any(Rect.class)))
+ .thenReturn(mMockTransaction);
+ when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockTransaction);
+ when(mMockTransaction.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockTransaction);
+ when(mMockTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockTransaction);
+ // Do the same for mMockFinishTransaction
+ when(mMockFinishTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockFinishTransaction.setCrop(any(SurfaceControl.class), any(Rect.class)))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockFinishTransaction.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockFinishTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockFinishTransaction);
+
+ mTestLeash = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setName("PipExpandAnimatorTest")
+ .setCallsite("PipExpandAnimatorTest")
+ .build();
+ }
+
+ @Test
+ public void setAnimationStartCallback_expand_callbackStartCallback() {
+ mRotation = Surface.ROTATION_0;
+ mBaseBounds = new Rect(0, 0, 1_000, 2_000);
+ mStartBounds = new Rect(500, 1_000, 1_000, 2_000);
+ mEndBounds = new Rect(mBaseBounds);
+ mPipExpandAnimator = new PipExpandAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mBaseBounds, mStartBounds, mEndBounds, mSourceRectHint,
+ mRotation);
+ mPipExpandAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipExpandAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipExpandAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipExpandAnimator.start();
+ mPipExpandAnimator.pause();
+ });
+
+ verify(mMockStartCallback).run();
+ verifyZeroInteractions(mMockEndCallback);
+ }
+
+ @Test
+ public void setAnimationEndCallback_expand_callbackStartAndEndCallback() {
+ mRotation = Surface.ROTATION_0;
+ mBaseBounds = new Rect(0, 0, 1_000, 2_000);
+ mStartBounds = new Rect(500, 1_000, 1_000, 2_000);
+ mEndBounds = new Rect(mBaseBounds);
+ mPipExpandAnimator = new PipExpandAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mBaseBounds, mStartBounds, mEndBounds, mSourceRectHint,
+ mRotation);
+ mPipExpandAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipExpandAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipExpandAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipExpandAnimator.start();
+ mPipExpandAnimator.end();
+ });
+
+ verify(mMockStartCallback).run();
+ verify(mMockEndCallback).run();
+ }
+
+ @Test
+ public void onAnimationEnd_expand_leashIsFullscreen() {
+ mRotation = Surface.ROTATION_0;
+ mBaseBounds = new Rect(0, 0, 1_000, 2_000);
+ mStartBounds = new Rect(500, 1_000, 1_000, 2_000);
+ mEndBounds = new Rect(mBaseBounds);
+ mPipExpandAnimator = new PipExpandAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mBaseBounds, mStartBounds, mEndBounds, mSourceRectHint,
+ mRotation);
+ mPipExpandAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipExpandAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipExpandAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipExpandAnimator.start();
+ clearInvocations(mMockTransaction);
+ mPipExpandAnimator.end();
+ });
+
+ verify(mMockTransaction).setCrop(mTestLeash, mEndBounds);
+ verify(mMockTransaction).setCornerRadius(mTestLeash, 0f);
+ verify(mMockTransaction).setShadowRadius(mTestLeash, 0f);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/WindowAnimatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/WindowAnimatorTest.kt
index 841ffcc..c19232b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/WindowAnimatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/animation/WindowAnimatorTest.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.shared.animation
-import android.animation.ValueAnimator
+import android.graphics.PointF
import android.graphics.Rect
import android.util.DisplayMetrics
import android.view.SurfaceControl
@@ -31,6 +31,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -44,12 +45,33 @@
private val displayMetrics = DisplayMetrics().apply { density = 1f }
+ private val positionXArgumentCaptor = argumentCaptor<Float>()
+ private val positionYArgumentCaptor = argumentCaptor<Float>()
+ private val scaleXArgumentCaptor = argumentCaptor<Float>()
+ private val scaleYArgumentCaptor = argumentCaptor<Float>()
+
@Before
fun setup() {
whenever(change.leash).thenReturn(leash)
- whenever(change.startAbsBounds).thenReturn(START_BOUNDS)
+ whenever(change.endAbsBounds).thenReturn(END_BOUNDS)
whenever(transaction.setPosition(any(), anyFloat(), anyFloat())).thenReturn(transaction)
whenever(transaction.setScale(any(), anyFloat(), anyFloat())).thenReturn(transaction)
+ whenever(
+ transaction.setPosition(
+ any(),
+ positionXArgumentCaptor.capture(),
+ positionYArgumentCaptor.capture(),
+ )
+ )
+ .thenReturn(transaction)
+ whenever(
+ transaction.setScale(
+ any(),
+ scaleXArgumentCaptor.capture(),
+ scaleYArgumentCaptor.capture(),
+ )
+ )
+ .thenReturn(transaction)
}
@Test
@@ -67,16 +89,18 @@
change,
transaction
)
+ valueAnimator.start()
assertThat(valueAnimator.duration).isEqualTo(100L)
assertThat(valueAnimator.interpolator).isEqualTo(Interpolators.STANDARD_ACCELERATE)
- assertStartAndEndBounds(valueAnimator, startBounds = START_BOUNDS, endBounds = START_BOUNDS)
+ val expectedPosition = PointF(END_BOUNDS.left.toFloat(), END_BOUNDS.top.toFloat())
+ assertTransactionParams(expectedPosition, expectedScale = PointF(1f, 1f))
}
@Test
- fun createBoundsAnimator_startScaleAndOffset_returnsCorrectBounds() = runOnUiThread {
+ fun createBoundsAnimator_startScaleAndOffset_correctPosAndScale() = runOnUiThread {
val bounds = Rect(/* left= */ 100, /* top= */ 200, /* right= */ 300, /* bottom= */ 400)
- whenever(change.startAbsBounds).thenReturn(bounds)
+ whenever(change.endAbsBounds).thenReturn(bounds)
val boundsAnimParams =
WindowAnimator.BoundsAnimationParams(
durationMs = 100L,
@@ -92,19 +116,18 @@
change,
transaction
)
+ valueAnimator.start()
- assertStartAndEndBounds(
- valueAnimator,
- startBounds =
- Rect(/* left= */ 150, /* top= */ 260, /* right= */ 250, /* bottom= */ 360),
- endBounds = bounds,
+ assertTransactionParams(
+ expectedPosition = PointF(150f, 260f),
+ expectedScale = PointF(0.5f, 0.5f),
)
}
@Test
- fun createBoundsAnimator_endScaleAndOffset_returnsCorrectBounds() = runOnUiThread {
+ fun createBoundsAnimator_endScaleAndOffset_correctPosAndScale() = runOnUiThread {
val bounds = Rect(/* left= */ 100, /* top= */ 200, /* right= */ 300, /* bottom= */ 400)
- whenever(change.startAbsBounds).thenReturn(bounds)
+ whenever(change.endAbsBounds).thenReturn(bounds)
val boundsAnimParams =
WindowAnimator.BoundsAnimationParams(
durationMs = 100L,
@@ -120,28 +143,56 @@
change,
transaction
)
+ valueAnimator.start()
+ valueAnimator.end()
- assertStartAndEndBounds(
- valueAnimator,
- startBounds = bounds,
- endBounds = Rect(/* left= */ 150, /* top= */ 260, /* right= */ 250, /* bottom= */ 360),
+ assertTransactionParams(
+ expectedPosition = PointF(150f, 260f),
+ expectedScale = PointF(0.5f, 0.5f),
)
}
- private fun assertStartAndEndBounds(
- valueAnimator: ValueAnimator,
- startBounds: Rect,
- endBounds: Rect,
- ) {
- valueAnimator.start()
- valueAnimator.animatedValue
- assertThat(valueAnimator.animatedValue).isEqualTo(startBounds)
- valueAnimator.end()
- assertThat(valueAnimator.animatedValue).isEqualTo(endBounds)
+ @Test
+ fun createBoundsAnimator_middleOfAnimation_correctPosAndScale() = runOnUiThread {
+ val bounds = Rect(/* left= */ 100, /* top= */ 200, /* right= */ 300, /* bottom= */ 400)
+ whenever(change.endAbsBounds).thenReturn(bounds)
+ val boundsAnimParams =
+ WindowAnimator.BoundsAnimationParams(
+ durationMs = 100L,
+ endOffsetYDp = 10f,
+ startScale = 0.5f,
+ endScale = 0.9f,
+ interpolator = Interpolators.LINEAR,
+ )
+
+ val valueAnimator =
+ WindowAnimator.createBoundsAnimator(
+ displayMetrics,
+ boundsAnimParams,
+ change,
+ transaction
+ )
+ valueAnimator.currentPlayTime = 50
+
+ assertTransactionParams(
+ // We should have a window of size 140x140, which we centre by placing at pos 130, 230.
+ // Then add 10*0.5 as y-offset
+ expectedPosition = PointF(130f, 235f),
+ expectedScale = PointF(0.7f, 0.7f),
+ )
+ }
+
+ private fun assertTransactionParams(expectedPosition: PointF, expectedScale: PointF) {
+ assertThat(positionXArgumentCaptor.lastValue).isWithin(TOLERANCE).of(expectedPosition.x)
+ assertThat(positionYArgumentCaptor.lastValue).isWithin(TOLERANCE).of(expectedPosition.y)
+ assertThat(scaleXArgumentCaptor.lastValue).isWithin(TOLERANCE).of(expectedScale.x)
+ assertThat(scaleYArgumentCaptor.lastValue).isWithin(TOLERANCE).of(expectedScale.y)
}
companion object {
- private val START_BOUNDS =
+ private val END_BOUNDS =
Rect(/* left= */ 10, /* top= */ 20, /* right= */ 30, /* bottom= */ 40)
+
+ private const val TOLERANCE = 1e-3f
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
index cb8c743..1215c52 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt
@@ -64,6 +64,7 @@
context = context,
shellInit = ShellInit(TestShellExecutor()),
persistentRepository = mock(),
+ repositoryInitializer = mock(),
mainCoroutineScope = mock()
)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
index 0ccd424..52e93bb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
@@ -39,6 +39,7 @@
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -73,6 +74,7 @@
returnToDragStartAnimatorMock,
desktopRepository,
)
+ whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
index 0b04a21..2ae2461 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
@@ -49,6 +49,7 @@
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.capture
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
@@ -112,6 +113,7 @@
returnToDragStartAnimator,
desktopRepository,
)
+ whenever(context.createContextAsUser(any(), any())).thenReturn(context)
}
@Test
@@ -195,7 +197,7 @@
verify(toggleResizeDesktopTaskTransitionHandler, times(1))
.startTransition(capture(wctCaptor), any())
- verify(returnToDragStartAnimator, times(1)).start(any(), any(), any(), any(), any())
+ verify(returnToDragStartAnimator, times(1)).start(any(), any(), any(), any(), anyOrNull())
for (change in wctCaptor.value.changes) {
val bounds = change.value.configuration.windowConfiguration.bounds
val leftBounds = getLeftTaskBounds()
@@ -479,6 +481,7 @@
assertThat(tilingDecoration.rightTaskResizingHelper).isNull()
verify(desktopWindowDecoration, times(2)).removeDragResizeListener(any())
verify(tiledTaskHelper, times(2)).dispose()
+ verify(context, never()).getApplicationContext()
}
private fun initTiledTaskHelperMock(taskInfo: ActivityManager.RunningTaskInfo) {
diff --git a/libs/androidfw/PngCrunch.cpp b/libs/androidfw/PngCrunch.cpp
index cf3c0ee..e945405 100644
--- a/libs/androidfw/PngCrunch.cpp
+++ b/libs/androidfw/PngCrunch.cpp
@@ -506,8 +506,7 @@
// Set up the write functions which write to our custom data sources.
png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
- // We want small files and can take the performance hit to achieve this goal.
- png_set_compression_level(write_ptr, Z_BEST_COMPRESSION);
+ png_set_compression_level(write_ptr, options.compression_level);
// Begin analysis of the image data.
// Scan the entire image and determine if:
diff --git a/libs/androidfw/include/androidfw/Png.h b/libs/androidfw/include/androidfw/Png.h
index 2ece43e..72be59b 100644
--- a/libs/androidfw/include/androidfw/Png.h
+++ b/libs/androidfw/include/androidfw/Png.h
@@ -31,6 +31,8 @@
struct PngOptions {
int grayscale_tolerance = 0;
+ // By default we want small files and can take the performance hit to achieve this goal.
+ int compression_level = 9;
};
/**
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 93df478..f255967 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -139,3 +139,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "bitmap_ashmem_long_name"
+ namespace: "core_graphics"
+ description: "Whether to have more information in ashmem filenames for bitmaps"
+ bug: "369619160"
+}
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index b73380e..cc292d9 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -15,6 +15,7 @@
*/
#include "Bitmap.h"
+#include <android-base/file.h>
#include "HardwareBitmapUploader.h"
#include "Properties.h"
#ifdef __ANDROID__ // Layoutlib does not support render thread
@@ -46,16 +47,27 @@
#include <SkImage.h>
#include <SkImageAndroid.h>
#include <SkImagePriv.h>
+#include <SkJpegEncoder.h>
#include <SkJpegGainmapEncoder.h>
#include <SkPixmap.h>
+#include <SkPngEncoder.h>
#include <SkRect.h>
#include <SkStream.h>
-#include <SkJpegEncoder.h>
-#include <SkPngEncoder.h>
#include <SkWebpEncoder.h>
+#include <atomic>
+#include <format>
#include <limits>
+#ifdef __ANDROID__
+#include <com_android_graphics_hwui_flags.h>
+namespace hwui_flags = com::android::graphics::hwui::flags;
+#else
+namespace hwui_flags {
+constexpr bool bitmap_ashmem_long_name() { return false; }
+}
+#endif
+
namespace android {
#ifdef __ANDROID__
@@ -86,6 +98,28 @@
}
#endif
+// generate an ID for this Bitmap, id is a 64-bit integer of 3 parts:
+// 0000xxxxxx - the lower 6 decimal digits is a monotonically increasing number
+// 000x000000 - the 7th decimal digit is the storage type (see PixelStorageType)
+// xxx0000000 - the 8th decimal digit and above is the current pid
+//
+// e.g. 43231000076 - means this bitmap is the 76th bitmap created, has the
+// storage type of 'Heap', and is created in a process with pid 4323.
+//
+// NOTE:
+// 1) the monotonic number could increase beyond 1000,000 and wrap around, which
+// only happens when more than 1,000,000 bitmaps have been created over time.
+// This could result in two IDs being the same despite being really rare.
+// 2) the IDs are intentionally represented in decimal to make it easier to
+// reason and associate with numbers shown in heap dump (mostly in decimal)
+// and PIDs shown in different tools (mostly in decimal as well).
+uint64_t Bitmap::getId(PixelStorageType type) {
+ static std::atomic<uint64_t> idCounter{0};
+ return (idCounter.fetch_add(1) % 1000000)
+ + static_cast<uint64_t>(type) * 1000000
+ + static_cast<uint64_t>(getpid()) * 10000000;
+}
+
bool Bitmap::computeAllocationSize(size_t rowBytes, int height, size_t* size) {
return 0 <= height && height <= std::numeric_limits<size_t>::max() &&
!__builtin_mul_overflow(rowBytes, (size_t)height, size) &&
@@ -117,6 +151,20 @@
return wrapper;
}
+std::string Bitmap::getAshmemId(const char* tag, uint64_t bitmapId,
+ int width, int height, size_t size) {
+ if (!hwui_flags::bitmap_ashmem_long_name()) {
+ return "bitmap";
+ }
+ static std::string sCmdline = [] {
+ std::string temp;
+ android::base::ReadFileToString("/proc/self/cmdline", &temp);
+ return temp;
+ }();
+ return std::format("bitmap/{}-id_{}-{}x{}-size_{}-{}",
+ tag, bitmapId, width, height, size, sCmdline);
+}
+
sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(SkBitmap* bitmap) {
return allocateBitmap(bitmap, &Bitmap::allocateAshmemBitmap);
}
@@ -124,7 +172,9 @@
sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) {
#ifdef __ANDROID__
// Create new ashmem region with read/write priv
- int fd = ashmem_create_region("bitmap", size);
+ uint64_t id = getId(PixelStorageType::Ashmem);
+ auto ashmemId = getAshmemId("allocate", id, info.width(), info.height(), size);
+ int fd = ashmem_create_region(ashmemId.c_str(), size);
if (fd < 0) {
return nullptr;
}
@@ -140,7 +190,7 @@
close(fd);
return nullptr;
}
- return sk_sp<Bitmap>(new Bitmap(addr, fd, size, info, rowBytes));
+ return sk_sp<Bitmap>(new Bitmap(addr, fd, size, info, rowBytes, id));
#else
return Bitmap::allocateHeapBitmap(size, info, rowBytes);
#endif
@@ -261,7 +311,8 @@
Bitmap::Bitmap(void* address, size_t size, const SkImageInfo& info, size_t rowBytes)
: SkPixelRef(info.width(), info.height(), address, rowBytes)
, mInfo(validateAlpha(info))
- , mPixelStorageType(PixelStorageType::Heap) {
+ , mPixelStorageType(PixelStorageType::Heap)
+ , mId(getId(mPixelStorageType)) {
mPixelStorage.heap.address = address;
mPixelStorage.heap.size = size;
traceBitmapCreate();
@@ -270,16 +321,19 @@
Bitmap::Bitmap(SkPixelRef& pixelRef, const SkImageInfo& info)
: SkPixelRef(info.width(), info.height(), pixelRef.pixels(), pixelRef.rowBytes())
, mInfo(validateAlpha(info))
- , mPixelStorageType(PixelStorageType::WrappedPixelRef) {
+ , mPixelStorageType(PixelStorageType::WrappedPixelRef)
+ , mId(getId(mPixelStorageType)) {
pixelRef.ref();
mPixelStorage.wrapped.pixelRef = &pixelRef;
traceBitmapCreate();
}
-Bitmap::Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes)
+Bitmap::Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info,
+ size_t rowBytes, uint64_t id)
: SkPixelRef(info.width(), info.height(), address, rowBytes)
, mInfo(validateAlpha(info))
- , mPixelStorageType(PixelStorageType::Ashmem) {
+ , mPixelStorageType(PixelStorageType::Ashmem)
+ , mId(id != INVALID_BITMAP_ID ? id : getId(mPixelStorageType)) {
mPixelStorage.ashmem.address = address;
mPixelStorage.ashmem.fd = fd;
mPixelStorage.ashmem.size = mappedSize;
@@ -293,7 +347,8 @@
, mInfo(validateAlpha(info))
, mPixelStorageType(PixelStorageType::Hardware)
, mPalette(palette)
- , mPaletteGenerationId(getGenerationID()) {
+ , mPaletteGenerationId(getGenerationID())
+ , mId(getId(mPixelStorageType)) {
mPixelStorage.hardware.buffer = buffer;
mPixelStorage.hardware.size = AHardwareBuffer_getAllocationSize(buffer);
AHardwareBuffer_acquire(buffer);
@@ -578,6 +633,7 @@
}
std::mutex Bitmap::mLock{};
+
size_t Bitmap::mTotalBitmapBytes = 0;
size_t Bitmap::mTotalBitmapCount = 0;
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 3d55d85..8abe6a8 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -37,10 +37,10 @@
namespace android {
enum class PixelStorageType {
- WrappedPixelRef,
- Heap,
- Ashmem,
- Hardware,
+ WrappedPixelRef = 0,
+ Heap = 1,
+ Ashmem = 2,
+ Hardware = 3,
};
// TODO: Find a better home for this. It's here because hwui/Bitmap is exported and CanvasTransform
@@ -79,6 +79,9 @@
static sk_sp<Bitmap> allocateHeapBitmap(const SkImageInfo& info);
static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& i, size_t rowBytes);
+ static std::string getAshmemId(const char* tag, uint64_t bitmapId,
+ int width, int height, size_t size);
+
/* The createFrom factories construct a new Bitmap object by wrapping the already allocated
* memory that is provided as an input param.
*/
@@ -104,6 +107,10 @@
void setColorSpace(sk_sp<SkColorSpace> colorSpace);
void setAlphaType(SkAlphaType alphaType);
+ uint64_t getId() const {
+ return mId;
+ }
+
void getSkBitmap(SkBitmap* outBitmap);
SkBitmap getSkBitmap() {
@@ -177,11 +184,14 @@
static bool compress(const SkBitmap& bitmap, JavaCompressFormat format,
int32_t quality, SkWStream* stream);
private:
+ static constexpr uint64_t INVALID_BITMAP_ID = 0u;
+
static sk_sp<Bitmap> allocateAshmemBitmap(size_t size, const SkImageInfo& i, size_t rowBytes);
Bitmap(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes);
Bitmap(SkPixelRef& pixelRef, const SkImageInfo& info);
- Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes);
+ Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes,
+ uint64_t id = INVALID_BITMAP_ID);
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
Bitmap(AHardwareBuffer* buffer, const SkImageInfo& info, size_t rowBytes,
BitmapPalette palette);
@@ -229,6 +239,9 @@
sk_sp<SkImage> mImage; // Cache is used only for HW Bitmaps with Skia pipeline.
+ uint64_t mId; // unique ID for this bitmap
+ static uint64_t getId(PixelStorageType type);
+
// for tracing total number and memory usage of bitmaps
static std::mutex mLock;
static size_t mTotalBitmapBytes;
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 010c4e8..29efd98 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -196,7 +196,7 @@
int density) {
static jmethodID gBitmap_constructorMethodID =
GetMethodIDOrDie(env, gBitmap_class,
- "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
+ "<init>", "(JJIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
@@ -209,7 +209,8 @@
bitmapWrapper->bitmap().setImmutable();
}
jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
- reinterpret_cast<jlong>(bitmapWrapper), bitmap->width(), bitmap->height(), density,
+ static_cast<jlong>(bitmap->getId()), reinterpret_cast<jlong>(bitmapWrapper),
+ bitmap->width(), bitmap->height(), density,
isPremultiplied, ninePatchChunk, ninePatchInsets, fromMalloc);
if (env->ExceptionCheck() != 0) {
@@ -668,14 +669,20 @@
return STATUS_OK;
}
-static binder_status_t writeBlob(AParcel* parcel, const int32_t size, const void* data, bool immutable) {
+static binder_status_t writeBlob(AParcel* parcel, uint64_t bitmapId, const SkBitmap& bitmap) {
+ const size_t size = bitmap.computeByteSize();
+ const void* data = bitmap.getPixels();
+ const bool immutable = bitmap.isImmutable();
+
if (size <= 0 || data == nullptr) {
return STATUS_NOT_ENOUGH_DATA;
}
binder_status_t error = STATUS_OK;
if (shouldUseAshmem(parcel, size)) {
// Create new ashmem region with read/write priv
- base::unique_fd fd(ashmem_create_region("bitmap", size));
+ auto ashmemId = Bitmap::getAshmemId("writeblob", bitmapId,
+ bitmap.width(), bitmap.height(), size);
+ base::unique_fd fd(ashmem_create_region(ashmemId.c_str(), size));
if (fd.get() < 0) {
return STATUS_NO_MEMORY;
}
@@ -883,8 +890,7 @@
p.allowFds() ? "allowed" : "forbidden");
#endif
- size_t size = bitmap.computeByteSize();
- status = writeBlob(p.get(), size, bitmap.getPixels(), bitmap.isImmutable());
+ status = writeBlob(p.get(), bitmapWrapper->bitmap().getId(), bitmap);
if (status) {
doThrowRE(env, "Could not copy bitmap to parcel blob.");
return JNI_FALSE;
diff --git a/libs/hwui/platform/host/android/api-level.h b/libs/hwui/platform/host/android/api-level.h
deleted file mode 120000
index 4fb4784..0000000
--- a/libs/hwui/platform/host/android/api-level.h
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../../bionic/libc/include/android/api-level.h
\ No newline at end of file
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 1024a55..5a8eb3a 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -16,11 +16,15 @@
package android.media;
+import static android.media.audio.Flags.FLAG_SPEAKER_CLEANUP_USAGE;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+// TODO switch from HIDL imports to AIDL
import android.audio.policy.configuration.V7_0.AudioUsage;
import android.compat.annotation.UnsupportedAppUsage;
import android.media.audiopolicy.AudioProductStrategy;
@@ -247,6 +251,16 @@
public static final int USAGE_ANNOUNCEMENT = SYSTEM_USAGE_OFFSET + 3;
/**
+ * @hide
+ * Usage value to use when a system application plays a signal intended to clean up the
+ * speaker transducers and free them of deposits of dust or water.
+ */
+ @FlaggedApi(FLAG_SPEAKER_CLEANUP_USAGE)
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int USAGE_SPEAKER_CLEANUP = SYSTEM_USAGE_OFFSET + 4;
+
+ /**
* IMPORTANT: when adding new usage types, add them to SDK_USAGES and update SUPPRESSIBLE_USAGES
* if applicable, as well as audioattributes.proto.
* Also consider adding them to <aaudio/AAudio.h> for the NDK.
@@ -1522,6 +1536,8 @@
return "USAGE_VEHICLE_STATUS";
case USAGE_ANNOUNCEMENT:
return "USAGE_ANNOUNCEMENT";
+ case USAGE_SPEAKER_CLEANUP:
+ return "USAGE_SPEAKER_CLEANUP";
default:
return "unknown usage " + usage;
}
@@ -1662,12 +1678,8 @@
}
/**
- * @param usage one of {@link AttributeSystemUsage},
- * {@link AttributeSystemUsage#USAGE_CALL_ASSISTANT},
- * {@link AttributeSystemUsage#USAGE_EMERGENCY},
- * {@link AttributeSystemUsage#USAGE_SAFETY},
- * {@link AttributeSystemUsage#USAGE_VEHICLE_STATUS},
- * {@link AttributeSystemUsage#USAGE_ANNOUNCEMENT}
+ * Returns whether the given usage can only be used by system-privileged components
+ * @param usage one of {@link AttributeSystemUsage}.
* @return boolean indicating if the usage is a system usage or not
* @hide
*/
@@ -1677,7 +1689,8 @@
|| usage == USAGE_EMERGENCY
|| usage == USAGE_SAFETY
|| usage == USAGE_VEHICLE_STATUS
- || usage == USAGE_ANNOUNCEMENT);
+ || usage == USAGE_ANNOUNCEMENT
+ || usage == USAGE_SPEAKER_CLEANUP);
}
/**
@@ -1792,6 +1805,7 @@
case USAGE_SAFETY:
case USAGE_VEHICLE_STATUS:
case USAGE_ANNOUNCEMENT:
+ case USAGE_SPEAKER_CLEANUP:
case USAGE_UNKNOWN:
return AudioSystem.STREAM_MUSIC;
default:
@@ -1831,7 +1845,8 @@
USAGE_EMERGENCY,
USAGE_SAFETY,
USAGE_VEHICLE_STATUS,
- USAGE_ANNOUNCEMENT
+ USAGE_ANNOUNCEMENT,
+ USAGE_SPEAKER_CLEANUP
})
@Retention(RetentionPolicy.SOURCE)
public @interface AttributeSystemUsage {}
@@ -1881,6 +1896,7 @@
USAGE_SAFETY,
USAGE_VEHICLE_STATUS,
USAGE_ANNOUNCEMENT,
+ USAGE_SPEAKER_CLEANUP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AttributeUsage {}
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index b022ea1..88efed5 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -16,12 +16,16 @@
package android.media;
+import static android.media.tv.flags.Flags.FLAG_SET_RESOURCE_HOLDER_RETAIN;
+
import static com.android.media.flags.Flags.FLAG_UPDATE_CLIENT_PROFILE_PRIORITY;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Context;
import android.hardware.cas.AidlCasPluginDescriptor;
@@ -511,18 +515,20 @@
private final TunerResourceManager.ResourcesReclaimListener mResourceListener =
new TunerResourceManager.ResourcesReclaimListener() {
- @Override
- public void onReclaimResources() {
- synchronized (mSessionMap) {
- List<Session> sessionList = new ArrayList<>(mSessionMap.keySet());
- for (Session casSession: sessionList) {
- casSession.close();
+ @Override
+ public void onReclaimResources() {
+ synchronized (mSessionMap) {
+ List<Session> sessionList = new ArrayList<>(mSessionMap.keySet());
+ for (Session casSession : sessionList) {
+ casSession.close();
+ }
+ }
+ if (mEventHandler != null) {
+ mEventHandler.sendMessage(
+ mEventHandler.obtainMessage(EventHandler.MSG_CAS_RESOURCE_LOST));
}
}
- mEventHandler.sendMessage(mEventHandler.obtainMessage(
- EventHandler.MSG_CAS_RESOURCE_LOST));
- }
- };
+ };
/**
* Describe a CAS plugin with its CA_system_ID and string name.
@@ -988,12 +994,32 @@
* @param priority the new priority. Any negative value would cause no-op on priority setting
* and the API would only process nice value setting in that case.
* @param niceValue the nice value.
+ * @hide
*/
@FlaggedApi(FLAG_UPDATE_CLIENT_PROFILE_PRIORITY)
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
public boolean updateResourcePriority(int priority, int niceValue) {
return mTunerResourceManager.updateClientPriority(mClientId, priority, niceValue);
}
+ /**
+ * Determines whether the resource holder retains ownership of the resource during a challenge
+ * scenario, when both resource holder and resource challenger have same processId and same
+ * priority.
+ *
+ * @param resourceHolderRetain Set to {@code true} to allow the resource holder to retain
+ * ownership, or false to allow the resource challenger to acquire the resource.
+ * If not explicitly set, resourceHolderRetain is set to {@code false}.
+ * @hide
+ */
+ @FlaggedApi(FLAG_SET_RESOURCE_HOLDER_RETAIN)
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+ public void setResourceHolderRetain(boolean resourceHolderRetain) {
+ mTunerResourceManager.setResourceHolderRetain(mClientId, resourceHolderRetain);
+ }
+
IHwBinder getBinder() {
if (mICas != null) {
return null; // Return IHwBinder only for HIDL
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index d49f7dd..4de6863 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -71,4 +71,12 @@
namespace: "media_tv"
description: "Standardize AIDL Extension Interface of TIS"
bug: "330366987"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "set_resource_holder_retain"
+ is_exported: true
+ namespace: "media_tv"
+ description : "Feature flag to add setResourceHolderRetain api to MediaCas and Tuner JAVA."
+ bug: "372973197"
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index cdf50ec..b1adb77 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -16,6 +16,8 @@
package android.media.tv.tuner;
+import static android.media.tv.flags.Flags.FLAG_SET_RESOURCE_HOLDER_RETAIN;
+
import android.annotation.BytesLong;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
@@ -751,6 +753,21 @@
}
/**
+ * Determines whether the resource holder retains ownership of the resource during a challenge
+ * scenario, when both resource holder and resource challenger have same processId and same
+ * priority.
+ *
+ * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or
+ * false to allow the resource challenger to acquire the resource. If not explicitly set,
+ * resourceHolderRetain is set to false.
+ */
+ @FlaggedApi(FLAG_SET_RESOURCE_HOLDER_RETAIN)
+ @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+ public void setResourceHolderRetain(boolean resourceHolderRetain) {
+ mTunerResourceManager.setResourceHolderRetain(mClientId, resourceHolderRetain);
+ }
+
+ /**
* Checks if there is an unused frontend resource available.
*
* @param frontendType {@link android.media.tv.tuner.frontend.FrontendSettings.Type} for the
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index bb581eb..be65ad9 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -40,8 +40,11 @@
* <p>Resources include:
* <ul>
* <li>TunerFrontend {@link android.media.tv.tuner.frontend}.
+ * <li>Demux {@link com.android.server.tv.tunerresourcemanager.DemuxResource}.
+ * <li>Descrambler {@link android.media.tv.tuner.Descrambler}.
* <li>TunerLnb {@link android.media.tv.tuner.Lnb}.
* <li>MediaCas {@link android.media.MediaCas}.
+ * <li>CiCam {@link com.android.server.tv.tunerresourcemanager.CiCamResource}.
* <ul>
*
* <p>Expected workflow is:
@@ -78,7 +81,7 @@
TUNER_RESOURCE_TYPE_LNB,
TUNER_RESOURCE_TYPE_CAS_SESSION,
TUNER_RESOURCE_TYPE_FRONTEND_CICAM,
- TUNER_RESOURCE_TYPE_MAX,
+ TUNER_RESOURCE_TYPE_MAX, // upper bound of constants
})
@Retention(RetentionPolicy.SOURCE)
public @interface TunerResourceType {}
@@ -220,6 +223,25 @@
}
/**
+ * Determines whether the resource holder retains ownership of the resource during a challenge
+ * scenario, when both resource holder and resource challenger have same processId and same
+ * priority.
+ *
+ * @param clientId The client id used to set ownership of resource to owner in case of resource
+ * challenger situation.
+ * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or
+ * false to allow the resource challenger to acquire the resource. If not explicitly set,
+ * resourceHolderRetain is set to false.
+ */
+ public void setResourceHolderRetain(int clientId, boolean resourceHolderRetain) {
+ try {
+ mService.setResourceHolderRetain(clientId, resourceHolderRetain);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Stores the frontend resource map if it was stored before.
*
* <p>This API is only for testing purpose and should be used in pair with
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index 109c791..c57be1b0 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -151,6 +151,18 @@
*/
void setLnbInfoList(in long[] lnbIds);
+ /**
+ * Determines whether the Resource Holder retains ownership of the resource during a challenge
+ * scenario, when both Resource Holder and Resource Challenger have same processId and same
+ * priority.
+ *
+ * @param clientId The resourceHolderRetain of the client is updated using client ID.
+ * @param resourceHolderRetain set to true to allow the Resource Holder to retain ownership, or
+ * false to allow the Resource Challenger to acquire the resource. If not explicitly set,
+ * resourceHolderRetain is set to false.
+ */
+ void setResourceHolderRetain(int clientId, boolean resourceHolderRetain);
+
/*
* This API is used by the Tuner framework to request a frontend from the TunerHAL.
*
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 202535d..b025cb8 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -283,6 +283,7 @@
ASurfaceTransaction_setEnableBackPressure; # introduced=31
ASurfaceTransaction_setFrameRate; # introduced=30
ASurfaceTransaction_setFrameRateWithChangeStrategy; # introduced=31
+ ASurfaceTransaction_setFrameRateParams; # introduced=36
ASurfaceTransaction_clearFrameRate; # introduced=34
ASurfaceTransaction_setFrameTimeline; # introduced=Tiramisu
ASurfaceTransaction_setGeometry; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index e46db6b..698bc84 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -731,6 +731,28 @@
transaction->setFrameRate(surfaceControl, frameRate, compatibility, changeFrameRateStrategy);
}
+void ASurfaceTransaction_setFrameRateParams(
+ ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+ float desiredMinRate, float desiredMaxRate, float fixedSourceRate,
+ ANativeWindow_ChangeFrameRateStrategy changeFrameRateStrategy) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+
+ if (desiredMaxRate < desiredMinRate) {
+ ALOGW("desiredMaxRate must be greater than or equal to desiredMinRate");
+ return;
+ }
+ // TODO(b/362798998): Fix plumbing to send modern params
+ int compatibility = fixedSourceRate == 0 ? ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT
+ : ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+ double frameRate = compatibility == ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
+ ? fixedSourceRate
+ : desiredMinRate;
+ transaction->setFrameRate(surfaceControl, frameRate, compatibility, changeFrameRateStrategy);
+}
+
void ASurfaceTransaction_clearFrameRate(ASurfaceTransaction* aSurfaceTransaction,
ASurfaceControl* aSurfaceControl) {
CHECK_NOT_NULL(aSurfaceTransaction);
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 744e97e..1998d0c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -1,7 +1,6 @@
package com.android.settingslib;
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
-import static android.webkit.Flags.updateServiceV2;
import android.annotation.ColorInt;
import android.app.admin.DevicePolicyManager;
@@ -496,7 +495,7 @@
|| packageName.equals(sServicesSystemSharedLibPackageName)
|| packageName.equals(sSharedSystemSharedLibPackageName)
|| packageName.equals(PrintManager.PRINT_SPOOLER_PACKAGE_NAME)
- || (updateServiceV2() && packageName.equals(getDefaultWebViewPackageName(pm)))
+ || packageName.equals(getDefaultWebViewPackageName(pm))
|| isDeviceProvisioningPackage(resources, packageName);
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 509b88b..558ccaf 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -263,5 +263,6 @@
VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ALL, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, ANY_STRING_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 1659c9e..c2beaa8 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -944,7 +944,8 @@
Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE,
Settings.System.WEAR_TTS_PREWARM_ENABLED,
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
- Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific
+ Settings.System.MULTI_AUDIO_FOCUS_ENABLED, // form-factor/OEM specific
+ Settings.System.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME // internal cache
);
if (!Flags.backUpSmoothDisplayAndForcePeakRefreshRate()) {
settings.add(Settings.System.MIN_REFRESH_RATE);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 8ddd922..a18b6c1 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -97,6 +97,7 @@
"tests/src/**/systemui/media/dialog/MediaOutputBroadcastDialogTest.java",
"tests/src/**/systemui/media/dialog/MediaOutputDialogTest.java",
"tests/src/**/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt",
+ "tests/src/**/systemui/settings/brightness/BrightnessDialogTest.kt",
],
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4531b79..0537b14 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -30,6 +30,7 @@
<uses-permission android:name="android.permission.READ_WALLPAPER_INTERNAL" />
<!-- Used to read storage for all users -->
+ <uses-permission android:name="android.permission.STORAGE_INTERNAL" />
<uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
@@ -1137,5 +1138,11 @@
android:name="android.service.dream"
android:resource="@xml/home_controls_dream_metadata" />
</service>
+
+ <service android:name="com.android.systemui.dreams.homecontrols.system.HomeControlsRemoteService"
+ android:singleUser="true"
+ android:exported="false"
+ />
+
</application>
</manifest>
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index d124c02..a56c2fe 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -59,7 +59,7 @@
flag {
name: "notification_over_expansion_clipping_fix"
namespace: "systemui"
- description: "fix NSSL clipping when over-expanding; fixes split shade bug."
+ description: "Fix NSSL clipping when over-expanding; fixes split shade bug."
bug: "288553572"
metadata {
purpose: PURPOSE_BUGFIX
@@ -67,6 +67,14 @@
}
flag {
+ name: "notification_add_x_on_hover_to_dismiss"
+ namespace: "systemui"
+ description: "Adds an x to notifications which shows up on mouse hover, allowing the user to "
+ "dismiss a notification with mouse."
+ bug: "376297472"
+}
+
+flag {
name: "notification_async_group_header_inflation"
namespace: "systemui"
description: "Inflates the notification group summary header views from the background thread."
@@ -740,10 +748,13 @@
}
flag {
- name: "smartspace_swipe_event_logging"
+ name: "smartspace_swipe_event_logging_fix"
namespace: "systemui"
description: "Log card swipe events in smartspace"
bug: "374150422"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -908,16 +919,6 @@
}
flag {
- name: "delayed_wakelock_release_on_background_thread"
- namespace: "systemui"
- description: "Released delayed wakelocks on background threads to avoid janking screen transitions."
- bug: "316128516"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "notify_power_manager_user_activity_background"
namespace: "systemui"
description: "Decide whether to notify the user activity to power manager in the background thread."
@@ -1625,6 +1626,16 @@
}
flag {
+ name: "home_controls_dream_hsum"
+ namespace: "systemui"
+ description: "Enables the home controls dream in HSUM"
+ bug: "370691405"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "only_show_media_stream_slider_in_single_volume_mode"
namespace: "systemui"
description: "When the device is in single volume mode, only show media stream slider and hide all other stream (e.g. call, notification, alarm, etc) sliders in volume panel"
@@ -1652,6 +1663,13 @@
}
flag {
+ name: "bouncer_ui_revamp"
+ namespace: "systemui"
+ description: "Updates to background (blur), button animations and font changes."
+ bug: "376491880"
+}
+
+flag {
name: "ensure_enr_views_visibility"
namespace: "systemui"
description: "Ensures public and private visibilities"
@@ -1667,3 +1685,12 @@
description: "Expands the shade on long press of any status bar"
bug: "371224114"
}
+
+
+flag {
+ name: "keyboard_shortcut_helper_shortcut_customizer"
+ namespace: "systemui"
+ description: "An implementation of shortcut customizations through shortcut helper."
+ bug: "365064144"
+}
+
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
index 6e83124..82d1436 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
@@ -32,11 +32,7 @@
* Note: You can use [Color.Unspecified] to disable the tint and keep the original icon colors.
*/
@Composable
-fun Icon(
- icon: Icon,
- modifier: Modifier = Modifier,
- tint: Color = LocalContentColor.current,
-) {
+fun Icon(icon: Icon, modifier: Modifier = Modifier, tint: Color = LocalContentColor.current) {
val contentDescription = icon.contentDescription?.load()
when (icon) {
is Icon.Loaded -> {
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
index f9e2252..0d8a470 100644
--- 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
@@ -21,12 +21,10 @@
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
@@ -53,17 +51,13 @@
@SuppressLint("InflateParams")
val view =
remember(context) {
- (LayoutInflater.from(context)
- .inflate(
- R.layout.keyguard_status_bar,
- null,
- false,
- ) as KeyguardStatusBarView)
+ (LayoutInflater.from(context).inflate(R.layout.keyguard_status_bar, null, false)
+ as KeyguardStatusBarView)
.also {
it.layoutParams =
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT
+ ViewGroup.LayoutParams.WRAP_CONTENT,
)
}
}
@@ -92,10 +86,8 @@
view
},
modifier =
- Modifier.fillMaxWidth().padding(horizontal = 16.dp).height {
- Utils.getStatusBarHeaderHeightKeyguard(context)
- },
- update = { viewController.setDisplayCutout(viewDisplayCutout) }
+ modifier.fillMaxWidth().height { Utils.getStatusBarHeaderHeightKeyguard(context) },
+ update = { viewController.setDisplayCutout(viewDisplayCutout) },
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 6f1349f..9d4408a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -45,11 +45,13 @@
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.SceneScope
import com.android.compose.windowsizeclass.LocalWindowSizeClass
+import com.android.systemui.res.R
/** Renders a lightweight shade UI container, as an overlay. */
@Composable
@@ -104,13 +106,11 @@
@Composable
private fun Modifier.panelSize(): Modifier {
val widthSizeClass = LocalWindowSizeClass.current.widthSizeClass
-
return this.then(
- when (widthSizeClass) {
- WindowWidthSizeClass.Compact -> Modifier.fillMaxWidth()
- WindowWidthSizeClass.Medium -> Modifier.width(OverlayShade.Dimensions.PanelWidthMedium)
- WindowWidthSizeClass.Expanded -> Modifier.width(OverlayShade.Dimensions.PanelWidthLarge)
- else -> error("Unsupported WindowWidthSizeClass \"$widthSizeClass\"")
+ if (widthSizeClass == WindowWidthSizeClass.Compact) {
+ Modifier.fillMaxWidth()
+ } else {
+ Modifier.width(dimensionResource(id = R.dimen.shade_panel_width))
}
)
}
@@ -176,8 +176,6 @@
object Dimensions {
val ScrimContentPadding = 16.dp
val PanelCornerRadius = 46.dp
- val PanelWidthMedium = 390.dp
- val PanelWidthLarge = 474.dp
val OverscrollLimit = 32.dp
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
index a883977..4ed8fd8 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
@@ -27,7 +27,7 @@
import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceEvents
-import com.android.systemui.plugins.clocks.ClockReactiveSetting
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
@@ -103,7 +103,9 @@
view.onZenDataChanged(data)
}
- override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {}
+ override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) {
+ view.updateAxes(axes)
+ }
override var isReactiveTouchInteractionEnabled
get() = view.isReactiveTouchInteractionEnabled
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index c5b7518..7014826 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -33,8 +33,8 @@
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.ClockMessageBuffers
-import com.android.systemui.plugins.clocks.ClockReactiveSetting
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.plugins.clocks.DefaultClockFaceLayout
import com.android.systemui.plugins.clocks.ThemeConfig
@@ -264,7 +264,7 @@
override fun onZenDataChanged(data: ZenData) {}
- override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {}
+ override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) {}
}
open inner class DefaultClockAnimations(
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index a89e6fb..d70d61c 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -21,12 +21,12 @@
import com.android.systemui.log.core.LogcatOnlyMessageBuffer
import com.android.systemui.plugins.clocks.AxisType
import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockFontAxis
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProvider
-import com.android.systemui.plugins.clocks.ClockReactiveAxis
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.shared.clocks.view.HorizontalAlignment
import com.android.systemui.shared.clocks.view.VerticalAlignment
@@ -91,15 +91,42 @@
axes =
if (isClockReactiveVariantsEnabled)
listOf(
- ClockReactiveAxis(
- key = "wdth",
- type = AxisType.Slider,
- maxValue = 1000f,
- minValue = 100f,
+ ClockFontAxis(
+ key = "wght",
+ type = AxisType.Float,
+ minValue = 1f,
currentValue = 400f,
+ maxValue = 1000f,
+ name = "Weight",
+ description = "Glyph Weight",
+ ),
+ ClockFontAxis(
+ key = "wdth",
+ type = AxisType.Float,
+ minValue = 25f,
+ currentValue = 100f,
+ maxValue = 151f,
name = "Width",
description = "Glyph Width",
- )
+ ),
+ ClockFontAxis(
+ key = "ROND",
+ type = AxisType.Boolean,
+ minValue = 0f,
+ currentValue = 0f,
+ maxValue = 100f,
+ name = "Round",
+ description = "Glyph Roundness",
+ ),
+ ClockFontAxis(
+ key = "slnt",
+ type = AxisType.Boolean,
+ minValue = 0f,
+ currentValue = 0f,
+ maxValue = -10f,
+ name = "Slant",
+ description = "Glyph Slant",
+ ),
)
else listOf(),
)
@@ -121,12 +148,12 @@
FontTextStyle(
lineHeight = 147.25f,
fontVariation =
- "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
+ "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
),
aodStyle =
FontTextStyle(
fontVariation =
- "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
+ "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
fillColorLight = "#FFFFFFFF",
outlineColor = "#00000000",
renderType = RenderType.CHANGE_WEIGHT,
@@ -147,12 +174,12 @@
FontTextStyle(
lineHeight = 147.25f,
fontVariation =
- "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
+ "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
),
aodStyle =
FontTextStyle(
fontVariation =
- "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
+ "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
fillColorLight = "#FFFFFFFF",
outlineColor = "#00000000",
renderType = RenderType.CHANGE_WEIGHT,
@@ -173,12 +200,12 @@
FontTextStyle(
lineHeight = 147.25f,
fontVariation =
- "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
+ "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
),
aodStyle =
FontTextStyle(
fontVariation =
- "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
+ "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
fillColorLight = "#FFFFFFFF",
outlineColor = "#00000000",
renderType = RenderType.CHANGE_WEIGHT,
@@ -199,12 +226,12 @@
FontTextStyle(
lineHeight = 147.25f,
fontVariation =
- "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
+ "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
),
aodStyle =
FontTextStyle(
fontVariation =
- "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
+ "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
fillColorLight = "#FFFFFFFF",
outlineColor = "#00000000",
renderType = RenderType.CHANGE_WEIGHT,
@@ -229,12 +256,14 @@
timespec = DigitalTimespec.TIME_FULL_FORMAT,
style =
FontTextStyle(
- fontVariation = "'wght' 600, 'wdth' 100, 'opsz' 144, 'ROND' 100",
+ fontVariation =
+ "'wght' 600, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
fontSizeScale = 0.98f,
),
aodStyle =
FontTextStyle(
- fontVariation = "'wght' 133, 'wdth' 43, 'opsz' 144, 'ROND' 100",
+ fontVariation =
+ "'wght' 133, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
fillColorLight = "#FFFFFFFF",
outlineColor = "#00000000",
renderType = RenderType.CHANGE_WEIGHT,
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
index a75022a..d8fd776 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
@@ -23,8 +23,8 @@
import com.android.systemui.plugins.clocks.ClockConfig
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockEvents
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.ClockMessageBuffers
-import com.android.systemui.plugins.clocks.ClockReactiveSetting
import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
@@ -113,9 +113,9 @@
largeClock.events.onZenDataChanged(data)
}
- override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {
- smallClock.events.onReactiveAxesChanged(axes)
- largeClock.events.onReactiveAxesChanged(axes)
+ override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) {
+ smallClock.events.onFontAxesChanged(axes)
+ largeClock.events.onFontAxesChanged(axes)
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
index 8ffc00d..a4782ac 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -32,7 +32,7 @@
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.plugins.clocks.ClockFaceEvents
import com.android.systemui.plugins.clocks.ClockFaceLayout
-import com.android.systemui.plugins.clocks.ClockReactiveSetting
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.DefaultClockFaceLayout
import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.WeatherData
@@ -136,13 +136,16 @@
override fun onFontSettingChanged(fontSizePx: Float) {
layerController.faceEvents.onFontSettingChanged(fontSizePx)
+ view.requestLayout()
}
override fun onThemeChanged(theme: ThemeConfig) {
layerController.faceEvents.onThemeChanged(theme)
}
- override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {}
+ override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) {
+ layerController.events.onFontAxesChanged(axes)
+ }
/**
* targetRegion passed to all customized clock applies counter translationY of
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
index 7b1960e..143b28f 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
@@ -31,7 +31,7 @@
import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceEvents
-import com.android.systemui.plugins.clocks.ClockReactiveSetting
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
@@ -246,7 +246,9 @@
override fun onZenDataChanged(data: ZenData) {}
- override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {}
+ override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) {
+ view.updateAxes(axes)
+ }
}
override val animations =
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt
index ce4d71c..b09332f 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/DigitalClockFaceView.kt
@@ -25,6 +25,7 @@
import com.android.systemui.log.core.Logger
import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.clocks.AlarmData
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
import com.android.systemui.shared.clocks.LogUtil
@@ -126,6 +127,11 @@
invalidate()
}
+ fun updateAxes(axes: List<ClockFontAxisSetting>) {
+ digitalClockTextViewMap.forEach { _, view -> view.updateAxes(axes) }
+ requestLayout()
+ }
+
fun onFontSettingChanged(fontSizePx: Float) {
digitalClockTextViewMap.forEach { _, view -> view.applyTextSize(fontSizePx) }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index 25b2ad7..edcee9d 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -39,8 +39,7 @@
DigitalClockFaceView(context, messageBuffer) {
override var digitalClockTextViewMap = mutableMapOf<Int, SimpleDigitalClockTextView>()
val digitLeftTopMap = mutableMapOf<Int, Point>()
- var maxSingleDigitHeight = -1
- var maxSingleDigitWidth = -1
+ var maxSingleDigitSize = Point(-1, -1)
val lockscreenTranslate = Point(0, 0)
val aodTranslate = Point(0, 0)
@@ -119,25 +118,26 @@
}
protected override fun calculateSize(widthMeasureSpec: Int, heightMeasureSpec: Int): Point {
+ maxSingleDigitSize = Point(-1, -1)
digitalClockTextViewMap.forEach { (_, textView) ->
textView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+ maxSingleDigitSize.x = max(maxSingleDigitSize.x, textView.measuredWidth)
+ maxSingleDigitSize.y = max(maxSingleDigitSize.y, textView.measuredHeight)
}
val textView = digitalClockTextViewMap[R.id.HOUR_FIRST_DIGIT]!!
- maxSingleDigitHeight = textView.measuredHeight
- maxSingleDigitWidth = textView.measuredWidth
- aodTranslate.x = -(maxSingleDigitWidth * AOD_HORIZONTAL_TRANSLATE_RATIO).toInt()
- aodTranslate.y = (maxSingleDigitHeight * AOD_VERTICAL_TRANSLATE_RATIO).toInt()
+ aodTranslate.x = -(maxSingleDigitSize.x * AOD_HORIZONTAL_TRANSLATE_RATIO).toInt()
+ aodTranslate.y = (maxSingleDigitSize.y * AOD_VERTICAL_TRANSLATE_RATIO).toInt()
return Point(
- ((maxSingleDigitWidth + abs(aodTranslate.x)) * 2),
- ((maxSingleDigitHeight + abs(aodTranslate.y)) * 2),
+ ((maxSingleDigitSize.x + abs(aodTranslate.x)) * 2),
+ ((maxSingleDigitSize.y + abs(aodTranslate.y)) * 2),
)
}
protected override fun calculateLeftTopPosition() {
digitLeftTopMap[R.id.HOUR_FIRST_DIGIT] = Point(0, 0)
- digitLeftTopMap[R.id.HOUR_SECOND_DIGIT] = Point(maxSingleDigitWidth, 0)
- digitLeftTopMap[R.id.MINUTE_FIRST_DIGIT] = Point(0, maxSingleDigitHeight)
- digitLeftTopMap[R.id.MINUTE_SECOND_DIGIT] = Point(maxSingleDigitWidth, maxSingleDigitHeight)
+ digitLeftTopMap[R.id.HOUR_SECOND_DIGIT] = Point(maxSingleDigitSize.x, 0)
+ digitLeftTopMap[R.id.MINUTE_FIRST_DIGIT] = Point(0, maxSingleDigitSize.y)
+ digitLeftTopMap[R.id.MINUTE_SECOND_DIGIT] = Point(maxSingleDigitSize)
digitLeftTopMap.forEach { _, point ->
point.x += abs(aodTranslate.x)
point.y += abs(aodTranslate.y)
@@ -162,7 +162,7 @@
override fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
dozeControlState.animateDoze = {
super.animateDoze(isDozing, isAnimated)
- if (maxSingleDigitHeight == -1) {
+ if (maxSingleDigitSize.x < 0 || maxSingleDigitSize.y < 0) {
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
}
digitalClockTextViewMap.forEach { (id, textView) ->
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index baed3ae..0dacce1 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -41,6 +41,7 @@
import com.android.systemui.customization.R
import com.android.systemui.log.core.Logger
import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.shared.clocks.AssetLoader
import com.android.systemui.shared.clocks.ClockAnimation
import com.android.systemui.shared.clocks.DigitTranslateAnimator
@@ -140,6 +141,25 @@
invalidate()
}
+ override fun updateAxes(axes: List<ClockFontAxisSetting>) {
+ val sb = StringBuilder()
+ sb.append("'opsz' 144")
+
+ for (axis in axes) {
+ if (sb.length > 0) sb.append(", ")
+ sb.append("'${axis.key}' ${axis.value.toInt()}")
+ }
+
+ val fvar = sb.toString()
+ lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
+ typeface = lockScreenPaint.typeface
+ textAnimator.setTextStyle(fvar = fvar, animate = true)
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+ recomputeMaxSingleDigitSizes()
+ requestLayout()
+ invalidate()
+ }
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
logger.d("onMeasure()")
if (isVertical) {
@@ -189,6 +209,7 @@
MeasureSpec.getMode(measuredHeight),
)
}
+
if (MeasureSpec.getMode(widthMeasureSpec) == EXACTLY) {
expectedWidth = widthMeasureSpec
} else {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt
index 30b54d8..3d2ed4a1 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockView.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shared.clocks.view
import androidx.annotation.VisibleForTesting
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.shared.clocks.AssetLoader
import com.android.systemui.shared.clocks.TextStyle
@@ -34,6 +35,8 @@
fun updateColor(color: Int)
+ fun updateAxes(axes: List<ClockFontAxisSetting>)
+
fun refreshTime()
fun animateCharge()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
index 4856f15..e142169 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
@@ -3,7 +3,9 @@
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyResourcesManager
import android.content.pm.UserInfo
+import android.hardware.biometrics.Flags
import android.os.UserManager
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
@@ -33,6 +35,7 @@
import org.mockito.junit.MockitoJUnit
private const val USER_ID = 22
+private const val OWNER_ID = 10
private const val OPERATION_ID = 100L
private const val MAX_ATTEMPTS = 5
@@ -67,7 +70,7 @@
lockPatternUtils,
userManager,
devicePolicyManager,
- systemClock
+ systemClock,
)
}
@@ -115,58 +118,87 @@
@Test fun pinCredentialWhenBadAndThrottled() = pinCredential(badCredential(timeout = 5_000))
- private fun pinCredential(result: VerifyCredentialResponse) = runTest {
- val usedAttempts = 1
- whenever(lockPatternUtils.getCurrentFailedPasswordAttempts(eq(USER_ID)))
- .thenReturn(usedAttempts)
- whenever(lockPatternUtils.verifyCredential(any(), eq(USER_ID), anyInt())).thenReturn(result)
- whenever(lockPatternUtils.verifyGatekeeperPasswordHandle(anyLong(), anyLong(), eq(USER_ID)))
- .thenReturn(result)
- whenever(lockPatternUtils.setLockoutAttemptDeadline(anyInt(), anyInt())).thenAnswer {
- systemClock.elapsedRealtime() + (it.arguments[1] as Int)
- }
+ @EnableFlags(Flags.FLAG_PRIVATE_SPACE_BP)
+ @Test
+ fun pinCredentialTiedProfileWhenGood() = pinCredential(goodCredential(), OWNER_ID)
- // wrap in an async block so the test can advance the clock if throttling credential
- // checks prevents the method from returning
- val statusList = mutableListOf<CredentialStatus>()
- interactor
- .verifyCredential(pinRequest(), LockscreenCredential.createPin("1234"))
- .toList(statusList)
+ @EnableFlags(Flags.FLAG_PRIVATE_SPACE_BP)
+ @Test
+ fun pinCredentialTiedProfileWhenBad() = pinCredential(badCredential(), OWNER_ID)
- val last = statusList.removeLastOrNull()
- if (result.isMatched) {
- assertThat(statusList).isEmpty()
- val successfulResult = last as? CredentialStatus.Success.Verified
- assertThat(successfulResult).isNotNull()
- assertThat(successfulResult!!.hat).isEqualTo(result.gatekeeperHAT)
+ @EnableFlags(Flags.FLAG_PRIVATE_SPACE_BP)
+ @Test
+ fun pinCredentialTiedProfileWhenBadAndThrottled() =
+ pinCredential(badCredential(timeout = 5_000), OWNER_ID)
- verify(lockPatternUtils).userPresent(eq(USER_ID))
- verify(lockPatternUtils)
- .removeGatekeeperPasswordHandle(eq(result.gatekeeperPasswordHandle))
- } else {
- val failedResult = last as? CredentialStatus.Fail.Error
- assertThat(failedResult).isNotNull()
- assertThat(failedResult!!.remainingAttempts)
- .isEqualTo(if (result.timeout > 0) null else MAX_ATTEMPTS - usedAttempts - 1)
- assertThat(failedResult.urgentMessage).isNull()
+ private fun pinCredential(result: VerifyCredentialResponse, credentialOwner: Int = USER_ID) =
+ runTest {
+ val usedAttempts = 1
+ whenever(lockPatternUtils.getCurrentFailedPasswordAttempts(eq(USER_ID)))
+ .thenReturn(usedAttempts)
+ whenever(lockPatternUtils.verifyCredential(any(), eq(USER_ID), anyInt()))
+ .thenReturn(result)
+ whenever(lockPatternUtils.verifyTiedProfileChallenge(any(), eq(USER_ID), anyInt()))
+ .thenReturn(result)
+ whenever(
+ lockPatternUtils.verifyGatekeeperPasswordHandle(
+ anyLong(),
+ anyLong(),
+ eq(USER_ID),
+ )
+ )
+ .thenReturn(result)
+ whenever(lockPatternUtils.setLockoutAttemptDeadline(anyInt(), anyInt())).thenAnswer {
+ systemClock.elapsedRealtime() + (it.arguments[1] as Int)
+ }
- if (result.timeout > 0) { // failed and throttled
- // messages are in the throttled errors, so the final Error.error is empty
- assertThat(failedResult.error).isEmpty()
- assertThat(statusList).isNotEmpty()
- assertThat(statusList.filterIsInstance(CredentialStatus.Fail.Throttled::class.java))
- .hasSize(statusList.size)
+ // wrap in an async block so the test can advance the clock if throttling credential
+ // checks prevents the method from returning
+ val statusList = mutableListOf<CredentialStatus>()
+ interactor
+ .verifyCredential(
+ pinRequest(credentialOwner),
+ LockscreenCredential.createPin("1234"),
+ )
+ .toList(statusList)
- verify(lockPatternUtils).setLockoutAttemptDeadline(eq(USER_ID), eq(result.timeout))
- } else { // failed
- assertThat(failedResult.error)
- .matches(Regex("(.*)try again(.*)", RegexOption.IGNORE_CASE).toPattern())
+ val last = statusList.removeLastOrNull()
+ if (result.isMatched) {
assertThat(statusList).isEmpty()
+ val successfulResult = last as? CredentialStatus.Success.Verified
+ assertThat(successfulResult).isNotNull()
+ assertThat(successfulResult!!.hat).isEqualTo(result.gatekeeperHAT)
- verify(lockPatternUtils).reportFailedPasswordAttempt(eq(USER_ID))
+ verify(lockPatternUtils).userPresent(eq(USER_ID))
+ verify(lockPatternUtils)
+ .removeGatekeeperPasswordHandle(eq(result.gatekeeperPasswordHandle))
+ } else {
+ val failedResult = last as? CredentialStatus.Fail.Error
+ assertThat(failedResult).isNotNull()
+ assertThat(failedResult!!.remainingAttempts)
+ .isEqualTo(if (result.timeout > 0) null else MAX_ATTEMPTS - usedAttempts - 1)
+ assertThat(failedResult.urgentMessage).isNull()
+
+ if (result.timeout > 0) { // failed and throttled
+ // messages are in the throttled errors, so the final Error.error is empty
+ assertThat(failedResult.error).isEmpty()
+ assertThat(statusList).isNotEmpty()
+ assertThat(
+ statusList.filterIsInstance(CredentialStatus.Fail.Throttled::class.java)
+ )
+ .hasSize(statusList.size)
+
+ verify(lockPatternUtils)
+ .setLockoutAttemptDeadline(eq(USER_ID), eq(result.timeout))
+ } else { // failed
+ assertThat(failedResult.error)
+ .matches(Regex("(.*)try again(.*)", RegexOption.IGNORE_CASE).toPattern())
+ assertThat(statusList).isEmpty()
+
+ verify(lockPatternUtils).reportFailedPasswordAttempt(eq(USER_ID))
+ }
}
}
- }
@Test
fun pinCredentialWhenBadAndFinalAttempt() = runTest {
@@ -212,11 +244,11 @@
}
}
-private fun pinRequest(): BiometricPromptRequest.Credential.Pin =
+private fun pinRequest(credentialOwner: Int = USER_ID): BiometricPromptRequest.Credential.Pin =
BiometricPromptRequest.Credential.Pin(
promptInfo(),
- BiometricUserInfo(USER_ID),
- BiometricOperationInfo(OPERATION_ID)
+ BiometricUserInfo(userId = USER_ID, deviceCredentialOwnerId = credentialOwner),
+ BiometricOperationInfo(OPERATION_ID),
)
private fun goodCredential(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index dc499cd..b39a888 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -77,6 +77,7 @@
private val fingerprintRepository = FakeFingerprintPropertyRepository()
private val promptRepository = FakePromptRepository()
private val fakeExecutor = FakeExecutor(FakeSystemClock())
+ private val credentialInteractor = FakeCredentialInteractor()
private lateinit var displayStateRepository: FakeDisplayStateRepository
private lateinit var displayRepository: FakeDisplayRepository
@@ -99,8 +100,9 @@
PromptSelectorInteractorImpl(
fingerprintRepository,
displayStateInteractor,
+ credentialInteractor,
promptRepository,
- lockPatternUtils
+ lockPatternUtils,
)
}
@@ -134,13 +136,13 @@
testScope.runTest {
useBiometricsAndReset(
allowCredentialFallback = true,
- setComponentNameForConfirmDeviceCredentialActivity = true
+ setComponentNameForConfirmDeviceCredentialActivity = true,
)
}
private fun TestScope.useBiometricsAndReset(
allowCredentialFallback: Boolean,
- setComponentNameForConfirmDeviceCredentialActivity: Boolean = false
+ setComponentNameForConfirmDeviceCredentialActivity: Boolean = false,
) {
setUserCredentialType(isPassword = true)
@@ -357,7 +359,7 @@
private fun setPrompt(
info: PromptInfo = basicPromptInfo(),
- onSwitchToCredential: Boolean = false
+ onSwitchToCredential: Boolean = false,
) {
interactor.setPrompt(
info,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 55fd344..c803097 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -28,7 +28,6 @@
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.hardware.biometrics.BiometricFingerprintConstants
-import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
import android.hardware.biometrics.PromptContentItemBulletedText
import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
@@ -163,7 +162,7 @@
naturalDisplayWidth = 1000,
naturalDisplayHeight = 3000,
scaleFactor = 1f,
- rotation = if (isLandscape) Surface.ROTATION_90 else Surface.ROTATION_0
+ rotation = if (isLandscape) Surface.ROTATION_90 else Surface.ROTATION_0,
)
private lateinit var promptContentView: PromptContentView
@@ -180,21 +179,21 @@
whenever(
kosmos.packageManager.getApplicationInfo(
eq(OP_PACKAGE_NAME_WITH_APP_LOGO),
- anyInt()
+ anyInt(),
)
)
.thenReturn(applicationInfoWithIconAndDescription)
whenever(
kosmos.packageManager.getApplicationInfo(
eq(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO),
- anyInt()
+ anyInt(),
)
)
.thenReturn(applicationInfoWithIconAndDescription)
whenever(
kosmos.packageManager.getApplicationInfo(
eq(OP_PACKAGE_NAME_CAN_NOT_BE_FOUND),
- anyInt()
+ anyInt(),
)
)
.thenThrow(NameNotFoundException())
@@ -220,13 +219,13 @@
overrideResource(logoResFromApp, logoDrawableFromAppRes)
overrideResource(
R.array.config_useActivityLogoForBiometricPrompt,
- arrayOf(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO)
+ arrayOf(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO),
)
overrideResource(R.dimen.biometric_dialog_fingerprint_icon_width, mockFingerprintIconWidth)
overrideResource(
R.dimen.biometric_dialog_fingerprint_icon_height,
- mockFingerprintIconHeight
+ mockFingerprintIconHeight,
)
overrideResource(R.dimen.biometric_dialog_face_icon_size, mockFaceIconSize)
@@ -243,7 +242,7 @@
it.sensorType.toSensorType(),
it.allLocations.associateBy { sensorLocationInternal ->
sensorLocationInternal.displayId
- }
+ },
)
}
@@ -441,7 +440,7 @@
kosmos.promptViewModel.showAuthenticated(
modality = testCase.authenticatedModality,
- dismissAfterDelay = DELAY
+ dismissAfterDelay = DELAY,
)
// SFPS test cases
@@ -513,7 +512,7 @@
kosmos.promptViewModel.showAuthenticated(
modality = testCase.authenticatedModality,
dismissAfterDelay = DELAY,
- "TEST"
+ "TEST",
)
if (testCase.isFingerprintOnly) {
@@ -558,7 +557,7 @@
kosmos.promptViewModel.showAuthenticated(
modality = testCase.authenticatedModality,
- dismissAfterDelay = DELAY
+ dismissAfterDelay = DELAY,
)
if (testCase.isFaceOnly) {
@@ -598,7 +597,7 @@
kosmos.promptViewModel.showAuthenticated(
modality = testCase.authenticatedModality,
- dismissAfterDelay = DELAY
+ dismissAfterDelay = DELAY,
)
kosmos.promptViewModel.confirmAuthenticated()
@@ -701,7 +700,7 @@
.isEqualTo(
Pair(
expectedUdfpsOverlayParams.sensorBounds.width(),
- expectedUdfpsOverlayParams.sensorBounds.height()
+ expectedUdfpsOverlayParams.sensorBounds.height(),
)
)
} else {
@@ -834,7 +833,7 @@
kosmos.promptViewModel.showAuthenticated(
modality = testCase.authenticatedModality,
- dismissAfterDelay = DELAY
+ dismissAfterDelay = DELAY,
)
kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
@@ -907,10 +906,7 @@
}
)
- assertButtonsVisible(
- cancel = expectConfirmation,
- confirm = expectConfirmation,
- )
+ assertButtonsVisible(cancel = expectConfirmation, confirm = expectConfirmation)
}
@Test
@@ -1158,10 +1154,7 @@
testScheduler.runCurrent()
assertThat(messages)
- .containsExactly(
- PromptMessage.Empty,
- PromptMessage.Error(expectedErrorMessage),
- )
+ .containsExactly(PromptMessage.Empty, PromptMessage.Error(expectedErrorMessage))
.inOrder()
testScheduler.advanceUntilIdle()
@@ -1221,10 +1214,7 @@
assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
if (expectConfirmation) {
assertThat(size).isEqualTo(PromptSize.MEDIUM)
- assertButtonsVisible(
- cancel = true,
- confirm = true,
- )
+ assertButtonsVisible(cancel = true, confirm = true)
kosmos.promptViewModel.confirmAuthenticated()
assertThat(message).isEqualTo(PromptMessage.Empty)
@@ -1251,10 +1241,7 @@
assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
if (expectConfirmation) {
assertThat(size).isEqualTo(PromptSize.MEDIUM)
- assertButtonsVisible(
- cancel = true,
- confirm = true,
- )
+ assertButtonsVisible(cancel = true, confirm = true)
if (testCase.modalities.hasSfps) {
kosmos.promptViewModel.showAuthenticated(BiometricModality.Fingerprint, 0)
@@ -1290,10 +1277,7 @@
if (expectConfirmation) {
if (testCase.isFaceOnly) {
assertThat(size).isEqualTo(PromptSize.MEDIUM)
- assertButtonsVisible(
- cancel = true,
- confirm = true,
- )
+ assertButtonsVisible(cancel = true, confirm = true)
kosmos.promptViewModel.confirmAuthenticated()
} else if (testCase.isCoex) {
@@ -1323,10 +1307,7 @@
assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
if (expectConfirmation) {
assertThat(size).isEqualTo(PromptSize.MEDIUM)
- assertButtonsVisible(
- cancel = true,
- confirm = true,
- )
+ assertButtonsVisible(cancel = true, confirm = true)
kosmos.promptViewModel.confirmAuthenticated()
assertThat(message).isEqualTo(PromptMessage.Empty)
@@ -1398,10 +1379,7 @@
assertThat(authenticating).isFalse()
assertThat(authenticated?.isAuthenticated).isTrue()
assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
- assertButtonsVisible(
- cancel = expectConfirmation,
- confirm = expectConfirmation,
- )
+ assertButtonsVisible(cancel = expectConfirmation, confirm = expectConfirmation)
}
@Test
@@ -1421,7 +1399,7 @@
errorMessage,
messageAfterError = helpMessage,
authenticateAfterError = false,
- failedModality = testCase.authenticatedModality
+ failedModality = testCase.authenticatedModality,
)
}
@@ -1472,7 +1450,7 @@
kosmos.promptViewModel.onAnnounceAccessibilityHint(
obtainMotionEvent(MotionEvent.ACTION_HOVER_ENTER),
- true
+ true,
)
if (testCase.modalities.hasUdfps) {
@@ -1497,14 +1475,13 @@
kosmos.promptViewModel.onAnnounceAccessibilityHint(
obtainMotionEvent(MotionEvent.ACTION_HOVER_ENTER),
- true
+ true,
)
assertThat(hint.isNullOrBlank()).isTrue()
}
@Test
- @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
fun descriptionOverriddenByVerticalListContentView() =
runGenericTest(description = "test description", contentView = promptContentView) {
val contentView by collectLastValue(kosmos.promptViewModel.contentView)
@@ -1515,11 +1492,10 @@
}
@Test
- @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
fun descriptionOverriddenByContentViewWithMoreOptionsButton() =
runGenericTest(
description = "test description",
- contentView = promptContentViewWithMoreOptionsButton
+ contentView = promptContentViewWithMoreOptionsButton,
) {
val contentView by collectLastValue(kosmos.promptViewModel.contentView)
val description by collectLastValue(kosmos.promptViewModel.description)
@@ -1529,7 +1505,6 @@
}
@Test
- @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
fun descriptionWithoutContentView() =
runGenericTest(description = "test description") {
val contentView by collectLastValue(kosmos.promptViewModel.contentView)
@@ -1540,7 +1515,6 @@
}
@Test
- @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
fun logo_nullIfPkgNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1549,7 +1523,6 @@
}
@Test
- @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
fun logo_defaultFromActivityInfo() =
runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) {
val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1564,7 +1537,6 @@
}
@Test
- @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
fun logo_defaultIsNull() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1573,7 +1545,6 @@
}
@Test
- @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
fun logo_default() = runGenericTest {
val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
assertThat(logoInfo).isNotNull()
@@ -1581,7 +1552,6 @@
}
@Test
- @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
fun logo_resSetByApp() =
runGenericTest(logoRes = logoResFromApp) {
val expectedBitmap = context.getDrawable(logoResFromApp).toBitmap()
@@ -1591,7 +1561,6 @@
}
@Test
- @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
fun logo_bitmapSetByApp() =
runGenericTest(logoBitmap = logoBitmapFromApp) {
val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1599,7 +1568,6 @@
}
@Test
- @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
fun logoDescription_emptyIfPkgNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1607,7 +1575,6 @@
}
@Test
- @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
fun logoDescription_defaultFromActivityInfo() =
runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) {
val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1619,7 +1586,6 @@
}
@Test
- @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
fun logoDescription_defaultIsEmpty() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1627,14 +1593,12 @@
}
@Test
- @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
fun logoDescription_default() = runGenericTest {
val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromAppInfo)
}
@Test
- @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
fun logoDescription_setByApp() =
runGenericTest(logoDescription = logoDescriptionFromApp) {
val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
@@ -1826,7 +1790,7 @@
kosmos.biometricStatusRepository.setFingerprintAcquiredStatus(
AcquiredFingerprintAuthenticationStatus(
AuthenticationReason.BiometricPromptAuthentication,
- BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_UNKNOWN
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_UNKNOWN,
)
)
@@ -1893,7 +1857,7 @@
fingerprint =
fingerprintSensorPropertiesInternal(
strong = true,
- sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+ sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON,
)
.first(),
authenticatedModality = BiometricModality.Fingerprint,
@@ -1903,7 +1867,7 @@
fingerprint =
fingerprintSensorPropertiesInternal(
strong = true,
- sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+ sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON,
)
.first(),
authenticatedModality = BiometricModality.Fingerprint,
@@ -1913,7 +1877,7 @@
fingerprint =
fingerprintSensorPropertiesInternal(
strong = true,
- sensorType = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
+ sensorType = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
)
.first(),
authenticatedModality = BiometricModality.Fingerprint,
@@ -1932,7 +1896,7 @@
fingerprint =
fingerprintSensorPropertiesInternal(
strong = true,
- sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+ sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON,
)
.first(),
authenticatedModality = BiometricModality.Fingerprint,
@@ -1958,7 +1922,7 @@
fingerprint =
fingerprintSensorPropertiesInternal(
strong = true,
- sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+ sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON,
)
.first(),
authenticatedModality = BiometricModality.Fingerprint,
@@ -1969,7 +1933,7 @@
fingerprint =
fingerprintSensorPropertiesInternal(
strong = true,
- sensorType = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
+ sensorType = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
)
.first(),
authenticatedModality = BiometricModality.Fingerprint,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
index 18f33e4..116b705 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
@@ -30,16 +30,20 @@
import com.android.systemui.common.shared.model.Text
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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(AndroidJUnit4::class)
class BrightnessSliderViewModelTest : SysuiTestCase() {
@@ -49,15 +53,17 @@
private val kosmos = testKosmos()
- private val underTest =
+ private val underTest by lazy {
with(kosmos) {
BrightnessSliderViewModel(
screenBrightnessInteractor,
brightnessPolicyEnforcementInteractor,
- applicationCoroutineScope,
sliderHapticsViewModelFactory,
+ brightnessMirrorShowingInteractor,
+ supportsMirroring = true,
)
}
+ }
@Before
fun setUp() {
@@ -65,18 +71,18 @@
LinearBrightness(minBrightness),
LinearBrightness(maxBrightness),
)
+ underTest.activateIn(kosmos.testScope)
}
@Test
fun brightnessChangeInRepository_changeInFlow() =
with(kosmos) {
testScope.runTest {
- val gammaBrightness by collectLastValue(underTest.currentBrightness)
-
var brightness = 0.6f
fakeScreenBrightnessRepository.setBrightness(LinearBrightness(brightness))
+ runCurrent()
- assertThat(gammaBrightness!!.value)
+ assertThat(underTest.currentBrightness.value)
.isEqualTo(
BrightnessUtils.convertLinearToGammaFloat(
brightness,
@@ -87,8 +93,9 @@
brightness = 0.2f
fakeScreenBrightnessRepository.setBrightness(LinearBrightness(brightness))
+ runCurrent()
- assertThat(gammaBrightness!!.value)
+ assertThat(underTest.currentBrightness.value)
.isEqualTo(
BrightnessUtils.convertLinearToGammaFloat(
brightness,
@@ -117,7 +124,6 @@
testScope.runTest {
val temporaryBrightness by
collectLastValue(fakeScreenBrightnessRepository.temporaryBrightness)
- val brightness by collectLastValue(underTest.currentBrightness)
val newBrightness = underTest.maxBrightness.value / 3
val expectedTemporaryBrightness =
@@ -133,7 +139,7 @@
assertThat(temporaryBrightness!!.floatValue)
.isWithin(1e-5f)
.of(expectedTemporaryBrightness)
- assertThat(brightness!!.value).isNotEqualTo(newBrightness)
+ assertThat(underTest.currentBrightness.value).isNotEqualTo(newBrightness)
}
}
@@ -141,14 +147,13 @@
fun draggingStopped_currentBrightnessChanges() =
with(kosmos) {
testScope.runTest {
- val brightness by collectLastValue(underTest.currentBrightness)
-
val newBrightness = underTest.maxBrightness.value / 3
val drag = Drag.Stopped(GammaBrightness(newBrightness))
underTest.onDrag(drag)
+ runCurrent()
- assertThat(brightness!!.value).isEqualTo(newBrightness)
+ assertThat(underTest.currentBrightness.value).isEqualTo(newBrightness)
}
}
@@ -168,4 +173,40 @@
)
)
}
+
+ @Test
+ fun supportedMirror_mirrorShowingWhenDragging() =
+ with(kosmos) {
+ testScope.runTest {
+ val mirrorInInteractor by
+ collectLastValue(brightnessMirrorShowingInteractor.isShowing)
+
+ underTest.setIsDragging(true)
+ assertThat(mirrorInInteractor).isEqualTo(true)
+ assertThat(underTest.showMirror).isEqualTo(true)
+
+ underTest.setIsDragging(false)
+ assertThat(mirrorInInteractor).isEqualTo(false)
+ assertThat(underTest.showMirror).isEqualTo(false)
+ }
+ }
+
+ @Test
+ fun unsupportedMirror_mirrorNeverShowing() =
+ with(kosmos) {
+ testScope.runTest {
+ val mirrorInInteractor by
+ collectLastValue(brightnessMirrorShowingInteractor.isShowing)
+
+ val noMirrorViewModel = brightnessSliderViewModelFactory.create(false)
+
+ noMirrorViewModel.setIsDragging(true)
+ assertThat(mirrorInInteractor).isEqualTo(false)
+ assertThat(noMirrorViewModel.showMirror).isEqualTo(false)
+
+ noMirrorViewModel.setIsDragging(false)
+ assertThat(mirrorInInteractor).isEqualTo(false)
+ assertThat(noMirrorViewModel.showMirror).isEqualTo(false)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
index 2b7e7ad..20d6615 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
@@ -16,34 +16,57 @@
package com.android.systemui.deviceentry.domain.interactor
+import android.hardware.face.FaceManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.biometrics.authController
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.bouncer.data.repository.keyguardBouncerRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.configureKeyguardBypass
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.powerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.phone.dozeScrimController
+import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+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.ArgumentMatchers.anyBoolean
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -51,24 +74,52 @@
class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val underTest = kosmos.deviceEntryHapticsInteractor
+ private lateinit var underTest: DeviceEntryHapticsInteractor
+
+ @Before
+ fun setup() {
+ if (SceneContainerFlag.isEnabled) {
+ whenever(kosmos.authController.isUdfpsFingerDown).thenReturn(false)
+ whenever(kosmos.dozeScrimController.isPulsing).thenReturn(false)
+ whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(true)
+ whenever(kosmos.screenOffAnimationController.isKeyguardShowDelayed()).thenReturn(false)
+
+ // Dependencies for DeviceEntrySourceInteractor#biometricUnlockStateOnKeyguardDismissed
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
+ whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(true)
+
+ // Mock authenticationMethodIsSecure true
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+
+ kosmos.keyguardBouncerRepository.setAlternateVisible(false)
+ kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ } else {
+ underTest = kosmos.deviceEntryHapticsInteractor
+ }
+ }
+
+ @DisableSceneContainer
@Test
fun nonPowerButtonFPS_vibrateSuccess() =
testScope.runTest {
val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
- setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+ enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
runCurrent()
- enterDeviceFromBiometricUnlock()
+ enterDeviceFromFingerprintUnlockLegacy()
assertThat(playSuccessHaptic).isNotNull()
}
+ @DisableSceneContainer
@Test
fun powerButtonFPS_vibrateSuccess() =
testScope.runTest {
val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
- setPowerButtonFingerprintProperty()
- setFingerprintEnrolled()
+ enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
// It's been 10 seconds since the last power button wakeup
@@ -76,16 +127,16 @@
advanceTimeBy(10000)
runCurrent()
- enterDeviceFromBiometricUnlock()
+ enterDeviceFromFingerprintUnlockLegacy()
assertThat(playSuccessHaptic).isNotNull()
}
+ @DisableSceneContainer
@Test
fun powerButtonFPS_powerDown_doNotVibrateSuccess() =
testScope.runTest {
val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
- setPowerButtonFingerprintProperty()
- setFingerprintEnrolled()
+ enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
kosmos.fakeKeyEventRepository.setPowerButtonDown(true) // power button is currently DOWN
// It's been 10 seconds since the last power button wakeup
@@ -93,16 +144,16 @@
advanceTimeBy(10000)
runCurrent()
- enterDeviceFromBiometricUnlock()
+ enterDeviceFromFingerprintUnlockLegacy()
assertThat(playSuccessHaptic).isNull()
}
+ @DisableSceneContainer
@Test
fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() =
testScope.runTest {
val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
- setPowerButtonFingerprintProperty()
- setFingerprintEnrolled()
+ enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
// It's only been 50ms since the last power button wakeup
@@ -110,7 +161,7 @@
advanceTimeBy(50)
runCurrent()
- enterDeviceFromBiometricUnlock()
+ enterDeviceFromFingerprintUnlockLegacy()
assertThat(playSuccessHaptic).isNull()
}
@@ -118,7 +169,7 @@
fun nonPowerButtonFPS_vibrateError() =
testScope.runTest {
val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
- setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+ enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
runCurrent()
fingerprintFailure()
assertThat(playErrorHaptic).isNotNull()
@@ -128,8 +179,8 @@
fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() =
testScope.runTest {
val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
- setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
- coExEnrolledAndEnabled()
+ enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
+ enrollFace()
runCurrent()
faceFailure()
assertThat(playErrorHaptic).isNull()
@@ -139,8 +190,7 @@
fun powerButtonFPS_vibrateError() =
testScope.runTest {
val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
- setPowerButtonFingerprintProperty()
- setFingerprintEnrolled()
+ enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
runCurrent()
fingerprintFailure()
assertThat(playErrorHaptic).isNotNull()
@@ -150,15 +200,143 @@
fun powerButtonFPS_powerDown_doNotVibrateError() =
testScope.runTest {
val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
- setPowerButtonFingerprintProperty()
- setFingerprintEnrolled()
+ enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
kosmos.fakeKeyEventRepository.setPowerButtonDown(true)
runCurrent()
fingerprintFailure()
assertThat(playErrorHaptic).isNull()
}
- private suspend fun enterDeviceFromBiometricUnlock() {
+ @EnableSceneContainer
+ @Test
+ fun playSuccessHaptic_onDeviceEntryFromUdfps_sceneContainer() =
+ testScope.runTest {
+ kosmos.configureKeyguardBypass(isBypassAvailable = false)
+ underTest = kosmos.deviceEntryHapticsInteractor
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
+ runCurrent()
+ configureDeviceEntryFromBiometricSource(isFpUnlock = true)
+ verifyDeviceEntryFromFingerprintAuth()
+ assertThat(playSuccessHaptic).isNotNull()
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun playSuccessHaptic_onDeviceEntryFromSfps_sceneContainer() =
+ testScope.runTest {
+ kosmos.configureKeyguardBypass(isBypassAvailable = false)
+ underTest = kosmos.deviceEntryHapticsInteractor
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
+ kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
+
+ // It's been 10 seconds since the last power button wakeup
+ setAwakeFromPowerButton()
+ advanceTimeBy(10000)
+ runCurrent()
+
+ configureDeviceEntryFromBiometricSource(isFpUnlock = true)
+ verifyDeviceEntryFromFingerprintAuth()
+ assertThat(playSuccessHaptic).isNotNull()
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun playSuccessHaptic_onDeviceEntryFromFaceAuth_sceneContainer() =
+ testScope.runTest {
+ enrollFace()
+ kosmos.configureKeyguardBypass(isBypassAvailable = true)
+ underTest = kosmos.deviceEntryHapticsInteractor
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ configureDeviceEntryFromBiometricSource(isFaceUnlock = true)
+ verifyDeviceEntryFromFaceAuth()
+ assertThat(playSuccessHaptic).isNotNull()
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun skipSuccessHaptic_onDeviceEntryFromSfps_whenPowerDown_sceneContainer() =
+ testScope.runTest {
+ kosmos.configureKeyguardBypass(isBypassAvailable = false)
+ underTest = kosmos.deviceEntryHapticsInteractor
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
+ // power button is currently DOWN
+ kosmos.fakeKeyEventRepository.setPowerButtonDown(true)
+
+ // It's been 10 seconds since the last power button wakeup
+ setAwakeFromPowerButton()
+ advanceTimeBy(10000)
+ runCurrent()
+
+ configureDeviceEntryFromBiometricSource(isFpUnlock = true)
+ verifyDeviceEntryFromFingerprintAuth()
+ assertThat(playSuccessHaptic).isNull()
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun skipSuccessHaptic_onDeviceEntryFromSfps_whenPowerButtonRecentlyPressed_sceneContainer() =
+ testScope.runTest {
+ kosmos.configureKeyguardBypass(isBypassAvailable = false)
+ underTest = kosmos.deviceEntryHapticsInteractor
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+ enrollFingerprint(FingerprintSensorType.POWER_BUTTON)
+ kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
+
+ // It's only been 50ms since the last power button wakeup
+ setAwakeFromPowerButton()
+ advanceTimeBy(50)
+ runCurrent()
+
+ configureDeviceEntryFromBiometricSource(isFpUnlock = true)
+ verifyDeviceEntryFromFingerprintAuth()
+ assertThat(playSuccessHaptic).isNull()
+ }
+
+ // Mock dependencies for DeviceEntrySourceInteractor#deviceEntryFromBiometricSource
+ private fun configureDeviceEntryFromBiometricSource(
+ isFpUnlock: Boolean = false,
+ isFaceUnlock: Boolean = false,
+ ) {
+ // Mock DeviceEntrySourceInteractor#deviceEntryBiometricAuthSuccessState
+ if (isFpUnlock) {
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ }
+ if (isFaceUnlock) {
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+ SuccessFaceAuthenticationStatus(
+ FaceManager.AuthenticationResult(null, null, 0, true)
+ )
+ )
+
+ // Mock DeviceEntrySourceInteractor#faceWakeAndUnlockMode = MODE_UNLOCK_COLLAPSING
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(Scenes.Lockscreen)
+ )
+ )
+ }
+ underTest = kosmos.deviceEntryHapticsInteractor
+ }
+
+ private fun TestScope.verifyDeviceEntryFromFingerprintAuth() {
+ val deviceEntryFromBiometricSource by
+ collectLastValue(kosmos.deviceEntrySourceInteractor.deviceEntryFromBiometricSource)
+ assertThat(deviceEntryFromBiometricSource)
+ .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ }
+
+ private fun TestScope.verifyDeviceEntryFromFaceAuth() {
+ val deviceEntryFromBiometricSource by
+ collectLastValue(kosmos.deviceEntrySourceInteractor.deviceEntryFromBiometricSource)
+ assertThat(deviceEntryFromBiometricSource).isEqualTo(BiometricUnlockSource.FACE_SENSOR)
+ }
+
+ private fun enterDeviceFromFingerprintUnlockLegacy() {
kosmos.fakeKeyguardRepository.setBiometricUnlockSource(
BiometricUnlockSource.FINGERPRINT_SENSOR
)
@@ -177,21 +355,22 @@
)
}
- private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) {
- kosmos.fingerprintPropertyRepository.setProperties(
- sensorId = 0,
- strength = SensorStrength.STRONG,
- sensorType = fingerprintSensorType,
- sensorLocations = mapOf(),
- )
+ private fun enrollFingerprint(fingerprintSensorType: FingerprintSensorType?) {
+ if (fingerprintSensorType == null) {
+ kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ } else {
+ kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ kosmos.fingerprintPropertyRepository.setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = fingerprintSensorType,
+ sensorLocations = mapOf(),
+ )
+ }
}
- private fun setPowerButtonFingerprintProperty() {
- setFingerprintSensorType(FingerprintSensorType.POWER_BUTTON)
- }
-
- private fun setFingerprintEnrolled() {
- kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ private fun enrollFace() {
+ kosmos.biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
}
private fun setAwakeFromPowerButton() {
@@ -202,9 +381,4 @@
powerButtonLaunchGestureTriggered = false,
)
}
-
- private fun coExEnrolledAndEnabled() {
- setFingerprintEnrolled()
- kosmos.biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt
index 2e4c97b..b3c891d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt
@@ -16,21 +16,51 @@
package com.android.systemui.deviceentry.domain.interactor
+import android.hardware.face.FaceManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.biometrics.authController
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.configureKeyguardBypass
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardBypassRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.verifyCallback
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.phone.dozeScrimController
+import com.android.systemui.statusbar.phone.screenOffAnimationController
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.statusbar.policy.devicePostureController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
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.ArgumentMatchers.anyBoolean
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -38,45 +68,328 @@
class DeviceEntrySourceInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val underTest = kosmos.deviceEntrySourceInteractor
+ private lateinit var underTest: DeviceEntrySourceInteractor
+ @Before
+ fun setup() {
+ if (SceneContainerFlag.isEnabled) {
+ whenever(kosmos.authController.isUdfpsFingerDown).thenReturn(false)
+ whenever(kosmos.dozeScrimController.isPulsing).thenReturn(false)
+ whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(true)
+ whenever(kosmos.screenOffAnimationController.isKeyguardShowDelayed()).thenReturn(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ } else {
+ underTest = kosmos.deviceEntrySourceInteractor
+ }
+ }
+
+ @DisableSceneContainer
@Test
fun deviceEntryFromFaceUnlock() =
testScope.runTest {
val deviceEntryFromBiometricAuthentication by
collectLastValue(underTest.deviceEntryFromBiometricSource)
- keyguardRepository.setBiometricUnlockState(
+
+ kosmos.fakeKeyguardRepository.setBiometricUnlockState(
BiometricUnlockMode.WAKE_AND_UNLOCK,
BiometricUnlockSource.FACE_SENSOR,
)
runCurrent()
+
assertThat(deviceEntryFromBiometricAuthentication)
.isEqualTo(BiometricUnlockSource.FACE_SENSOR)
}
+ @DisableSceneContainer
@Test
- fun deviceEntryFromFingerprintUnlock() = runTest {
- val deviceEntryFromBiometricAuthentication by
- collectLastValue(underTest.deviceEntryFromBiometricSource)
- keyguardRepository.setBiometricUnlockState(
- BiometricUnlockMode.WAKE_AND_UNLOCK,
- BiometricUnlockSource.FINGERPRINT_SENSOR,
- )
- runCurrent()
- assertThat(deviceEntryFromBiometricAuthentication)
- .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ fun deviceEntryFromFingerprintUnlock() =
+ testScope.runTest {
+ val deviceEntryFromBiometricAuthentication by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+
+ kosmos.fakeKeyguardRepository.setBiometricUnlockState(
+ BiometricUnlockMode.WAKE_AND_UNLOCK,
+ BiometricUnlockSource.FINGERPRINT_SENSOR,
+ )
+ runCurrent()
+
+ assertThat(deviceEntryFromBiometricAuthentication)
+ .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ }
+
+ @DisableSceneContainer
+ @Test
+ fun noDeviceEntry() =
+ testScope.runTest {
+ val deviceEntryFromBiometricAuthentication by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+
+ kosmos.fakeKeyguardRepository.setBiometricUnlockState(
+ BiometricUnlockMode.ONLY_WAKE, // doesn't dismiss keyguard:
+ BiometricUnlockSource.FINGERPRINT_SENSOR,
+ )
+ runCurrent()
+
+ assertThat(deviceEntryFromBiometricAuthentication).isNull()
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun deviceEntryFromFingerprintUnlockOnLockScreen_sceneContainerEnabled() =
+ testScope.runTest {
+ underTest = kosmos.deviceEntrySourceInteractor
+ val deviceEntryFromBiometricSource by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
+ configureDeviceEntryBiometricAuthSuccessState(isFingerprintAuth = true)
+ configureBiometricUnlockState(
+ alternateBouncerVisible = false,
+ sceneKey = Scenes.Lockscreen,
+ )
+ runCurrent()
+
+ kosmos.configureKeyguardBypass(isBypassAvailable = true)
+ underTest = kosmos.deviceEntrySourceInteractor
+
+ assertThat(deviceEntryFromBiometricSource)
+ .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun deviceEntryFromFingerprintUnlockOnAod_sceneContainerEnabled() =
+ testScope.runTest {
+ underTest = kosmos.deviceEntrySourceInteractor
+ val deviceEntryFromBiometricSource by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(false)
+ configureDeviceEntryBiometricAuthSuccessState(isFingerprintAuth = true)
+ configureBiometricUnlockState(alternateBouncerVisible = false, sceneKey = Scenes.Dream)
+ runCurrent()
+
+ assertThat(deviceEntryFromBiometricSource)
+ .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun deviceEntryFromFingerprintUnlockOnBouncer_sceneContainerEnabled() =
+ testScope.runTest {
+ underTest = kosmos.deviceEntrySourceInteractor
+ val deviceEntryFromBiometricSource by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
+ configureDeviceEntryBiometricAuthSuccessState(isFingerprintAuth = true)
+ configureBiometricUnlockState(
+ alternateBouncerVisible = false,
+ sceneKey = Scenes.Bouncer,
+ )
+ runCurrent()
+
+ assertThat(deviceEntryFromBiometricSource)
+ .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun deviceEntryFromFingerprintUnlockOnShade_sceneContainerEnabled() =
+ testScope.runTest {
+ underTest = kosmos.deviceEntrySourceInteractor
+ val deviceEntryFromBiometricSource by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
+ configureDeviceEntryBiometricAuthSuccessState(isFingerprintAuth = true)
+ configureBiometricUnlockState(
+ alternateBouncerVisible = false,
+ sceneKey = Scenes.Lockscreen,
+ )
+ runCurrent()
+
+ assertThat(deviceEntryFromBiometricSource)
+ .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun deviceEntryFromFingerprintUnlockOnAlternateBouncer_sceneContainerEnabled() =
+ testScope.runTest {
+ underTest = kosmos.deviceEntrySourceInteractor
+ val deviceEntryFromBiometricSource by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
+ configureDeviceEntryBiometricAuthSuccessState(isFingerprintAuth = true)
+ configureBiometricUnlockState(
+ alternateBouncerVisible = true,
+ sceneKey = Scenes.Lockscreen,
+ )
+ runCurrent()
+
+ assertThat(deviceEntryFromBiometricSource)
+ .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun deviceEntryFromFaceUnlockOnLockScreen_bypassAvailable_sceneContainerEnabled() =
+ testScope.runTest {
+ kosmos.configureKeyguardBypass(isBypassAvailable = true)
+ underTest = kosmos.deviceEntrySourceInteractor
+
+ val deviceEntryFromBiometricSource by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
+ configureDeviceEntryBiometricAuthSuccessState(isFaceAuth = true)
+ configureBiometricUnlockState(
+ alternateBouncerVisible = false,
+ sceneKey = Scenes.Lockscreen,
+ )
+ runCurrent()
+
+ assertThat(deviceEntryFromBiometricSource).isEqualTo(BiometricUnlockSource.FACE_SENSOR)
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun deviceEntryFromFaceUnlockOnLockScreen_bypassDisabled_sceneContainerEnabled() =
+ testScope.runTest {
+ kosmos.configureKeyguardBypass(isBypassAvailable = false)
+ underTest = kosmos.deviceEntrySourceInteractor
+
+ collectLastValue(kosmos.keyguardBypassRepository.isBypassAvailable)
+ runCurrent()
+
+ val postureControllerCallback = kosmos.devicePostureController.verifyCallback()
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ val deviceEntryFromBiometricSource by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
+ configureDeviceEntryBiometricAuthSuccessState(isFaceAuth = true)
+ configureBiometricUnlockState(
+ alternateBouncerVisible = false,
+ sceneKey = Scenes.Lockscreen,
+ )
+ runCurrent()
+
+ // MODE_NONE does not dismiss keyguard
+ assertThat(deviceEntryFromBiometricSource).isNull()
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun deviceEntryFromFaceUnlockOnBouncer_sceneContainerEnabled() =
+ testScope.runTest {
+ kosmos.configureKeyguardBypass(isBypassAvailable = true)
+ underTest = kosmos.deviceEntrySourceInteractor
+ val deviceEntryFromBiometricSource by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
+ configureDeviceEntryBiometricAuthSuccessState(isFaceAuth = true)
+ configureBiometricUnlockState(
+ alternateBouncerVisible = false,
+ sceneKey = Scenes.Bouncer,
+ )
+ runCurrent()
+
+ assertThat(deviceEntryFromBiometricSource).isEqualTo(BiometricUnlockSource.FACE_SENSOR)
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun deviceEntryFromFaceUnlockOnShade_bypassAvailable_sceneContainerEnabled() =
+ testScope.runTest {
+ kosmos.configureKeyguardBypass(isBypassAvailable = true)
+ underTest = kosmos.deviceEntrySourceInteractor
+ val deviceEntryFromBiometricSource by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
+ configureDeviceEntryBiometricAuthSuccessState(isFaceAuth = true)
+ configureBiometricUnlockState(alternateBouncerVisible = false, sceneKey = Scenes.Shade)
+ runCurrent()
+
+ // MODE_NONE does not dismiss keyguard
+ assertThat(deviceEntryFromBiometricSource).isNull()
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun deviceEntryFromFaceUnlockOnShade_bypassDisabled_sceneContainerEnabled() =
+ testScope.runTest {
+ kosmos.configureKeyguardBypass(isBypassAvailable = false)
+ underTest = kosmos.deviceEntrySourceInteractor
+ val deviceEntryFromBiometricSource by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
+ configureDeviceEntryBiometricAuthSuccessState(isFaceAuth = true)
+ configureBiometricUnlockState(
+ alternateBouncerVisible = false,
+ sceneKey = Scenes.Lockscreen,
+ )
+ runCurrent()
+
+ assertThat(deviceEntryFromBiometricSource).isNull()
+ }
+
+ @EnableSceneContainer
+ @Test
+ fun deviceEntryFromFaceUnlockOnAlternateBouncer_sceneContainerEnabled() =
+ testScope.runTest {
+ kosmos.configureKeyguardBypass(isBypassAvailable = true)
+ underTest = kosmos.deviceEntrySourceInteractor
+ val deviceEntryFromBiometricSource by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+ configureDeviceEntryBiometricAuthSuccessState(isFaceAuth = true)
+ configureBiometricUnlockState(
+ alternateBouncerVisible = true,
+ sceneKey = Scenes.Lockscreen,
+ )
+ runCurrent()
+
+ assertThat(deviceEntryFromBiometricSource).isEqualTo(BiometricUnlockSource.FACE_SENSOR)
+ }
+
+ private fun configureDeviceEntryBiometricAuthSuccessState(
+ isFingerprintAuth: Boolean = false,
+ isFaceAuth: Boolean = false,
+ ) {
+ if (isFingerprintAuth) {
+ val successStatus = SuccessFingerprintAuthenticationStatus(0, true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(successStatus)
+ }
+
+ if (isFaceAuth) {
+ val successStatus: FaceAuthenticationStatus =
+ SuccessFaceAuthenticationStatus(
+ FaceManager.AuthenticationResult(null, null, 0, true)
+ )
+ kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(successStatus)
+ }
}
- @Test
- fun noDeviceEntry() = runTest {
- val deviceEntryFromBiometricAuthentication by
- collectLastValue(underTest.deviceEntryFromBiometricSource)
- keyguardRepository.setBiometricUnlockState(
- BiometricUnlockMode.ONLY_WAKE, // doesn't dismiss keyguard:
- BiometricUnlockSource.FINGERPRINT_SENSOR,
+ private fun configureBiometricUnlockState(
+ alternateBouncerVisible: Boolean,
+ sceneKey: SceneKey,
+ ) {
+ kosmos.keyguardBouncerRepository.setAlternateVisible(alternateBouncerVisible)
+ kosmos.sceneInteractor.changeScene(sceneKey, "reason")
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(sceneKey))
)
- runCurrent()
- assertThat(deviceEntryFromBiometricAuthentication).isNull()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index 77337d3..a981e20 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -18,6 +18,7 @@
import android.content.Intent
import android.content.mockedContext
+import android.content.res.Resources
import android.hardware.fingerprint.FingerprintManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -41,13 +42,16 @@
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.activityStarter
import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
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.ArgumentCaptor
@@ -55,6 +59,7 @@
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -63,8 +68,8 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val underTest = kosmos.occludingAppDeviceEntryInteractor
-
+ private lateinit var underTest: OccludingAppDeviceEntryInteractor
+ private lateinit var mockedResources: Resources
private val fingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
private val keyguardRepository = kosmos.fakeKeyguardRepository
private val bouncerRepository = kosmos.keyguardBouncerRepository
@@ -74,9 +79,18 @@
private val mockedContext = kosmos.mockedContext
private val mockedActivityStarter = kosmos.activityStarter
+ @Before
+ fun setup() {
+ mockedResources = mock<Resources>()
+ whenever(mockedContext.resources).thenReturn(mockedResources)
+ whenever(mockedResources.getBoolean(R.bool.config_goToHomeFromOccludedApps))
+ .thenReturn(true)
+ }
+
@Test
fun fingerprintSuccess_goToHomeScreen() =
testScope.runTest {
+ underTest = kosmos.occludingAppDeviceEntryInteractor
givenOnOccludingApp(true)
fingerprintAuthRepository.setAuthenticationStatus(
SuccessFingerprintAuthenticationStatus(0, true)
@@ -86,8 +100,23 @@
}
@Test
+ fun fingerprintSuccess_configOff_doesNotGoToHomeScreen() =
+ testScope.runTest {
+ whenever(mockedResources.getBoolean(R.bool.config_goToHomeFromOccludedApps))
+ .thenReturn(false)
+ underTest = kosmos.occludingAppDeviceEntryInteractor
+ givenOnOccludingApp(true)
+ fingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ verifyNeverGoToHomeScreen()
+ }
+
+ @Test
fun fingerprintSuccess_notInteractive_doesNotGoToHomeScreen() =
testScope.runTest {
+ underTest = kosmos.occludingAppDeviceEntryInteractor
givenOnOccludingApp(true)
powerRepository.setInteractive(false)
fingerprintAuthRepository.setAuthenticationStatus(
@@ -100,6 +129,7 @@
@Test
fun fingerprintSuccess_dreaming_doesNotGoToHomeScreen() =
testScope.runTest {
+ underTest = kosmos.occludingAppDeviceEntryInteractor
givenOnOccludingApp(true)
keyguardRepository.setDreaming(true)
fingerprintAuthRepository.setAuthenticationStatus(
@@ -112,6 +142,7 @@
@Test
fun fingerprintSuccess_notOnOccludingApp_doesNotGoToHomeScreen() =
testScope.runTest {
+ underTest = kosmos.occludingAppDeviceEntryInteractor
givenOnOccludingApp(false)
fingerprintAuthRepository.setAuthenticationStatus(
SuccessFingerprintAuthenticationStatus(0, true)
@@ -123,11 +154,12 @@
@Test
fun lockout_goToHomeScreenOnDismissAction() =
testScope.runTest {
+ underTest = kosmos.occludingAppDeviceEntryInteractor
givenOnOccludingApp(true)
fingerprintAuthRepository.setAuthenticationStatus(
ErrorFingerprintAuthenticationStatus(
FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
- "lockoutTest"
+ "lockoutTest",
)
)
runCurrent()
@@ -137,11 +169,12 @@
@Test
fun lockout_notOnOccludingApp_neverGoToHomeScreen() =
testScope.runTest {
+ underTest = kosmos.occludingAppDeviceEntryInteractor
givenOnOccludingApp(false)
fingerprintAuthRepository.setAuthenticationStatus(
ErrorFingerprintAuthenticationStatus(
FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
- "lockoutTest"
+ "lockoutTest",
)
)
runCurrent()
@@ -151,11 +184,12 @@
@Test
fun lockout_onOccludingApp_onCommunal_neverGoToHomeScreen() =
testScope.runTest {
+ underTest = kosmos.occludingAppDeviceEntryInteractor
givenOnOccludingApp(isOnOccludingApp = true, isOnCommunal = true)
fingerprintAuthRepository.setAuthenticationStatus(
ErrorFingerprintAuthenticationStatus(
FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
- "lockoutTest"
+ "lockoutTest",
)
)
runCurrent()
@@ -165,6 +199,7 @@
@Test
fun message_fpFailOnOccludingApp_thenNotOnOccludingApp() =
testScope.runTest {
+ underTest = kosmos.occludingAppDeviceEntryInteractor
val message by collectLastValue(underTest.message)
givenOnOccludingApp(true)
@@ -186,6 +221,7 @@
@Test
fun message_fpErrorHelpFailOnOccludingApp() =
testScope.runTest {
+ underTest = kosmos.occludingAppDeviceEntryInteractor
val message by collectLastValue(underTest.message)
givenOnOccludingApp(true)
@@ -218,6 +254,7 @@
@Test
fun message_fpError_lockoutFilteredOut() =
testScope.runTest {
+ underTest = kosmos.occludingAppDeviceEntryInteractor
val message by collectLastValue(underTest.message)
givenOnOccludingApp(true)
@@ -246,6 +283,7 @@
@Test
fun noMessage_fpErrorsWhileDozing() =
testScope.runTest {
+ underTest = kosmos.occludingAppDeviceEntryInteractor
val message by collectLastValue(underTest.message)
givenOnOccludingApp(true)
@@ -254,7 +292,7 @@
kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.OCCLUDED,
to = KeyguardState.DOZING,
- testScope
+ testScope,
)
runCurrent()
@@ -283,7 +321,7 @@
private suspend fun givenOnOccludingApp(
isOnOccludingApp: Boolean,
- isOnCommunal: Boolean = false
+ isOnCommunal: Boolean = false,
) {
powerRepository.setInteractive(true)
keyguardRepository.setIsDozing(false)
@@ -305,13 +343,13 @@
kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OCCLUDED,
- testScope
+ testScope,
)
} else {
kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.OCCLUDED,
to = KeyguardState.LOCKSCREEN,
- testScope
+ testScope,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt
similarity index 90%
rename from packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt
index 5a76489..f68a1b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt
@@ -19,6 +19,7 @@
import android.content.testableContext
import android.platform.test.annotations.EnableFlags
import android.view.Display
+import android.view.layoutInflater
import android.view.mockWindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -49,14 +50,18 @@
private val applicationContext = kosmos.testableContext
private val applicationWindowManager = kosmos.mockWindowManager
+ private val applicationLayoutInflater = kosmos.layoutInflater
- private val repo =
+ // Lazy so that @EnableFlags has time to run before this repo is instantiated
+ private val repo by lazy {
DisplayWindowPropertiesRepositoryImpl(
kosmos.applicationCoroutineScope,
applicationContext,
applicationWindowManager,
+ kosmos.layoutInflater,
fakeDisplayRepository,
)
+ }
@Before
fun start() {
@@ -81,6 +86,7 @@
windowType = WINDOW_TYPE_FOO,
context = applicationContext,
windowManager = applicationWindowManager,
+ layoutInflater = applicationLayoutInflater,
)
)
}
@@ -102,6 +108,14 @@
}
@Test
+ fun get_nonDefaultDisplayId_returnsNewLayoutInflater() =
+ testScope.runTest {
+ val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ assertThat(displayContext.layoutInflater).isNotSameInstanceAs(applicationLayoutInflater)
+ }
+
+ @Test
fun get_multipleCallsForDefaultDisplay_returnsSameInstance() =
testScope.runTest {
val displayContext = repo.get(DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
index 9300db9..4317b9f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
@@ -16,25 +16,37 @@
package com.android.systemui.dreams.homecontrols
import android.app.Activity
+import android.content.ComponentName
import android.content.Intent
+import android.os.powerManager
import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_ACTIVITY_PANEL
import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_DREAM
import android.service.controls.ControlsProviderService.EXTRA_CONTROLS_SURFACE
+import android.service.dreams.DreamService
import android.window.TaskFragmentInfo
+import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.controls.settings.FakeControlsSettingsRepository
+import com.android.systemui.dreams.homecontrols.service.TaskFragmentComponent
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.dreams.homecontrols.shared.model.fakeHomeControlsDataSource
+import com.android.systemui.dreams.homecontrols.shared.model.homeControlsDataSource
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.testKosmos
+import com.android.systemui.util.time.fakeSystemClock
import com.android.systemui.util.wakelock.WakeLockFake
import com.google.common.truth.Truth.assertThat
-import java.util.Optional
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,7 +59,6 @@
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -62,13 +73,18 @@
WakeLockFake.Builder(context).apply { setWakeLock(fakeWakeLock) }
}
+ private val lifecycleOwner = TestLifecycleOwner(coroutineDispatcher = kosmos.testDispatcher)
+
private val taskFragmentComponent = mock<TaskFragmentComponent>()
private val activity = mock<Activity>()
private val onCreateCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>()
private val onInfoChangedCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>()
private val hideCallback = argumentCaptor<() -> Unit>()
- private val dreamServiceDelegate =
- mock<DreamServiceDelegate> { on { getActivity(any()) } doReturn activity }
+ private var dreamService =
+ mock<DreamService> {
+ on { activity } doReturn activity
+ on { redirectWake } doReturn false
+ }
private val taskFragmentComponentFactory =
mock<TaskFragmentComponent.Factory> {
@@ -82,12 +98,32 @@
} doReturn taskFragmentComponent
}
- private val underTest: HomeControlsDreamService by lazy { buildService() }
+ private val underTest: HomeControlsDreamServiceImpl by lazy {
+ with(kosmos) {
+ HomeControlsDreamServiceImpl(
+ taskFragmentFactory = taskFragmentComponentFactory,
+ wakeLockBuilder = fakeWakeLockBuilder,
+ powerManager = powerManager,
+ systemClock = fakeSystemClock,
+ dataSource = homeControlsDataSource,
+ logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest"),
+ service = dreamService,
+ lifecycleOwner = lifecycleOwner,
+ )
+ }
+ }
@Before
fun setup() {
- whenever(kosmos.controlsComponent.getControlsListingController())
- .thenReturn(Optional.of(kosmos.controlsListingController))
+ Dispatchers.setMain(kosmos.testDispatcher)
+ kosmos.fakeHomeControlsDataSource.setComponentInfo(
+ HomeControlsComponentInfo(PANEL_COMPONENT, true)
+ )
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
}
@Test
@@ -108,13 +144,10 @@
@Test
fun testNotCreatingTaskFragmentComponentWhenActivityIsNull() =
testScope.runTest {
- val serviceWithNullActivity =
- buildService(
- mock<DreamServiceDelegate> { on { getActivity(underTest) } doReturn null }
- )
-
- serviceWithNullActivity.onAttachedToWindow()
+ dreamService = mock<DreamService> { on { activity } doReturn null }
+ underTest.onAttachedToWindow()
verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any())
+ verify(dreamService).finish()
}
@Test
@@ -137,9 +170,9 @@
@Test
fun testFinishesDreamWithoutRestartingActivityWhenNotRedirectingWakes() =
testScope.runTest {
- whenever(dreamServiceDelegate.redirectWake(any())).thenReturn(false)
underTest.onAttachedToWindow()
onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>())
+ runCurrent()
verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())
// Task fragment becomes empty
@@ -149,16 +182,21 @@
advanceUntilIdle()
// Dream is finished and activity is not restarted
verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())
- verify(dreamServiceDelegate, never()).wakeUp(any())
- verify(dreamServiceDelegate).finish(any())
+ verify(dreamService, never()).wakeUp()
+ verify(dreamService).finish()
}
@Test
fun testRestartsActivityWhenRedirectingWakes() =
testScope.runTest {
- whenever(dreamServiceDelegate.redirectWake(any())).thenReturn(true)
+ dreamService =
+ mock<DreamService> {
+ on { activity } doReturn activity
+ on { redirectWake } doReturn true
+ }
underTest.onAttachedToWindow()
onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>())
+ runCurrent()
verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())
// Task fragment becomes empty
@@ -166,30 +204,20 @@
mock<TaskFragmentInfo> { on { isEmpty } doReturn true }
)
advanceUntilIdle()
+
// Activity is restarted instead of finishing the dream.
verify(taskFragmentComponent, times(2)).startActivityInTaskFragment(intentMatcher())
- verify(dreamServiceDelegate).wakeUp(any())
- verify(dreamServiceDelegate, never()).finish(any())
+ verify(dreamService).wakeUp()
+ verify(dreamService, never()).finish()
}
private fun intentMatcher() =
argThat<Intent> {
getIntExtra(EXTRA_CONTROLS_SURFACE, CONTROLS_SURFACE_ACTIVITY_PANEL) ==
- CONTROLS_SURFACE_DREAM
+ CONTROLS_SURFACE_DREAM && component == PANEL_COMPONENT
}
- private fun buildService(
- activityProvider: DreamServiceDelegate = dreamServiceDelegate
- ): HomeControlsDreamService =
- with(kosmos) {
- return HomeControlsDreamService(
- controlsSettingsRepository = FakeControlsSettingsRepository(),
- taskFragmentFactory = taskFragmentComponentFactory,
- homeControlsComponentInteractor = homeControlsComponentInteractor,
- wakeLockBuilder = fakeWakeLockBuilder,
- dreamServiceDelegate = activityProvider,
- bgDispatcher = testDispatcher,
- logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest")
- )
- }
+ private companion object {
+ val PANEL_COMPONENT = ComponentName("test.pkg", "test.panel")
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
index 1adf414..ed45e8c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
@@ -34,9 +34,14 @@
import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
-import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.dreams.homecontrols.system.HomeControlsDreamStartable
+import com.android.systemui.dreams.homecontrols.system.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsComponent
+import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsListingController
+import com.android.systemui.dreams.homecontrols.system.domain.interactor.homeControlsComponentInteractor
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.userTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -96,8 +101,9 @@
HomeControlsDreamStartable(
mContext,
packageManager,
+ kosmos.userTracker,
homeControlsComponentInteractor,
- kosmos.applicationCoroutineScope
+ kosmos.applicationCoroutineScope,
)
}
@@ -113,7 +119,7 @@
.setComponentEnabledSetting(
eq(componentName),
eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
- eq(PackageManager.DONT_KILL_APP)
+ eq(PackageManager.DONT_KILL_APP),
)
}
@@ -128,7 +134,7 @@
.setComponentEnabledSetting(
eq(componentName),
eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
- eq(PackageManager.DONT_KILL_APP)
+ eq(PackageManager.DONT_KILL_APP),
)
}
@@ -143,14 +149,14 @@
.setComponentEnabledSetting(
eq(componentName),
eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
- eq(PackageManager.DONT_KILL_APP)
+ eq(PackageManager.DONT_KILL_APP),
)
}
private fun ControlsServiceInfo(
componentName: ComponentName,
label: CharSequence,
- hasPanel: Boolean
+ hasPanel: Boolean,
): ControlsServiceInfo {
val serviceInfo =
ServiceInfo().apply {
@@ -165,7 +171,7 @@
context: Context,
serviceInfo: ServiceInfo,
private val label: CharSequence,
- hasPanel: Boolean
+ hasPanel: Boolean,
) : ControlsServiceInfo(context, serviceInfo) {
init {
@@ -185,7 +191,7 @@
UserInfo(
/* id= */ PRIMARY_USER_ID,
/* name= */ "primary user",
- /* flags= */ UserInfo.FLAG_PRIMARY
+ /* flags= */ UserInfo.FLAG_PRIMARY,
)
private const val TEST_PACKAGE_PANEL = "pkg.panel"
private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
@@ -193,13 +199,13 @@
SelectedComponentRepository.SelectedComponent(
TEST_PACKAGE_PANEL,
TEST_COMPONENT_PANEL,
- true
+ true,
)
private val TEST_SELECTED_COMPONENT_NON_PANEL =
SelectedComponentRepository.SelectedComponent(
TEST_PACKAGE_PANEL,
TEST_COMPONENT_PANEL,
- false
+ false,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxyTest.kt
new file mode 100644
index 0000000..e57776f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxyTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dreams.homecontrols.service
+
+import android.content.ComponentName
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+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.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HomeControlsRemoteProxyTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val fakeBinder = kosmos.fakeHomeControlsRemoteBinder
+
+ private val underTest by lazy { kosmos.homeControlsRemoteProxy }
+
+ @Test
+ fun testRegistersOnlyWhileSubscribed() =
+ testScope.runTest {
+ assertThat(fakeBinder.callbacks).isEmpty()
+
+ val job = launch { underTest.componentInfo.collect {} }
+ runCurrent()
+ assertThat(fakeBinder.callbacks).hasSize(1)
+
+ job.cancel()
+ runCurrent()
+ assertThat(fakeBinder.callbacks).isEmpty()
+ }
+
+ @Test
+ fun testEmitsOnCallback() =
+ testScope.runTest {
+ val componentInfo by collectLastValue(underTest.componentInfo)
+ assertThat(componentInfo).isNull()
+
+ fakeBinder.notifyCallbacks(TEST_COMPONENT, allowTrivialControlsOnLockscreen = true)
+ assertThat(componentInfo)
+ .isEqualTo(
+ HomeControlsComponentInfo(
+ TEST_COMPONENT,
+ allowTrivialControlsOnLockscreen = true,
+ )
+ )
+ }
+
+ @Test
+ fun testOnlyRegistersSingleCallbackForMultipleSubscribers() =
+ testScope.runTest {
+ assertThat(fakeBinder.callbacks).isEmpty()
+
+ // 2 collectors
+ val job = launch {
+ launch { underTest.componentInfo.collect {} }
+ launch { underTest.componentInfo.collect {} }
+ }
+ runCurrent()
+ assertThat(fakeBinder.callbacks).hasSize(1)
+ job.cancel()
+ }
+
+ private companion object {
+ val TEST_COMPONENT = ComponentName("pkg.test", "class.test")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegatorTest.kt
new file mode 100644
index 0000000..4002175
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegatorTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dreams.homecontrols.service
+
+import android.content.ComponentName
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dreams.homecontrols.dagger.HomeControlsRemoteServiceComponent
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.service.ObservableServiceConnection
+import com.android.systemui.util.service.PersistentConnectionManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+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.kotlin.argumentCaptor
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RemoteHomeControlsDataSourceDelegatorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val proxy = kosmos.homeControlsRemoteProxy
+ private val fakeBinder = kosmos.fakeHomeControlsRemoteBinder
+
+ private val callbackCaptor =
+ argumentCaptor<ObservableServiceConnection.Callback<HomeControlsRemoteProxy>>()
+
+ private val connectionManager =
+ mock<PersistentConnectionManager<HomeControlsRemoteProxy>> {
+ on { start() } doAnswer { simulateConnect() }
+ on { stop() } doAnswer { simulateDisconnect() }
+ }
+ private val serviceComponent =
+ mock<HomeControlsRemoteServiceComponent> {
+ on { connectionManager } doReturn connectionManager
+ }
+
+ private val underTest by lazy { kosmos.remoteHomeControlsDataSourceDelegator }
+
+ @Before
+ fun setUp() {
+ kosmos.homeControlsRemoteServiceFactory =
+ mock<HomeControlsRemoteServiceComponent.Factory>().stub {
+ on { create(callbackCaptor.capture()) } doReturn serviceComponent
+ }
+ }
+
+ @Test
+ fun testQueriesComponentInfoFromBinder() =
+ testScope.runTest {
+ assertThat(fakeBinder.callbacks).isEmpty()
+
+ val componentInfo by collectLastValue(underTest.componentInfo)
+
+ assertThat(componentInfo).isNull()
+ assertThat(fakeBinder.callbacks).hasSize(1)
+
+ fakeBinder.notifyCallbacks(TEST_COMPONENT, allowTrivialControlsOnLockscreen = true)
+ assertThat(componentInfo)
+ .isEqualTo(
+ HomeControlsComponentInfo(
+ TEST_COMPONENT,
+ allowTrivialControlsOnLockscreen = true,
+ )
+ )
+ }
+
+ @Test
+ fun testOnlyConnectToServiceOnSubscription() =
+ testScope.runTest {
+ verify(connectionManager, never()).start()
+
+ val job = launch { underTest.componentInfo.collect {} }
+ runCurrent()
+ verify(connectionManager, times(1)).start()
+ verify(connectionManager, never()).stop()
+
+ job.cancel()
+ runCurrent()
+ verify(connectionManager, times(1)).start()
+ verify(connectionManager, times(1)).stop()
+ }
+
+ private fun simulateConnect() {
+ callbackCaptor.lastValue.onConnected(mock(), proxy)
+ }
+
+ private fun simulateDisconnect() {
+ callbackCaptor.lastValue.onDisconnected(mock(), 0)
+ }
+
+ private companion object {
+ val TEST_COMPONENT = ComponentName("pkg.test", "class.test")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt
new file mode 100644
index 0000000..f8a45e8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dreams.homecontrols.system
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.ServiceInfo
+import android.content.pm.UserInfo
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.authorizedPanelsRepository
+import com.android.systemui.controls.panels.selectedComponentRepository
+import com.android.systemui.controls.settings.FakeControlsSettingsRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener
+import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsComponent
+import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsListingController
+import com.android.systemui.dreams.homecontrols.system.domain.interactor.homeControlsComponentInteractor
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+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.Mockito
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HomeControlsRemoteServiceBinderTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val lifecycleOwner = TestLifecycleOwner(coroutineDispatcher = kosmos.testDispatcher)
+ private val fakeControlsSettingsRepository = FakeControlsSettingsRepository()
+
+ private val underTest by lazy {
+ HomeControlsRemoteServiceBinder(
+ kosmos.homeControlsComponentInteractor,
+ fakeControlsSettingsRepository,
+ kosmos.backgroundCoroutineContext,
+ logcatLogBuffer(),
+ lifecycleOwner,
+ )
+ }
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ fakeUserRepository.setUserInfos(listOf(PRIMARY_USER))
+ whenever(controlsComponent.getControlsListingController())
+ .thenReturn(Optional.of(controlsListingController))
+ }
+ }
+
+ @Test
+ fun testRegisterSingleListener() =
+ testScope.runTest {
+ setup()
+ val controlsSettings by collectLastValue(addCallback())
+ runServicesUpdate()
+
+ assertThat(controlsSettings)
+ .isEqualTo(
+ CallbackArgs(
+ panelComponent = TEST_COMPONENT,
+ allowTrivialControlsOnLockscreen = false,
+ )
+ )
+ }
+
+ @Test
+ fun testRegisterMultipleListeners() =
+ testScope.runTest {
+ setup()
+ val controlsSettings1 by collectLastValue(addCallback())
+ val controlsSettings2 by collectLastValue(addCallback())
+ runServicesUpdate()
+
+ assertThat(controlsSettings1)
+ .isEqualTo(
+ CallbackArgs(
+ panelComponent = TEST_COMPONENT,
+ allowTrivialControlsOnLockscreen = false,
+ )
+ )
+ assertThat(controlsSettings2)
+ .isEqualTo(
+ CallbackArgs(
+ panelComponent = TEST_COMPONENT,
+ allowTrivialControlsOnLockscreen = false,
+ )
+ )
+ }
+
+ @Test
+ fun testListenerCalledWhenStateChanges() =
+ testScope.runTest {
+ setup()
+ val controlsSettings by collectLastValue(addCallback())
+ runServicesUpdate()
+
+ assertThat(controlsSettings)
+ .isEqualTo(
+ CallbackArgs(
+ panelComponent = TEST_COMPONENT,
+ allowTrivialControlsOnLockscreen = false,
+ )
+ )
+
+ kosmos.authorizedPanelsRepository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
+
+ // Updated with null component now that we are no longer authorized.
+ assertThat(controlsSettings)
+ .isEqualTo(
+ CallbackArgs(panelComponent = null, allowTrivialControlsOnLockscreen = false)
+ )
+ }
+
+ private fun TestScope.runServicesUpdate() {
+ runCurrent()
+ val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
+ val callback = withArgCaptor {
+ Mockito.verify(kosmos.controlsListingController).addCallback(capture())
+ }
+ callback.onServicesUpdated(listings)
+ runCurrent()
+ }
+
+ private fun addCallback() = conflatedCallbackFlow {
+ val callback =
+ object : IOnControlsSettingsChangeListener.Stub() {
+ override fun onControlsSettingsChanged(
+ panelComponent: ComponentName?,
+ allowTrivialControlsOnLockscreen: Boolean,
+ ) {
+ trySend(CallbackArgs(panelComponent, allowTrivialControlsOnLockscreen))
+ }
+ }
+ underTest.registerListenerForCurrentUser(callback)
+ awaitClose { underTest.unregisterListenerForCurrentUser(callback) }
+ }
+
+ private suspend fun TestScope.setup() {
+ kosmos.fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
+ kosmos.fakeUserTracker.set(listOf(PRIMARY_USER), 0)
+ kosmos.authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+ kosmos.selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
+ runCurrent()
+ }
+
+ private data class CallbackArgs(
+ val panelComponent: ComponentName?,
+ val allowTrivialControlsOnLockscreen: Boolean,
+ )
+
+ private fun ControlsServiceInfo(
+ componentName: ComponentName,
+ label: CharSequence,
+ hasPanel: Boolean,
+ ): ControlsServiceInfo {
+ val serviceInfo =
+ ServiceInfo().apply {
+ applicationInfo = ApplicationInfo()
+ packageName = componentName.packageName
+ name = componentName.className
+ }
+ return FakeControlsServiceInfo(context, serviceInfo, label, hasPanel)
+ }
+
+ private class FakeControlsServiceInfo(
+ context: Context,
+ serviceInfo: ServiceInfo,
+ private val label: CharSequence,
+ hasPanel: Boolean,
+ ) : ControlsServiceInfo(context, serviceInfo) {
+
+ init {
+ if (hasPanel) {
+ panelActivity = serviceInfo.componentName
+ }
+ }
+
+ override fun loadLabel(): CharSequence {
+ return label
+ }
+ }
+
+ private companion object {
+ const val PRIMARY_USER_ID = 0
+ val PRIMARY_USER =
+ UserInfo(
+ /* id= */ PRIMARY_USER_ID,
+ /* name= */ "primary user",
+ /* flags= */ UserInfo.FLAG_PRIMARY,
+ )
+
+ private const val TEST_PACKAGE = "pkg"
+ private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
+ private val TEST_SELECTED_COMPONENT_PANEL =
+ SelectedComponentRepository.SelectedComponent(TEST_PACKAGE, TEST_COMPONENT, true)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorTest.kt
similarity index 64%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorTest.kt
index 7292985..c950523 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorTest.kt
@@ -20,40 +20,32 @@
import android.content.pm.ApplicationInfo
import android.content.pm.ServiceInfo
import android.content.pm.UserInfo
-import android.os.PowerManager
-import android.os.UserHandle
-import android.os.powerManager
-import android.service.dream.dreamManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.data.repository.fakePackageChangeRepository
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
+import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsComponent
+import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsListingController
+import com.android.systemui.dreams.homecontrols.system.domain.interactor.homeControlsComponentInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
-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.launch
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.ArgumentMatchers.anyInt
-import org.mockito.Mockito.anyLong
-import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@OptIn(ExperimentalCoroutinesApi::class)
@@ -68,7 +60,6 @@
@Before
fun setUp() =
with(kosmos) {
- fakeSystemClock.setCurrentTimeMillis(0)
fakeUserRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
whenever(controlsComponent.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
@@ -172,113 +163,6 @@
}
}
- @Test
- fun testMonitoringUpdatesAndRestart() =
- with(kosmos) {
- testScope.runTest {
- setActiveUser(PRIMARY_USER)
- authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
- selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
- whenever(controlsListingController.getCurrentServices())
- .thenReturn(
- listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
- )
-
- val job = launch { underTest.monitorUpdatesAndRestart() }
- val panelComponent by collectLastValue(underTest.panelComponent)
-
- assertThat(panelComponent).isEqualTo(TEST_COMPONENT)
- verify(dreamManager, never()).startDream()
-
- fakeSystemClock.advanceTime(100)
- // The package update is started.
- fakePackageChangeRepository.notifyUpdateStarted(
- TEST_PACKAGE,
- UserHandle.of(PRIMARY_USER_ID),
- )
- fakeSystemClock.advanceTime(MAX_UPDATE_CORRELATION_DELAY.inWholeMilliseconds)
- // Task fragment becomes empty as a result of the update.
- underTest.onDreamEndUnexpectedly()
-
- runCurrent()
- verify(dreamManager, never()).startDream()
-
- fakeSystemClock.advanceTime(500)
- // The package update is finished.
- fakePackageChangeRepository.notifyUpdateFinished(
- TEST_PACKAGE,
- UserHandle.of(PRIMARY_USER_ID),
- )
-
- runCurrent()
- verify(dreamManager).startDream()
- job.cancel()
- }
- }
-
- @Test
- fun testMonitoringUpdatesAndRestart_dreamEndsAfterDelay() =
- with(kosmos) {
- testScope.runTest {
- setActiveUser(PRIMARY_USER)
- authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
- selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
- whenever(controlsListingController.getCurrentServices())
- .thenReturn(
- listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
- )
-
- val job = launch { underTest.monitorUpdatesAndRestart() }
- val panelComponent by collectLastValue(underTest.panelComponent)
-
- assertThat(panelComponent).isEqualTo(TEST_COMPONENT)
- verify(dreamManager, never()).startDream()
-
- fakeSystemClock.advanceTime(100)
- // The package update is started.
- fakePackageChangeRepository.notifyUpdateStarted(
- TEST_PACKAGE,
- UserHandle.of(PRIMARY_USER_ID),
- )
- fakeSystemClock.advanceTime(MAX_UPDATE_CORRELATION_DELAY.inWholeMilliseconds + 100)
- // Task fragment becomes empty as a result of the update.
- underTest.onDreamEndUnexpectedly()
-
- runCurrent()
- verify(dreamManager, never()).startDream()
-
- fakeSystemClock.advanceTime(500)
- // The package update is finished.
- fakePackageChangeRepository.notifyUpdateFinished(
- TEST_PACKAGE,
- UserHandle.of(PRIMARY_USER_ID),
- )
-
- runCurrent()
- verify(dreamManager, never()).startDream()
- job.cancel()
- }
- }
-
- @Test
- fun testDreamUnexpectedlyEnds_triggersUserActivity() =
- with(kosmos) {
- testScope.runTest {
- fakeSystemClock.setUptimeMillis(100000L)
- verify(powerManager, never()).userActivity(anyLong(), anyInt(), anyInt())
-
- // Dream ends unexpectedly
- underTest.onDreamEndUnexpectedly()
-
- verify(powerManager)
- .userActivity(
- 100000L,
- PowerManager.USER_ACTIVITY_EVENT_OTHER,
- PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS
- )
- }
- }
-
private fun runServicesUpdate(hasPanelBoolean: Boolean = true) {
val listings =
listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = hasPanelBoolean))
@@ -297,7 +181,7 @@
private fun ControlsServiceInfo(
componentName: ComponentName,
label: CharSequence,
- hasPanel: Boolean
+ hasPanel: Boolean,
): ControlsServiceInfo {
val serviceInfo =
ServiceInfo().apply {
@@ -312,7 +196,7 @@
context: Context,
serviceInfo: ServiceInfo,
private val label: CharSequence,
- hasPanel: Boolean
+ hasPanel: Boolean,
) : ControlsServiceInfo(context, serviceInfo) {
init {
@@ -332,7 +216,7 @@
UserInfo(
/* id= */ PRIMARY_USER_ID,
/* name= */ "primary user",
- /* flags= */ UserInfo.FLAG_PRIMARY
+ /* flags= */ UserInfo.FLAG_PRIMARY,
)
private const val ANOTHER_USER_ID = 1
@@ -340,7 +224,7 @@
UserInfo(
/* id= */ ANOTHER_USER_ID,
/* name= */ "another user",
- /* flags= */ UserInfo.FLAG_PRIMARY
+ /* flags= */ UserInfo.FLAG_PRIMARY,
)
private const val TEST_PACKAGE = "pkg"
private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index 21679f9..2a6d29c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -427,7 +427,7 @@
@After
fun clear() {
- testScope.launch { tutorialSchedulerRepository.clearDataStore() }
+ testScope.launch { tutorialSchedulerRepository.clear() }
}
private suspend fun triggerMaxEducationSignals(gestureType: GestureType) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt
index 1d96c4d..8bb6962 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt
@@ -47,13 +47,13 @@
TutorialSchedulerRepository(
context,
testScope.backgroundScope,
- "TutorialSchedulerRepositoryTest"
+ "TutorialSchedulerRepositoryTest",
)
}
@After
fun clear() {
- testScope.launch { underTest.clearDataStore() }
+ testScope.launch { underTest.clear() }
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
index 38e4ae1..bcac086 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
@@ -26,10 +26,11 @@
import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator
import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.settings.userTracker
+import com.android.systemui.statusbar.commandline.commandRegistry
+import com.android.systemui.testKosmos
import com.android.systemui.touchpad.data.repository.FakeTouchpadRepository
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.hours
@@ -60,7 +61,7 @@
class TutorialNotificationCoordinatorTest : SysuiTestCase() {
private lateinit var underTest: TutorialNotificationCoordinator
- private val kosmos = Kosmos()
+ private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val keyboardRepository = FakeKeyboardRepository()
private val touchpadRepository = FakeTouchpadRepository()
@@ -85,6 +86,7 @@
touchpadRepository,
repository,
kosmos.inputDeviceTutorialLogger,
+ kosmos.commandRegistry,
)
underTest =
TutorialNotificationCoordinator(
@@ -100,7 +102,7 @@
@After
fun clear() {
- runBlocking { repository.clearDataStore() }
+ runBlocking { repository.clear() }
dataStoreScope.cancel()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
index b0ffc47..5df9b7b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
@@ -24,8 +24,9 @@
import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor.TutorialType
import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger
import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.commandline.commandRegistry
+import com.android.systemui.testKosmos
import com.android.systemui.touchpad.data.repository.FakeTouchpadRepository
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.hours
@@ -49,7 +50,7 @@
class TutorialSchedulerInteractorTest : SysuiTestCase() {
private lateinit var underTest: TutorialSchedulerInteractor
- private val kosmos = Kosmos()
+ private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private lateinit var dataStoreScope: CoroutineScope
private val keyboardRepository = FakeKeyboardRepository()
@@ -71,12 +72,13 @@
touchpadRepository,
schedulerRepository,
kosmos.inputDeviceTutorialLogger,
+ kosmos.commandRegistry,
)
}
@After
fun clear() {
- runBlocking { schedulerRepository.clearDataStore() }
+ runBlocking { schedulerRepository.clear() }
dataStoreScope.cancel()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index a8bb2b0..46d1ebe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.coroutines.collectValues
import com.android.systemui.dock.DockManager
import com.android.systemui.dock.DockManagerFake
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
@@ -54,6 +55,7 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.cameraLauncher
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -117,8 +119,8 @@
BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
":" +
- BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- )
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET,
+ ),
)
repository = FakeKeyguardRepository()
@@ -141,13 +143,7 @@
context = context,
userFileManager =
mock<UserFileManager>().apply {
- whenever(
- getSharedPreferences(
- anyString(),
- anyInt(),
- anyInt(),
- )
- )
+ whenever(getSharedPreferences(anyString(), anyInt(), anyInt()))
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
@@ -181,10 +177,7 @@
featureFlags = FakeFeatureFlags()
val withDeps =
- KeyguardInteractorFactory.create(
- featureFlags = featureFlags,
- repository = repository,
- )
+ KeyguardInteractorFactory.create(featureFlags = featureFlags, repository = repository)
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = withDeps.keyguardInteractor,
@@ -205,6 +198,7 @@
appContext = context,
sceneInteractor = { kosmos.sceneInteractor },
)
+ kosmos.keyguardQuickAffordanceInteractor = underTest
whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f))
}
@@ -241,9 +235,7 @@
testScope.runTest {
val configKey = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
quickAccessWallet.setState(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = ICON,
- )
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
val collectedValue =
@@ -268,9 +260,7 @@
whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
quickAccessWallet.setState(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = ICON,
- )
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
val collectedValue by
@@ -287,9 +277,7 @@
whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
quickAccessWallet.setState(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = ICON,
- )
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
val collectedValue by
@@ -305,9 +293,7 @@
testScope.runTest {
biometricSettingsRepository.setIsUserInLockdown(true)
quickAccessWallet.setState(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = ICON,
- )
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
val collectedValue by
@@ -323,9 +309,7 @@
testScope.runTest {
repository.setIsDozing(true)
homeControls.setState(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = ICON,
- )
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
val collectedValue =
@@ -340,9 +324,7 @@
testScope.runTest {
repository.setKeyguardShowing(false)
homeControls.setState(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = ICON,
- )
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
val collectedValue =
@@ -446,7 +428,7 @@
val collectedValue =
collectLastValue(
underTest.quickAffordanceAlwaysVisible(
- KeyguardQuickAffordancePosition.BOTTOM_START,
+ KeyguardQuickAffordancePosition.BOTTOM_START
)
)
assertThat(collectedValue())
@@ -487,10 +469,7 @@
@Test
fun select() =
testScope.runTest {
- overrideResource(
- R.array.config_keyguardQuickAffordanceDefaults,
- arrayOf<String>(),
- )
+ overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
@@ -530,10 +509,7 @@
activationState = ActivationState.NotSupported,
)
)
- assertThat(endConfig())
- .isEqualTo(
- KeyguardQuickAffordanceModel.Hidden,
- )
+ assertThat(endConfig()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
assertThat(underTest.getSelections())
.isEqualTo(
mapOf(
@@ -543,7 +519,7 @@
id = homeControls.key,
name = homeControls.pickerName(),
iconResourceId = homeControls.pickerIconResourceId,
- ),
+ )
),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
)
@@ -551,7 +527,7 @@
underTest.select(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- quickAccessWallet.key
+ quickAccessWallet.key,
)
assertThat(startConfig())
@@ -564,10 +540,7 @@
activationState = ActivationState.NotSupported,
)
)
- assertThat(endConfig())
- .isEqualTo(
- KeyguardQuickAffordanceModel.Hidden,
- )
+ assertThat(endConfig()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
assertThat(underTest.getSelections())
.isEqualTo(
mapOf(
@@ -577,7 +550,7 @@
id = quickAccessWallet.key,
name = quickAccessWallet.pickerName(),
iconResourceId = quickAccessWallet.pickerIconResourceId,
- ),
+ )
),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
)
@@ -614,7 +587,7 @@
id = quickAccessWallet.key,
name = quickAccessWallet.pickerName(),
iconResourceId = quickAccessWallet.pickerIconResourceId,
- ),
+ )
),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
listOf(
@@ -622,7 +595,7 @@
id = qrCodeScanner.key,
name = qrCodeScanner.pickerName(),
iconResourceId = qrCodeScanner.pickerIconResourceId,
- ),
+ )
),
)
)
@@ -653,10 +626,7 @@
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
- assertThat(startConfig())
- .isEqualTo(
- KeyguardQuickAffordanceModel.Hidden,
- )
+ assertThat(startConfig()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
assertThat(endConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
@@ -677,24 +647,18 @@
id = quickAccessWallet.key,
name = quickAccessWallet.pickerName(),
iconResourceId = quickAccessWallet.pickerIconResourceId,
- ),
+ )
),
)
)
underTest.unselect(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- quickAccessWallet.key
+ quickAccessWallet.key,
)
- assertThat(startConfig())
- .isEqualTo(
- KeyguardQuickAffordanceModel.Hidden,
- )
- assertThat(endConfig())
- .isEqualTo(
- KeyguardQuickAffordanceModel.Hidden,
- )
+ assertThat(startConfig()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ assertThat(endConfig()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
assertThat(underTest.getSelections())
.isEqualTo(
mapOf<String, List<String>>(
@@ -768,15 +732,12 @@
id = quickAccessWallet.key,
name = quickAccessWallet.pickerName(),
iconResourceId = quickAccessWallet.pickerIconResourceId,
- ),
+ )
),
)
)
- underTest.unselect(
- KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- null,
- )
+ underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, null)
assertThat(underTest.getSelections())
.isEqualTo(
@@ -787,15 +748,29 @@
)
}
+ @EnableSceneContainer
+ @Test
+ fun updatesLaunchingAffordanceFromCameraLauncher() =
+ testScope.runTest {
+ val launchingAffordance by collectLastValue(underTest.launchingAffordance)
+ runCurrent()
+
+ kosmos.cameraLauncher.setLaunchingAffordance(true)
+ runCurrent()
+ assertThat(launchingAffordance).isTrue()
+
+ kosmos.cameraLauncher.setLaunchingAffordance(false)
+ runCurrent()
+ assertThat(launchingAffordance).isFalse()
+ }
+
companion object {
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
private val ICON: Icon =
Icon.Resource(
res = CONTENT_DESCRIPTION_RESOURCE_ID,
contentDescription =
- ContentDescription.Resource(
- res = CONTENT_DESCRIPTION_RESOURCE_ID,
- ),
+ ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID),
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 12eadfc..b5e670c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -36,6 +36,7 @@
import com.android.systemui.flags.parameterizeSceneContainerFlag
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -82,6 +83,7 @@
private val communalRepository by lazy { kosmos.communalSceneRepository }
private val screenOffAnimationController by lazy { kosmos.screenOffAnimationController }
private val deviceEntryRepository by lazy { kosmos.fakeDeviceEntryRepository }
+ private val pulseExpansionInteractor by lazy { kosmos.pulseExpansionInteractor }
private val notificationsKeyguardInteractor by lazy { kosmos.notificationsKeyguardInteractor }
private val dozeParameters by lazy { kosmos.dozeParameters }
private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
@@ -188,7 +190,7 @@
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
runCurrent()
- notificationsKeyguardInteractor.setPulseExpanding(true)
+ pulseExpansionInteractor.setPulseExpanding(true)
deviceEntryRepository.setBypassEnabled(false)
runCurrent()
@@ -200,7 +202,7 @@
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
runCurrent()
- notificationsKeyguardInteractor.setPulseExpanding(false)
+ pulseExpansionInteractor.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(true)
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
runCurrent()
@@ -219,7 +221,7 @@
to = KeyguardState.DOZING,
testScope,
)
- notificationsKeyguardInteractor.setPulseExpanding(false)
+ pulseExpansionInteractor.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParameters.alwaysOn).thenReturn(false)
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
@@ -239,7 +241,7 @@
to = KeyguardState.DOZING,
testScope,
)
- notificationsKeyguardInteractor.setPulseExpanding(false)
+ pulseExpansionInteractor.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParameters.alwaysOn).thenReturn(true)
whenever(dozeParameters.displayNeedsBlanking).thenReturn(true)
@@ -260,7 +262,7 @@
to = KeyguardState.DOZING,
testScope,
)
- notificationsKeyguardInteractor.setPulseExpanding(false)
+ pulseExpansionInteractor.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParameters.alwaysOn).thenReturn(true)
whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
@@ -276,7 +278,7 @@
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
runCurrent()
- notificationsKeyguardInteractor.setPulseExpanding(false)
+ pulseExpansionInteractor.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(true)
whenever(dozeParameters.alwaysOn).thenReturn(true)
whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
@@ -298,7 +300,7 @@
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
runCurrent()
- notificationsKeyguardInteractor.setPulseExpanding(false)
+ pulseExpansionInteractor.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParameters.alwaysOn).thenReturn(true)
whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
index fd1c043..c3a777c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
import com.android.systemui.testKosmos
@@ -62,8 +63,7 @@
fun back_notEditing_hidesShade() =
testScope.runTest {
val actions by collectLastValue(underTest.actions)
- val isEditing by
- collectLastValue(kosmos.quickSettingsContainerViewModel.editModeViewModel.isEditing)
+ val isEditing by collectLastValue(kosmos.editModeViewModel.isEditing)
underTest.activateIn(this)
assertThat(isEditing).isFalse()
@@ -77,7 +77,7 @@
val actions by collectLastValue(underTest.actions)
underTest.activateIn(this)
- kosmos.quickSettingsContainerViewModel.editModeViewModel.startEditing()
+ kosmos.editModeViewModel.startEditing()
assertThat(actions?.get(Back)).isNull()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelTest.kt
deleted file mode 100644
index 32772d2..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelTest.kt
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.ui.viewmodel
-
-import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
-import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
-import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.lifecycle.activateIn
-import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.testKosmos
-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.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
-@EnableSceneContainer
-class QuickSettingsShadeUserActionsViewModelTest : SysuiTestCase() {
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
- private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
-
- private val underTest by lazy { kosmos.quickSettingsShadeUserActionsViewModel }
-
- @Test
- fun upTransitionSceneKey_deviceLocked_lockscreen() =
- testScope.runTest {
- underTest.activateIn(this)
- val actions by collectLastValue(underTest.actions)
- val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
- lockDevice()
-
- assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(actions?.get(Swipe.Down)).isNull()
- assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
- }
-
- @Test
- fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
- testScope.runTest {
- underTest.activateIn(this)
- val actions by collectLastValue(underTest.actions)
- val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
- lockDevice()
- kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
-
- assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(homeScene).isEqualTo(Scenes.Gone)
- }
-
- @Test
- fun upTransitionSceneKey_deviceUnlocked_gone() =
- testScope.runTest {
- underTest.activateIn(this)
- val actions by collectLastValue(underTest.actions)
- val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
- lockDevice()
- unlockDevice()
-
- assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(actions?.get(Swipe.Down)).isNull()
- assertThat(homeScene).isEqualTo(Scenes.Gone)
- }
-
- @Test
- fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
- testScope.runTest {
- underTest.activateIn(this)
- val actions by collectLastValue(underTest.actions)
- val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
- kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.None
- )
- sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
-
- assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
- }
-
- @Test
- fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
- testScope.runTest {
- underTest.activateIn(this)
- val actions by collectLastValue(underTest.actions)
- val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
- kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.None
- )
- runCurrent()
- sceneInteractor.changeScene(Scenes.Gone, "reason")
-
- assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(homeScene).isEqualTo(Scenes.Gone)
- }
-
- @Test
- fun backTransitionSceneKey_notEditing_Home() =
- testScope.runTest {
- underTest.activateIn(this)
- val actions by collectLastValue(underTest.actions)
-
- assertThat((actions?.get(Back) as? UserActionResult.ChangeScene)?.toScene)
- .isEqualTo(SceneFamilies.Home)
- }
-
- @Test
- fun backTransition_editing_noDestination() =
- testScope.runTest {
- underTest.activateIn(this)
- val actions by collectLastValue(underTest.actions)
- kosmos.editModeViewModel.startEditing()
-
- assertThat(actions!!).isNotEmpty()
- assertThat(actions?.get(Back)).isNull()
- }
-
- private fun TestScope.lockDevice() {
- val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
-
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
- sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
- runCurrent()
- }
-
- private fun TestScope.unlockDevice() {
- val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
-
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
- sceneInteractor.changeScene(Scenes.Gone, "reason")
- runCurrent()
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 9db2014..319f1e5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -44,6 +44,8 @@
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.ui.viewmodel.lockscreenUserActionsViewModel
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -70,7 +72,6 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -101,7 +102,6 @@
@EnableSceneContainer
class SceneFrameworkIntegrationTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
private var bouncerSceneJob: Job? = null
@Before
@@ -137,151 +137,149 @@
.isTrue()
}
- @Test
- fun startsInLockscreenScene() =
- testScope.runTest { kosmos.assertCurrentScene(Scenes.Lockscreen) }
+ @Test fun startsInLockscreenScene() = kosmos.runTest { assertCurrentScene(Scenes.Lockscreen) }
@Test
fun clickLockButtonAndEnterCorrectPin_unlocksDevice() =
- testScope.runTest {
- kosmos.emulateUserDrivenTransition(Scenes.Bouncer)
+ kosmos.runTest {
+ emulateUserDrivenTransition(Scenes.Bouncer)
- kosmos.fakeSceneDataSource.pause()
- kosmos.enterPin()
- kosmos.emulatePendingTransitionProgress(expectedVisible = false)
- kosmos.assertCurrentScene(Scenes.Gone)
+ fakeSceneDataSource.pause()
+ enterPin()
+ emulatePendingTransitionProgress(expectedVisible = false)
+ assertCurrentScene(Scenes.Gone)
}
@Test
fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
- testScope.runTest {
- val actions by collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
+ kosmos.runTest {
+ val actions by testScope.collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
- kosmos.emulateUserDrivenTransition(to = upDestinationSceneKey)
+ emulateUserDrivenTransition(to = upDestinationSceneKey)
- kosmos.fakeSceneDataSource.pause()
- kosmos.enterPin()
- kosmos.emulatePendingTransitionProgress(expectedVisible = false)
- kosmos.assertCurrentScene(Scenes.Gone)
+ fakeSceneDataSource.pause()
+ enterPin()
+ emulatePendingTransitionProgress(expectedVisible = false)
+ assertCurrentScene(Scenes.Gone)
}
@Test
fun swipeUpOnLockscreen_withAuthMethodSwipe_dismissesLockscreen() =
- testScope.runTest {
- kosmos.setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
+ kosmos.runTest {
+ setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
- val actions by collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
+ val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
- kosmos.emulateUserDrivenTransition(to = upDestinationSceneKey)
+ emulateUserDrivenTransition(to = upDestinationSceneKey)
}
@Test
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
- testScope.runTest {
- val actions by collectLastValue(kosmos.shadeUserActionsViewModel.actions)
- kosmos.setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
- kosmos.assertCurrentScene(Scenes.Lockscreen)
+ kosmos.runTest {
+ val actions by testScope.collectLastValue(shadeUserActionsViewModel.actions)
+ setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
+ assertCurrentScene(Scenes.Lockscreen)
// Emulate a user swipe to the shade scene.
- kosmos.emulateUserDrivenTransition(to = Scenes.Shade)
- kosmos.assertCurrentScene(Scenes.Shade)
+ emulateUserDrivenTransition(to = Scenes.Shade)
+ assertCurrentScene(Scenes.Shade)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Lockscreen)
- kosmos.emulateUserDrivenTransition(to = Scenes.Lockscreen)
+ emulateUserDrivenTransition(to = Scenes.Lockscreen)
}
@Test
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
- testScope.runTest {
- val actions by collectLastValue(kosmos.shadeUserActionsViewModel.actions)
- val canSwipeToEnter by collectLastValue(kosmos.deviceEntryInteractor.canSwipeToEnter)
+ kosmos.runTest {
+ val actions by testScope.collectLastValue(shadeUserActionsViewModel.actions)
+ val canSwipeToEnter by testScope.collectLastValue(deviceEntryInteractor.canSwipeToEnter)
- kosmos.setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
+ setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
assertThat(canSwipeToEnter).isTrue()
- kosmos.assertCurrentScene(Scenes.Lockscreen)
+ assertCurrentScene(Scenes.Lockscreen)
// Emulate a user swipe to dismiss the lockscreen.
- kosmos.emulateUserDrivenTransition(to = Scenes.Gone)
- kosmos.assertCurrentScene(Scenes.Gone)
+ emulateUserDrivenTransition(to = Scenes.Gone)
+ assertCurrentScene(Scenes.Gone)
// Emulate a user swipe to the shade scene.
- kosmos.emulateUserDrivenTransition(to = Scenes.Shade)
- kosmos.assertCurrentScene(Scenes.Shade)
+ emulateUserDrivenTransition(to = Scenes.Shade)
+ assertCurrentScene(Scenes.Shade)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
- kosmos.emulateUserDrivenTransition(to = Scenes.Gone)
+ emulateUserDrivenTransition(to = Scenes.Gone)
}
@Test
fun withAuthMethodNone_deviceWakeUp_skipsLockscreen() =
- testScope.runTest {
- kosmos.setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = false)
- kosmos.putDeviceToSleep()
- kosmos.assertCurrentScene(Scenes.Lockscreen)
+ kosmos.runTest {
+ setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = false)
+ putDeviceToSleep()
+ assertCurrentScene(Scenes.Lockscreen)
- kosmos.wakeUpDevice()
- kosmos.assertCurrentScene(Scenes.Gone)
+ wakeUpDevice()
+ assertCurrentScene(Scenes.Gone)
}
@Test
fun withAuthMethodSwipe_deviceWakeUp_doesNotSkipLockscreen() =
- testScope.runTest {
- kosmos.setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
- kosmos.putDeviceToSleep()
- kosmos.assertCurrentScene(Scenes.Lockscreen)
+ kosmos.runTest {
+ setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
+ putDeviceToSleep()
+ assertCurrentScene(Scenes.Lockscreen)
- kosmos.wakeUpDevice()
- kosmos.assertCurrentScene(Scenes.Lockscreen)
+ wakeUpDevice()
+ assertCurrentScene(Scenes.Lockscreen)
}
@Test
fun lockDeviceLocksDevice() =
- testScope.runTest {
- kosmos.unlockDevice()
- kosmos.assertCurrentScene(Scenes.Gone)
+ kosmos.runTest {
+ unlockDevice()
+ assertCurrentScene(Scenes.Gone)
- kosmos.lockDevice()
- kosmos.assertCurrentScene(Scenes.Lockscreen)
+ lockDevice()
+ assertCurrentScene(Scenes.Lockscreen)
}
@Test
fun deviceGoesToSleep_switchesToLockscreen() =
- testScope.runTest {
- kosmos.unlockDevice()
- kosmos.assertCurrentScene(Scenes.Gone)
+ kosmos.runTest {
+ unlockDevice()
+ assertCurrentScene(Scenes.Gone)
- kosmos.putDeviceToSleep()
- kosmos.assertCurrentScene(Scenes.Lockscreen)
+ putDeviceToSleep()
+ assertCurrentScene(Scenes.Lockscreen)
}
@Test
fun deviceGoesToSleep_wakeUp_unlock() =
- testScope.runTest {
- kosmos.unlockDevice()
- kosmos.assertCurrentScene(Scenes.Gone)
- kosmos.putDeviceToSleep()
- kosmos.assertCurrentScene(Scenes.Lockscreen)
- kosmos.wakeUpDevice()
- kosmos.assertCurrentScene(Scenes.Lockscreen)
+ kosmos.runTest {
+ unlockDevice()
+ assertCurrentScene(Scenes.Gone)
+ putDeviceToSleep()
+ assertCurrentScene(Scenes.Lockscreen)
+ wakeUpDevice()
+ assertCurrentScene(Scenes.Lockscreen)
- kosmos.unlockDevice()
- kosmos.assertCurrentScene(Scenes.Gone)
+ unlockDevice()
+ assertCurrentScene(Scenes.Gone)
}
@Test
fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
- testScope.runTest {
- kosmos.unlockDevice()
- val actions by collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
+ kosmos.runTest {
+ unlockDevice()
+ val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
@@ -289,46 +287,46 @@
@Test
fun deviceGoesToSleep_withLockTimeout_staysOnLockscreen() =
- testScope.runTest {
- kosmos.unlockDevice()
- kosmos.assertCurrentScene(Scenes.Gone)
- kosmos.putDeviceToSleep()
- kosmos.assertCurrentScene(Scenes.Lockscreen)
+ kosmos.runTest {
+ unlockDevice()
+ assertCurrentScene(Scenes.Gone)
+ putDeviceToSleep()
+ assertCurrentScene(Scenes.Lockscreen)
// Pretend like the timeout elapsed and now lock the device.
- kosmos.lockDevice()
- kosmos.assertCurrentScene(Scenes.Lockscreen)
+ lockDevice()
+ assertCurrentScene(Scenes.Lockscreen)
}
@Test
fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
- testScope.runTest {
- kosmos.setAuthMethod(AuthenticationMethodModel.Password)
- val actions by collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
+ kosmos.runTest {
+ setAuthMethod(AuthenticationMethodModel.Password)
+ val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
- kosmos.emulateUserDrivenTransition(to = upDestinationSceneKey)
+ emulateUserDrivenTransition(to = upDestinationSceneKey)
- kosmos.fakeSceneDataSource.pause()
- kosmos.dismissIme()
+ fakeSceneDataSource.pause()
+ dismissIme()
- kosmos.emulatePendingTransitionProgress()
- kosmos.assertCurrentScene(Scenes.Lockscreen)
+ emulatePendingTransitionProgress()
+ assertCurrentScene(Scenes.Lockscreen)
}
@Test
fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
- testScope.runTest {
- kosmos.setAuthMethod(AuthenticationMethodModel.Password)
- val actions by collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
+ kosmos.runTest {
+ setAuthMethod(AuthenticationMethodModel.Password)
+ val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
- kosmos.emulateUserDrivenTransition(to = upDestinationSceneKey)
+ emulateUserDrivenTransition(to = upDestinationSceneKey)
val bouncerActionButton by
- collectLastValue(kosmos.bouncerSceneContentViewModel.actionButton)
+ testScope.collectLastValue(bouncerSceneContentViewModel.actionButton)
assertWithMessage("Bouncer action button not visible")
.that(bouncerActionButton)
.isNotNull()
@@ -340,56 +338,56 @@
@Test
fun bouncerActionButtonClick_duringCall_returnsToCall() =
- testScope.runTest {
- kosmos.setAuthMethod(AuthenticationMethodModel.Password)
- kosmos.startPhoneCall()
- val actions by collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
+ kosmos.runTest {
+ setAuthMethod(AuthenticationMethodModel.Password)
+ startPhoneCall()
+ val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
- kosmos.emulateUserDrivenTransition(to = upDestinationSceneKey)
+ emulateUserDrivenTransition(to = upDestinationSceneKey)
val bouncerActionButton by
- collectLastValue(kosmos.bouncerSceneContentViewModel.actionButton)
+ testScope.collectLastValue(bouncerSceneContentViewModel.actionButton)
assertWithMessage("Bouncer action button not visible during call")
.that(bouncerActionButton)
.isNotNull()
kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!)
runCurrent()
- verify(kosmos.mockTelecomManager).showInCallScreen(any())
+ verify(mockTelecomManager).showInCallScreen(any())
}
@Test
fun showBouncer_whenLockedSimIntroduced() =
- testScope.runTest {
- kosmos.setAuthMethod(AuthenticationMethodModel.None)
- kosmos.introduceLockedSim()
- kosmos.assertCurrentScene(Scenes.Bouncer)
+ kosmos.runTest {
+ setAuthMethod(AuthenticationMethodModel.None)
+ introduceLockedSim()
+ assertCurrentScene(Scenes.Bouncer)
}
@Test
fun goesToGone_whenSimUnlocked_whileDeviceUnlocked() =
- testScope.runTest {
- kosmos.fakeSceneDataSource.pause()
- kosmos.introduceLockedSim()
- kosmos.emulatePendingTransitionProgress(expectedVisible = true)
- kosmos.enterSimPin(
+ kosmos.runTest {
+ fakeSceneDataSource.pause()
+ introduceLockedSim()
+ emulatePendingTransitionProgress(expectedVisible = true)
+ enterSimPin(
authMethodAfterSimUnlock = AuthenticationMethodModel.None,
enableLockscreen = false,
)
- kosmos.assertCurrentScene(Scenes.Gone)
+ assertCurrentScene(Scenes.Gone)
}
@Test
fun showLockscreen_whenSimUnlocked_whileDeviceLocked() =
- testScope.runTest {
- kosmos.fakeSceneDataSource.pause()
- kosmos.introduceLockedSim()
- kosmos.emulatePendingTransitionProgress(expectedVisible = true)
- kosmos.enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.Pin)
- kosmos.assertCurrentScene(Scenes.Lockscreen)
+ kosmos.runTest {
+ fakeSceneDataSource.pause()
+ introduceLockedSim()
+ emulatePendingTransitionProgress(expectedVisible = true)
+ enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.Pin)
+ assertCurrentScene(Scenes.Lockscreen)
}
/**
@@ -457,10 +455,10 @@
*/
private fun Kosmos.emulatePendingTransitionProgress(expectedVisible: Boolean = true) {
assertWithMessage("The FakeSceneDataSource has to be paused for this to do anything.")
- .that(kosmos.fakeSceneDataSource.isPaused)
+ .that(fakeSceneDataSource.isPaused)
.isTrue()
- val to = kosmos.fakeSceneDataSource.pendingScene ?: return
+ val to = fakeSceneDataSource.pendingScene ?: return
val from = getCurrentSceneInUi()
if (to == from) {
@@ -489,7 +487,7 @@
// End the transition and report the change.
transitionState.value = ObservableTransitionState.Idle(to)
- kosmos.fakeSceneDataSource.unpause(force = true)
+ fakeSceneDataSource.unpause(force = true)
testScope.runCurrent()
assertWithMessage("Visibility mismatch after scene transition from $from to $to!")
@@ -523,7 +521,7 @@
private fun Kosmos.emulateUserDrivenTransition(to: SceneKey?) {
checkNotNull(to)
- kosmos.fakeSceneDataSource.pause()
+ fakeSceneDataSource.pause()
sceneInteractor.changeScene(to, "reason")
emulatePendingTransitionProgress(expectedVisible = to != Scenes.Gone)
@@ -634,7 +632,7 @@
}
/** Changes device wakefulness state from awake to asleep, going through intermediary states. */
- private suspend fun Kosmos.putDeviceToSleep() {
+ private fun Kosmos.putDeviceToSleep() {
val wakefulnessModel = powerInteractor.detailedWakefulness.value
assertWithMessage("Cannot put device to sleep as it's already asleep!")
.that(wakefulnessModel.isAwake())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 4995920..2c8f7cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -32,6 +32,7 @@
import com.android.internal.logging.uiEventLoggerFake
import com.android.internal.policy.IKeyguardDismissCallback
import com.android.keyguard.AuthInteractionProperties
+import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
@@ -71,8 +72,6 @@
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.scenetransition.lockscreenSceneTransitionInteractor
-import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
@@ -128,6 +127,7 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -159,7 +159,8 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
+ whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(true)
underTest = kosmos.sceneContainerStartable
}
@@ -405,10 +406,7 @@
assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer)
underTest.start()
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
-
+ updateFingerprintAuthStatus(isSuccess = true)
assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@@ -430,10 +428,7 @@
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
underTest.start()
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
-
+ updateFingerprintAuthStatus(isSuccess = true)
assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
assertThat(alternateBouncerVisible).isFalse()
}
@@ -464,9 +459,7 @@
runCurrent()
assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
+ updateFingerprintAuthStatus(isSuccess = true)
runCurrent()
assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
@@ -501,10 +494,7 @@
assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer)
assertThat(backStack?.asIterable()?.last()).isEqualTo(Scenes.Lockscreen)
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
-
+ updateFingerprintAuthStatus(isSuccess = true)
assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
assertThat(backStack?.asIterable()?.last()).isEqualTo(Scenes.Gone)
}
@@ -535,10 +525,7 @@
runCurrent()
assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer)
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
-
+ updateFingerprintAuthStatus(isSuccess = true)
assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@@ -554,10 +541,7 @@
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
underTest.start()
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
-
+ updateFingerprintAuthStatus(isSuccess = true)
assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@@ -592,9 +576,7 @@
transitionStateFlowValue.value = ObservableTransitionState.Idle(Scenes.Shade)
assertThat(currentSceneKey).isEqualTo(Scenes.Shade)
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
+ updateFingerprintAuthStatus(isSuccess = true)
runCurrent()
assertThat(currentSceneKey).isEqualTo(Scenes.Shade)
@@ -786,6 +768,7 @@
@DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playSuccessHaptics_onSuccessfulLockscreenAuth_udfps() =
testScope.runTest {
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
@@ -795,24 +778,19 @@
assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
underTest.start()
- unlockWithFingerprintAuth()
+ // unlock with fingerprint
+ updateFingerprintAuthStatus(isSuccess = true)
assertThat(playSuccessHaptic).isNotNull()
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
- verify(vibratorHelper)
- .vibrateAuthSuccess(
- "SceneContainerStartable, $currentSceneKey device-entry::success"
- )
+ verify(vibratorHelper).vibrateAuthSuccess(anyString())
verify(vibratorHelper, never()).vibrateAuthError(anyString())
-
- updateFingerprintAuthStatus(isSuccess = true)
- assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@Test
@EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playSuccessMSDLHaptics_onSuccessfulLockscreenAuth_udfps() =
testScope.runTest {
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
@@ -822,21 +800,19 @@
assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
underTest.start()
- unlockWithFingerprintAuth()
+ // unlock with fingerprint
+ updateFingerprintAuthStatus(isSuccess = true)
assertThat(playSuccessHaptic).isNotNull()
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK)
assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
-
- updateFingerprintAuthStatus(isSuccess = true)
- assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@Test
@DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playSuccessHaptics_onSuccessfulLockscreenAuth_sfps() =
testScope.runTest {
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
@@ -847,24 +823,19 @@
underTest.start()
allowHapticsOnSfps()
- unlockWithFingerprintAuth()
+ // unlock with fingerprint
+ updateFingerprintAuthStatus(isSuccess = true)
assertThat(playSuccessHaptic).isNotNull()
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
- verify(vibratorHelper)
- .vibrateAuthSuccess(
- "SceneContainerStartable, $currentSceneKey device-entry::success"
- )
+ verify(vibratorHelper).vibrateAuthSuccess(anyString())
verify(vibratorHelper, never()).vibrateAuthError(anyString())
-
- updateFingerprintAuthStatus(isSuccess = true)
- assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@Test
@EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playSuccessMSDLHaptics_onSuccessfulLockscreenAuth_sfps() =
testScope.runTest {
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
@@ -875,15 +846,12 @@
underTest.start()
allowHapticsOnSfps()
- unlockWithFingerprintAuth()
+ // unlock with fingerprint
+ updateFingerprintAuthStatus(isSuccess = true)
assertThat(playSuccessHaptic).isNotNull()
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK)
assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties)
-
- updateFingerprintAuthStatus(isSuccess = true)
- assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@Test
@@ -902,8 +870,7 @@
assertThat(playErrorHaptic).isNotNull()
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
- verify(vibratorHelper)
- .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error")
+ verify(vibratorHelper).vibrateAuthError(anyString())
verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
}
@@ -943,8 +910,7 @@
assertThat(playErrorHaptic).isNotNull()
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
- verify(vibratorHelper)
- .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error")
+ verify(vibratorHelper).vibrateAuthError(anyString())
verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
}
@@ -972,6 +938,7 @@
@DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun skipsSuccessHaptics_whenPowerButtonDown_sfps() =
testScope.runTest {
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
@@ -982,24 +949,19 @@
underTest.start()
allowHapticsOnSfps(isPowerButtonDown = true)
- unlockWithFingerprintAuth()
+ // unlock with fingerprint
+ updateFingerprintAuthStatus(isSuccess = true)
assertThat(playSuccessHaptic).isNull()
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
- verify(vibratorHelper, never())
- .vibrateAuthSuccess(
- "SceneContainerStartable, $currentSceneKey device-entry::success"
- )
+ verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
verify(vibratorHelper, never()).vibrateAuthError(anyString())
-
- updateFingerprintAuthStatus(isSuccess = true)
- assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@Test
@EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun skipsMSDLSuccessHaptics_whenPowerButtonDown_sfps() =
testScope.runTest {
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
@@ -1010,21 +972,19 @@
underTest.start()
allowHapticsOnSfps(isPowerButtonDown = true)
- unlockWithFingerprintAuth()
+ // unlock with fingerprint
+ updateFingerprintAuthStatus(isSuccess = true)
assertThat(playSuccessHaptic).isNull()
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
assertThat(msdlPlayer.latestTokenPlayed).isNull()
assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
-
- updateFingerprintAuthStatus(isSuccess = true)
- assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@Test
@DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun skipsSuccessHaptics_whenPowerButtonRecentlyPressed_sfps() =
testScope.runTest {
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
@@ -1035,24 +995,19 @@
underTest.start()
allowHapticsOnSfps(lastPowerPress = 50)
- unlockWithFingerprintAuth()
+ // unlock with fingerprint
+ updateFingerprintAuthStatus(isSuccess = true)
assertThat(playSuccessHaptic).isNull()
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
- verify(vibratorHelper, never())
- .vibrateAuthSuccess(
- "SceneContainerStartable, $currentSceneKey device-entry::success"
- )
+ verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
verify(vibratorHelper, never()).vibrateAuthError(anyString())
-
- updateFingerprintAuthStatus(isSuccess = true)
- assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@Test
@EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun skipsMSDLSuccessHaptics_whenPowerButtonRecentlyPressed_sfps() =
testScope.runTest {
+ whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true)
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
val playSuccessHaptic by
collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
@@ -1063,15 +1018,12 @@
underTest.start()
allowHapticsOnSfps(lastPowerPress = 50)
- unlockWithFingerprintAuth()
+ // unlock with fingerprint
+ updateFingerprintAuthStatus(isSuccess = true)
assertThat(playSuccessHaptic).isNull()
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
assertThat(msdlPlayer.latestTokenPlayed).isNull()
assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
-
- updateFingerprintAuthStatus(isSuccess = true)
- assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@Test
@@ -1090,9 +1042,7 @@
updateFingerprintAuthStatus(isSuccess = false)
assertThat(playErrorHaptic).isNull()
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
- verify(vibratorHelper, never())
- .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error")
+ verify(vibratorHelper, never()).vibrateAuthError(anyString())
verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
}
@@ -1112,7 +1062,6 @@
updateFingerprintAuthStatus(isSuccess = false)
assertThat(playErrorHaptic).isNull()
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
assertThat(msdlPlayer.latestTokenPlayed).isNull()
assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
}
@@ -1132,9 +1081,7 @@
updateFaceAuthStatus(isSuccess = false)
assertThat(playErrorHaptic).isNull()
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
- verify(vibratorHelper, never())
- .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error")
+ verify(vibratorHelper, never()).vibrateAuthError(anyString())
verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
}
@@ -1153,7 +1100,6 @@
updateFaceAuthStatus(isSuccess = false)
assertThat(playErrorHaptic).isNull()
- assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
assertThat(msdlPlayer.latestTokenPlayed).isNull()
assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
}
@@ -1176,9 +1122,7 @@
)
.forEachIndexed { index, sceneKey ->
if (sceneKey == Scenes.Gone) {
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
+ updateFingerprintAuthStatus(isSuccess = true)
runCurrent()
}
fakeSceneDataSource.pause()
@@ -1334,9 +1278,7 @@
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
underTest.start()
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
+ updateFingerprintAuthStatus(isSuccess = true)
runCurrent()
powerInteractor.setAwakeForTest()
runCurrent()
@@ -1586,9 +1528,7 @@
runCurrent()
verify(falsingCollector).onBouncerShown()
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
+ updateFingerprintAuthStatus(isSuccess = true)
runCurrent()
sceneInteractor.changeScene(Scenes.Gone, "reason")
runCurrent()
@@ -2350,9 +2290,7 @@
val dismissCallback: IKeyguardDismissCallback = mock()
kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
+ updateFingerprintAuthStatus(isSuccess = true)
runCurrent()
kosmos.fakeExecutor.runAllReady()
@@ -2641,13 +2579,6 @@
runCurrent()
}
- private fun unlockWithFingerprintAuth() {
- kosmos.fakeKeyguardRepository.setBiometricUnlockSource(
- BiometricUnlockSource.FINGERPRINT_SENSOR
- )
- kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.UNLOCK_COLLAPSING)
- }
-
private fun TestScope.setupBiometricAuth(
hasSfps: Boolean = false,
hasUdfps: Boolean = false,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 89ad699..01c17bd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -128,6 +128,7 @@
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
import com.android.systemui.shade.data.repository.ShadeRepository;
@@ -211,6 +212,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.stubbing.Answer;
@@ -373,6 +375,9 @@
protected ShadeRepository mShadeRepository;
protected FakeMSDLPlayer mMSDLPlayer = mKosmos.getMsdlPlayer();
+ protected BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
+ mKosmos.getBrightnessMirrorShowingInteractor();
+
protected final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
protected final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
protected final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
@@ -511,7 +516,8 @@
when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
when(mView.findViewById(R.id.keyguard_status_view))
.thenReturn(mock(KeyguardStatusView.class));
- View rootView = mock(View.class);
+ ViewGroup rootView = mock(ViewGroup.class);
+ when(rootView.isVisibleToUser()).thenReturn(true);
when(mView.getRootView()).thenReturn(rootView);
when(rootView.findViewById(R.id.keyguard_status_view))
.thenReturn(mock(KeyguardStatusView.class));
@@ -613,7 +619,8 @@
mScreenOffAnimationController,
new NotificationWakeUpCoordinatorLogger(logcatLogBuffer()),
notifsKeyguardInteractor,
- mKosmos.getCommunalInteractor());
+ mKosmos.getCommunalInteractor(),
+ mKosmos.getPulseExpansionInteractor());
mConfigurationController = new ConfigurationControllerImpl(mContext);
PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
mContext,
@@ -648,12 +655,21 @@
((Runnable) invocation.getArgument(0)).run();
return null;
}).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
+ when(mNotificationShadeWindowController.getWindowRootView()).thenReturn(rootView);
doAnswer(invocation -> {
mLayoutChangeListener = invocation.getArgument(0);
return null;
}).when(mView).addOnLayoutChangeListener(any());
when(mView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ ViewTreeObserver.OnGlobalLayoutListener gll = invocation.getArgument(0);
+ gll.onGlobalLayout();
+ return null;
+ }
+ }).when(mViewTreeObserver).addOnGlobalLayoutListener(any());
when(mView.getParent()).thenReturn(mViewParent);
when(mQs.getHeader()).thenReturn(mQsHeader);
when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN);
@@ -752,7 +768,8 @@
mPowerInteractor,
mKeyguardClockPositionAlgorithm,
mNaturalScrollingSettingObserver,
- mMSDLPlayer);
+ mMSDLPlayer,
+ mBrightnessMirrorShowingInteractor);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
null,
@@ -906,7 +923,7 @@
}
protected boolean onTouchEvent(MotionEvent ev) {
- return mTouchHandler.onTouch(mView, ev);
+ return mNotificationPanelViewController.handleExternalTouch(ev);
}
protected void setDozing(boolean dozing, boolean dozingAlwaysOn) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index ec75972..550fcf7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -364,6 +364,64 @@
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_SHADE_EXPANDS_ON_STATUS_BAR_LONG_PRESS)
+ public void onStatusBarLongPress_shadeExpands() {
+ long downTime = 42L;
+ // Start touch session with down event
+ onTouchEvent(MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, 1f, 1f, 0));
+ // Status bar triggers long press expand
+ mNotificationPanelViewController.onStatusBarLongPress(
+ MotionEvent.obtain(downTime, downTime + 27L, MotionEvent.ACTION_MOVE, 1f, 1f, 0));
+ assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
+ // Shade ignores the rest of the long press's touch session
+ assertThat(onTouchEvent(
+ MotionEvent.obtain(downTime, downTime + 42L, MotionEvent.ACTION_MOVE, 1f, 1f,
+ 0))).isFalse();
+
+ // Start new touch session
+ long downTime2 = downTime + 100L;
+ assertThat(onTouchEvent(
+ MotionEvent.obtain(downTime2, downTime2, MotionEvent.ACTION_DOWN, 1f, 1f,
+ 0))).isTrue();
+ // Shade no longer ignoring touches
+ assertThat(onTouchEvent(
+ MotionEvent.obtain(downTime2, downTime2 + 2L, MotionEvent.ACTION_MOVE, 1f, 1f,
+ 0))).isTrue();
+ }
+
+ @Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_SHADE_EXPANDS_ON_STATUS_BAR_LONG_PRESS)
+ public void onStatusBarLongPress_qsExpands() {
+ long downTime = 42L;
+ // Start with shade already expanded
+ mNotificationPanelViewController.setExpandedFraction(1F);
+
+ // Start touch session with down event
+ onTouchEvent(MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, 1f, 1f, 0));
+ // Status bar triggers long press expand
+ mNotificationPanelViewController.onStatusBarLongPress(
+ MotionEvent.obtain(downTime, downTime + 27L, MotionEvent.ACTION_MOVE, 1f, 1f, 0));
+ assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
+ // Shade expands to QS
+ verify(mQsController, atLeastOnce()).flingQs(0F, ShadeViewController.FLING_EXPAND);
+ // Shade ignores the rest of the long press's touch session
+ assertThat(onTouchEvent(
+ MotionEvent.obtain(downTime, downTime + 42L, MotionEvent.ACTION_MOVE, 1f, 1f,
+ 0))).isFalse();
+
+ // Start new touch session
+ long downTime2 = downTime + 100L;
+ assertThat(onTouchEvent(
+ MotionEvent.obtain(downTime2, downTime2, MotionEvent.ACTION_DOWN, 1f, 1f,
+ 0))).isTrue();
+ // Shade no longer ignoring touches
+ assertThat(onTouchEvent(
+ MotionEvent.obtain(downTime2, downTime2 + 2L, MotionEvent.ACTION_MOVE, 1f, 1f,
+ 0))).isTrue();
+
+ }
+
+ @Test
@DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
public void test_pulsing_onTouchEvent_noTracking() {
// GIVEN device is pulsing
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 47eebf6..59d0d70 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -51,7 +51,12 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.DragDownHelper
@@ -70,6 +75,7 @@
import com.android.systemui.statusbar.phone.DozeServiceHost
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
+import com.android.systemui.testKosmos
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -78,11 +84,15 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@@ -98,6 +108,7 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.clearInvocations
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -107,6 +118,8 @@
@RunWithLooper(setAsMainLooper = true)
class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : SysuiTestCase() {
+ val kosmos = testKosmos()
+
@Mock private lateinit var view: NotificationShadeWindowView
@Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var centralSurfaces: CentralSurfaces
@@ -148,6 +161,10 @@
private val notificationLaunchAnimationInteractor =
NotificationLaunchAnimationInteractor(notificationLaunchAnimationRepository)
+ private val brightnessMirrorShowingRepository = BrightnessMirrorShowingRepository()
+ private val brightnessMirrorShowingInteractor =
+ BrightnessMirrorShowingInteractor(brightnessMirrorShowingRepository)
+
private lateinit var falsingCollector: FalsingCollectorFake
private lateinit var fakeClock: FakeSystemClock
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
@@ -181,8 +198,9 @@
featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
- testScope = TestScope()
+ testScope = kosmos.testScope
testableLooper = TestableLooper.get(this)
+
falsingCollector = FalsingCollectorFake()
fakeClock = FakeSystemClock()
underTest =
@@ -221,6 +239,7 @@
alternateBouncerInteractor,
mock(BouncerViewBinder::class.java),
mock(ConfigurationForwarder::class.java),
+ brightnessMirrorShowingInteractor,
)
underTest.setupExpandedStatusBar()
underTest.setDragDownHelper(dragDownHelper)
@@ -597,6 +616,39 @@
verify(dragDownHelper).stopDragging()
}
+ @Test
+ @EnableFlags(QSComposeFragment.FLAG_NAME)
+ fun mirrorShowing_depthControllerSet() =
+ testScope.runTest {
+ try {
+ Dispatchers.setMain(kosmos.testDispatcher)
+
+ // Simulate attaching the view so flow collection starts.
+ whenever(view.viewTreeObserver).thenReturn(mock(ViewTreeObserver::class.java))
+ val onAttachStateChangeListenerArgumentCaptor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
+ verify(view, atLeast(1))
+ .addOnAttachStateChangeListener(
+ onAttachStateChangeListenerArgumentCaptor.capture()
+ )
+ for (listener in onAttachStateChangeListenerArgumentCaptor.allValues) {
+ listener.onViewAttachedToWindow(view)
+ }
+ testableLooper.processAllMessages()
+ clearInvocations(notificationShadeDepthController)
+
+ brightnessMirrorShowingInteractor.setMirrorShowing(true)
+ runCurrent()
+ verify(notificationShadeDepthController).brightnessMirrorVisible = true
+
+ brightnessMirrorShowingInteractor.setMirrorShowing(false)
+ runCurrent()
+ verify(notificationShadeDepthController).brightnessMirrorVisible = false
+ } finally {
+ Dispatchers.resetMain()
+ }
+ }
+
companion object {
private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 1c196c0..9b91fc7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -42,6 +42,8 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.DragDownHelper
@@ -106,7 +108,7 @@
@Mock private lateinit var quickSettingsController: QuickSettingsController
@Mock
private lateinit var notificationStackScrollLayoutController:
- NotificationStackScrollLayoutController
+ NotificationStackScrollLayoutController
@Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
@Mock
private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
@@ -122,7 +124,7 @@
private lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@Mock
private lateinit var unfoldTransitionProgressProvider:
- Optional<UnfoldTransitionProgressProvider>
+ Optional<UnfoldTransitionProgressProvider>
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
@Mock private lateinit var mGlanceableHubContainerController: GlanceableHubContainerController
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@@ -132,6 +134,10 @@
@Captor
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
+ private val brightnessMirrorShowingRepository = BrightnessMirrorShowingRepository()
+ private val brightnessMirrorShowingInteractor =
+ BrightnessMirrorShowingInteractor(brightnessMirrorShowingRepository)
+
private lateinit var underTest: NotificationShadeWindowView
private lateinit var controller: NotificationShadeWindowViewController
private lateinit var interactionEventHandler: InteractionEventHandler
@@ -142,10 +148,10 @@
MockitoAnnotations.initMocks(this)
underTest = spy(NotificationShadeWindowView(context, null))
whenever(
- underTest.findViewById<NotificationStackScrollLayout>(
- R.id.notification_stack_scroller
+ underTest.findViewById<NotificationStackScrollLayout>(
+ R.id.notification_stack_scroller
+ )
)
- )
.thenReturn(notificationStackScrollLayout)
whenever(underTest.findViewById<FrameLayout>(R.id.keyguard_bouncer_container))
.thenReturn(mock())
@@ -198,6 +204,7 @@
alternateBouncerInteractor,
mock(),
configurationForwarder,
+ brightnessMirrorShowingInteractor,
)
controller.setupExpandedStatusBar()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt
index 16da3d2..4795a12 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt
@@ -29,6 +29,10 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.android.systemui.statusbar.FakeStatusBarStateController
+import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomLeft
+import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomRight
+import com.android.systemui.statusbar.events.PrivacyDotCorner.TopLeft
+import com.android.systemui.statusbar.events.PrivacyDotCorner.TopRight
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -71,15 +75,15 @@
private fun createController() =
PrivacyDotViewControllerImpl(
- executor,
- testScope.backgroundScope,
- statusBarStateController,
- configurationController,
- contentInsetsProvider,
- animationScheduler = mock<SystemStatusAnimationScheduler>(),
- shadeInteractor = null,
- )
- .also { it.setUiExecutor(executor) }
+ executor,
+ testScope.backgroundScope,
+ statusBarStateController,
+ configurationController,
+ contentInsetsProvider,
+ animationScheduler = mock<SystemStatusAnimationScheduler>(),
+ shadeInteractor = null,
+ uiExecutor = executor,
+ )
@Test
fun topMargin_topLeftView_basedOnSeascapeArea() {
@@ -215,7 +219,7 @@
val controller = createAndInitializeController()
- assertThat(controller.currentViewState.cornerIndex).isEqualTo(TOP_RIGHT)
+ assertThat(controller.currentViewState.corner).isEqualTo(TopRight)
assertThat(controller.currentViewState.designatedCorner).isEqualTo(topRightView)
}
@@ -225,7 +229,7 @@
val controller = createAndInitializeController()
- assertThat(controller.currentViewState.cornerIndex).isEqualTo(BOTTOM_RIGHT)
+ assertThat(controller.currentViewState.corner).isEqualTo(BottomRight)
assertThat(controller.currentViewState.designatedCorner).isEqualTo(bottomRightView)
}
@@ -235,7 +239,7 @@
val controller = createAndInitializeController()
- assertThat(controller.currentViewState.cornerIndex).isEqualTo(TOP_LEFT)
+ assertThat(controller.currentViewState.corner).isEqualTo(TopLeft)
assertThat(controller.currentViewState.designatedCorner).isEqualTo(topLeftView)
}
@@ -245,7 +249,7 @@
val controller = createAndInitializeController()
- assertThat(controller.currentViewState.cornerIndex).isEqualTo(BOTTOM_LEFT)
+ assertThat(controller.currentViewState.corner).isEqualTo(BottomLeft)
assertThat(controller.currentViewState.designatedCorner).isEqualTo(bottomLeftView)
}
@@ -256,7 +260,7 @@
enableRtl()
val controller = createAndInitializeController()
- assertThat(controller.currentViewState.cornerIndex).isEqualTo(TOP_LEFT)
+ assertThat(controller.currentViewState.corner).isEqualTo(TopLeft)
assertThat(controller.currentViewState.designatedCorner).isEqualTo(topLeftView)
}
@@ -267,7 +271,7 @@
enableRtl()
val controller = createAndInitializeController()
- assertThat(controller.currentViewState.cornerIndex).isEqualTo(TOP_RIGHT)
+ assertThat(controller.currentViewState.corner).isEqualTo(TopRight)
assertThat(controller.currentViewState.designatedCorner).isEqualTo(topRightView)
}
@@ -278,7 +282,7 @@
enableRtl()
val controller = createAndInitializeController()
- assertThat(controller.currentViewState.cornerIndex).isEqualTo(BOTTOM_LEFT)
+ assertThat(controller.currentViewState.corner).isEqualTo(BottomLeft)
assertThat(controller.currentViewState.designatedCorner).isEqualTo(bottomLeftView)
}
@@ -289,7 +293,7 @@
enableRtl()
val controller = createAndInitializeController()
- assertThat(controller.currentViewState.cornerIndex).isEqualTo(BOTTOM_RIGHT)
+ assertThat(controller.currentViewState.corner).isEqualTo(BottomRight)
assertThat(controller.currentViewState.designatedCorner).isEqualTo(bottomRightView)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index 9f752a8..3b5d358 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
@@ -86,6 +87,7 @@
private var bypassEnabled: Boolean = false
private var statusBarState: Int = StatusBarState.KEYGUARD
+
private fun eased(dozeAmount: Float) =
notificationWakeUpCoordinator.dozeAmountInterpolator.getInterpolation(dozeAmount)
@@ -119,6 +121,7 @@
logger,
kosmos.notificationsKeyguardInteractor,
kosmos.communalInteractor,
+ kosmos.pulseExpansionInteractor,
)
statusBarStateCallback = withArgCaptor {
verify(statusBarStateController).addCallback(capture())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
index 133a114..dcc8ecd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
@@ -58,19 +58,4 @@
assertThat(notifsFullyHidden).isTrue()
}
-
- @Test
- fun isPulseExpanding_reflectsRepository() =
- testComponent.runTest {
- underTest.setPulseExpanding(false)
- val isPulseExpanding by collectLastValue(underTest.isPulseExpanding)
- runCurrent()
-
- assertThat(isPulseExpanding).isFalse()
-
- underTest.setPulseExpanding(true)
- runCurrent()
-
- assertThat(isPulseExpanding).isTrue()
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index a21ca94..c9ca67e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -45,23 +45,25 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
+import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
+
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
-import java.util.List;
-
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
public class FooterViewTest extends SysuiTestCase {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getFlags() {
- return FlagsParameterization.allCombinationsOf(FooterViewRefactor.FLAG_NAME);
+ return FlagsParameterization.progressionOf(FooterViewRefactor.FLAG_NAME,
+ NotifRedesignFooter.FLAG_NAME);
}
public FooterViewTest(FlagsParameterization flags) {
@@ -74,8 +76,13 @@
@Before
public void setUp() {
- mView = (FooterView) LayoutInflater.from(mSpyContext).inflate(
- R.layout.status_bar_notification_footer, null, false);
+ if (NotifRedesignFooter.isEnabled()) {
+ mView = (FooterView) LayoutInflater.from(mSpyContext).inflate(
+ R.layout.status_bar_notification_footer_redesign, null, false);
+ } else {
+ mView = (FooterView) LayoutInflater.from(mSpyContext).inflate(
+ R.layout.status_bar_notification_footer, null, false);
+ }
mView.setAnimationDuration(0);
}
@@ -92,13 +99,14 @@
}
@Test
+ @DisableFlags(NotifRedesignFooter.FLAG_NAME)
public void setManageOnClick() {
mView.setManageButtonClickListener(mock(View.OnClickListener.class));
assertTrue(mView.findViewById(R.id.manage_text).hasOnClickListeners());
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void setHistoryShown() {
mView.showHistory(true);
assertTrue(mView.isHistoryShown());
@@ -107,7 +115,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void setHistoryNotShown() {
mView.showHistory(false);
assertFalse(mView.isHistoryShown());
@@ -133,6 +141,7 @@
@Test
@EnableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags(NotifRedesignFooter.FLAG_NAME)
public void testSetManageOrHistoryButtonText_resourceOnlyFetchedOnce() {
int resId = R.string.manage_notifications_history_text;
mView.setManageOrHistoryButtonText(resId);
@@ -151,7 +160,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void testSetManageOrHistoryButtonText_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.manage_notifications_history_text;
@@ -161,6 +170,7 @@
@Test
@EnableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags(NotifRedesignFooter.FLAG_NAME)
public void testSetManageOrHistoryButtonDescription_resourceOnlyFetchedOnce() {
int resId = R.string.manage_notifications_history_text;
mView.setManageOrHistoryButtonDescription(resId);
@@ -179,7 +189,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void testSetManageOrHistoryButtonDescription_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.accessibility_clear_all;
@@ -207,7 +217,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void testSetClearAllButtonText_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.clear_all_notifications_text;
@@ -235,7 +245,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void testSetClearAllButtonDescription_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.accessibility_clear_all;
@@ -263,7 +273,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void testSetMessageString_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.string.unlock_to_see_notif_text;
@@ -288,7 +298,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void testSetMessageIcon_expectsFlagEnabled() {
clearInvocations(mSpyContext);
int resId = R.drawable.ic_friction_lock_closed;
@@ -310,4 +320,3 @@
.isEqualTo(View.GONE);
}
}
-
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 2349c25..07935e4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -16,10 +16,12 @@
package com.android.systemui.statusbar.notification.stack
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -30,12 +32,14 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
private const val MAX_PULSE_HEIGHT = 100000f
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@SmallTest
-class AmbientStateTest : SysuiTestCase() {
+class AmbientStateTest(flags: FlagsParameterization) : SysuiTestCase() {
private val dumpManager = mock<DumpManager>()
private val sectionProvider = StackScrollAlgorithm.SectionProvider { _, _ -> false }
@@ -46,6 +50,18 @@
private lateinit var sut: AmbientState
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
@Before
fun setUp() {
sut =
@@ -56,7 +72,7 @@
bypassController,
statusBarKeyguardViewManager,
largeScreenShadeInterpolator,
- avalancheController
+ avalancheController,
)
}
@@ -97,6 +113,7 @@
assertThat(sut.pulseHeight).isEqualTo(expected)
}
+
// endregion
// region statusBarState
@@ -119,6 +136,7 @@
assertThat(sut.isFlingRequiredAfterLockScreenSwipeUp).isTrue()
}
+
// endregion
// region hideAmount
@@ -141,6 +159,7 @@
assertThat(sut.pulseHeight).isEqualTo(1f)
}
+
// endregion
// region dozeAmount
@@ -173,6 +192,7 @@
assertThat(sut.pulseHeight).isEqualTo(1f)
}
+
// endregion
// region trackedHeadsUpRow
@@ -189,10 +209,12 @@
assertThat(sut.trackedHeadsUpRow).isNull()
}
+
// endregion
// region isSwipingUp
@Test
+ @DisableSceneContainer
fun isSwipingUp_whenValueChangedToTrue_shouldRequireFling() {
sut.isSwipingUp = false
sut.isFlingRequiredAfterLockScreenSwipeUp = false
@@ -203,6 +225,7 @@
}
@Test
+ @DisableSceneContainer
fun isSwipingUp_whenValueChangedToFalse_shouldRequireFling() {
sut.isSwipingUp = true
sut.isFlingRequiredAfterLockScreenSwipeUp = false
@@ -211,10 +234,12 @@
assertThat(sut.isFlingRequiredAfterLockScreenSwipeUp).isTrue()
}
+
// endregion
// region isFlinging
@Test
+ @DisableSceneContainer
fun isFlinging_shouldNotNeedFling() {
sut.arrangeFlinging(true)
@@ -224,6 +249,7 @@
}
@Test
+ @DisableSceneContainer
fun isFlinging_whenNotOnLockScreen_shouldDoNothing() {
sut.arrangeFlinging(true)
sut.setStatusBarState(StatusBarState.SHADE)
@@ -235,6 +261,7 @@
}
@Test
+ @DisableSceneContainer
fun isFlinging_whenValueChangedToTrue_shouldDoNothing() {
sut.arrangeFlinging(false)
@@ -242,10 +269,12 @@
assertThat(sut.isFlingRequiredAfterLockScreenSwipeUp).isTrue()
}
+
// endregion
// region scrollY
@Test
+ @DisableSceneContainer
fun scrollY_shouldSetValueGreaterThanZero() {
sut.scrollY = 0
@@ -255,6 +284,7 @@
}
@Test
+ @DisableSceneContainer
fun scrollY_shouldNotSetValueLessThanZero() {
sut.scrollY = 0
@@ -262,21 +292,24 @@
assertThat(sut.scrollY).isEqualTo(0)
}
+
// endregion
// region setOverScrollAmount
+ @Test
+ @DisableSceneContainer
fun setOverScrollAmount_shouldSetValueOnTop() {
- sut.setOverScrollAmount(/* amount = */ 10f, /* onTop = */ true)
+ sut.setOverScrollAmount(/* amount= */ 10f, /* onTop= */ true)
- val resultOnTop = sut.getOverScrollAmount(/* top = */ true)
- val resultOnBottom = sut.getOverScrollAmount(/* top = */ false)
+ val resultOnTop = sut.getOverScrollAmount(/* top= */ true)
+ val resultOnBottom = sut.getOverScrollAmount(/* top= */ false)
assertThat(resultOnTop).isEqualTo(10f)
assertThat(resultOnBottom).isEqualTo(0f)
}
fun setOverScrollAmount_shouldSetValueOnBottom() {
- sut.setOverScrollAmount(/* amount = */ 10f, /* onTop = */ false)
+ sut.setOverScrollAmount(/* amount= */ 10f, /* onTop= */ false)
val resultOnTop = sut.getOverScrollAmount(/* top */ true)
val resultOnBottom = sut.getOverScrollAmount(/* top */ false)
@@ -284,6 +317,7 @@
assertThat(resultOnTop).isEqualTo(0f)
assertThat(resultOnBottom).isEqualTo(10f)
}
+
// endregion
// region IsPulseExpanding
@@ -317,6 +351,7 @@
assertThat(sut.isPulseExpanding).isFalse()
}
+
// endregion
// region isOnKeyguard
@@ -333,6 +368,7 @@
assertThat(sut.isOnKeyguard).isFalse()
}
+
// endregion
// region mIsClosing
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index d5a7c89..a940ed4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -19,6 +19,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
@@ -66,10 +67,13 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel.HorizontalPosition
import com.android.systemui.testKosmos
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertIs
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
@@ -165,6 +169,83 @@
}
@Test
+ fun validateHorizontalPositionSingleShade() =
+ testScope.runTest {
+ overrideDimensionPixelSize(R.dimen.shade_panel_width, 200)
+ val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ shadeTestUtil.setSplitShade(false)
+
+ val horizontalPosition = checkNotNull(dimens).horizontalPosition
+ assertIs<HorizontalPosition.EdgeToEdge>(horizontalPosition)
+ }
+
+ @Test
+ fun validateHorizontalPositionSplitShade() =
+ testScope.runTest {
+ overrideDimensionPixelSize(R.dimen.shade_panel_width, 200)
+ val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ shadeTestUtil.setSplitShade(true)
+
+ val horizontalPosition = checkNotNull(dimens).horizontalPosition
+ assertIs<HorizontalPosition.MiddleToEdge>(horizontalPosition)
+ assertThat(horizontalPosition.ratio).isEqualTo(0.5f)
+ }
+
+ @Test
+ @EnableSceneContainer
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun validateHorizontalPositionInSceneContainerSingleShade() =
+ testScope.runTest {
+ overrideDimensionPixelSize(R.dimen.shade_panel_width, 200)
+ val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ shadeTestUtil.setSplitShade(false)
+
+ val horizontalPosition = checkNotNull(dimens).horizontalPosition
+ assertIs<HorizontalPosition.EdgeToEdge>(horizontalPosition)
+ }
+
+ @Test
+ @EnableSceneContainer
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun validateHorizontalPositionInSceneContainerSplitShade() =
+ testScope.runTest {
+ overrideDimensionPixelSize(R.dimen.shade_panel_width, 200)
+ val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ shadeTestUtil.setSplitShade(true)
+
+ val horizontalPosition = checkNotNull(dimens).horizontalPosition
+ assertIs<HorizontalPosition.MiddleToEdge>(horizontalPosition)
+ assertThat(horizontalPosition.ratio).isEqualTo(0.5f)
+ }
+
+ @Test
+ @EnableSceneContainer
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun validateHorizontalPositionInDualShade_narrowLayout() =
+ testScope.runTest {
+ overrideDimensionPixelSize(R.dimen.shade_panel_width, 200)
+ val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ shadeTestUtil.setSplitShade(false)
+
+ val horizontalPosition = checkNotNull(dimens).horizontalPosition
+ assertIs<HorizontalPosition.EdgeToEdge>(horizontalPosition)
+ }
+
+ @Test
+ @EnableSceneContainer
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun validateHorizontalPositionInDualShade_wideLayout() =
+ testScope.runTest {
+ overrideDimensionPixelSize(R.dimen.shade_panel_width, 200)
+ val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ shadeTestUtil.setSplitShade(true)
+
+ val horizontalPosition = checkNotNull(dimens).horizontalPosition
+ assertIs<HorizontalPosition.FloatAtEnd>(horizontalPosition)
+ assertThat(horizontalPosition.width).isEqualTo(200)
+ }
+
+ @Test
fun validatePaddingTopInSplitShade_usesLargeHeaderHelper() =
testScope.runTest {
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/KeyguardBypassInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/KeyguardBypassInteractorTest.kt
new file mode 100644
index 0000000..c90183d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/domain/interactor/KeyguardBypassInteractorTest.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.phone.domain.interactor
+
+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.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.configureKeyguardBypass
+import com.android.systemui.keyguard.domain.interactor.KeyguardBypassInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardBypassInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
+class KeyguardBypassInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private lateinit var underTest: KeyguardBypassInteractor
+
+ @Test
+ fun canBypassFalseWhenBypassAvailableFalse() =
+ testScope.runTest {
+ initializeDependenciesForCanBypass(skipIsBypassAvailableCheck = false)
+ val canBypass by collectLastValue(underTest.canBypass)
+ runCurrent()
+ assertThat(canBypass).isFalse()
+ }
+
+ @Test
+ fun canBypassTrueOnPrimaryBouncerShowing() =
+ testScope.runTest {
+ initializeDependenciesForCanBypass(skipBouncerShowingCheck = false)
+ val canBypass by collectLastValue(underTest.canBypass)
+ runCurrent()
+ assertThat(canBypass).isTrue()
+ }
+
+ @Test
+ fun canBypassTrueOnAlternateBouncerShowing() =
+ testScope.runTest {
+ initializeDependenciesForCanBypass(skipAlternateBouncerShowingCheck = false)
+ val canBypass by collectLastValue(underTest.canBypass)
+ runCurrent()
+ assertThat(canBypass).isTrue()
+ }
+
+ @Test
+ fun canBypassFalseWhenNotOnLockscreenScene() =
+ testScope.runTest {
+ initializeDependenciesForCanBypass(skipOnLockscreenSceneCheck = false)
+ val canBypass by collectLastValue(underTest.canBypass)
+ val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ runCurrent()
+ assertThat(currentScene).isNotEqualTo(Scenes.Lockscreen)
+ assertThat(canBypass).isFalse()
+ }
+
+ @Test
+ fun canBypassFalseOnLaunchingAffordance() =
+ testScope.runTest {
+ initializeDependenciesForCanBypass(skipLaunchingAffordanceCheck = false)
+ val canBypass by collectLastValue(underTest.canBypass)
+ runCurrent()
+ assertThat(canBypass).isFalse()
+ }
+
+ @Test
+ fun canBypassFalseOnPulseExpanding() =
+ testScope.runTest {
+ initializeDependenciesForCanBypass(skipPulseExpandingCheck = false)
+ val canBypass by collectLastValue(underTest.canBypass)
+ runCurrent()
+ assertThat(canBypass).isFalse()
+ }
+
+ @Test
+ fun canBypassFalseOnQsExpanded() =
+ testScope.runTest {
+ initializeDependenciesForCanBypass(skipQsExpandedCheck = false)
+ val canBypass by collectLastValue(underTest.canBypass)
+ runCurrent()
+ assertThat(canBypass).isFalse()
+ }
+
+ // Initializes all canBypass dependencies to opposite of value needed to return
+ private fun initializeDependenciesForCanBypass(
+ skipIsBypassAvailableCheck: Boolean = true,
+ skipBouncerShowingCheck: Boolean = true,
+ skipAlternateBouncerShowingCheck: Boolean = true,
+ skipOnLockscreenSceneCheck: Boolean = true,
+ skipLaunchingAffordanceCheck: Boolean = true,
+ skipPulseExpandingCheck: Boolean = true,
+ skipQsExpandedCheck: Boolean = true,
+ ) {
+ // !isBypassAvailable false
+ kosmos.configureKeyguardBypass(isBypassAvailable = skipIsBypassAvailableCheck)
+ underTest = kosmos.keyguardBypassInteractor
+
+ // bouncerShowing false, !onLockscreenScene false
+ // !onLockscreenScene false
+ setScene(
+ bouncerShowing = !skipBouncerShowingCheck,
+ onLockscreenScene = skipOnLockscreenSceneCheck,
+ )
+ // alternateBouncerShowing false
+ setAlternateBouncerShowing(!skipAlternateBouncerShowingCheck)
+ // launchingAffordance false
+ setLaunchingAffordance(!skipLaunchingAffordanceCheck)
+ // pulseExpanding false
+ setPulseExpanding(!skipPulseExpandingCheck)
+ // qsExpanding false
+ setQsExpanded(!skipQsExpandedCheck)
+ }
+
+ private fun setAlternateBouncerShowing(alternateBouncerVisible: Boolean) {
+ kosmos.keyguardBouncerRepository.setAlternateVisible(alternateBouncerVisible)
+ }
+
+ private fun setScene(bouncerShowing: Boolean, onLockscreenScene: Boolean) {
+ if (bouncerShowing) {
+ kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "reason")
+ } else if (onLockscreenScene) {
+ kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ } else {
+ kosmos.sceneInteractor.changeScene(Scenes.Shade, "reason")
+ }
+ }
+
+ private fun setLaunchingAffordance(launchingAffordance: Boolean) {
+ kosmos.keyguardQuickAffordanceInteractor.setLaunchingAffordance(launchingAffordance)
+ }
+
+ private fun setPulseExpanding(pulseExpanding: Boolean) {
+ kosmos.pulseExpansionInteractor.setPulseExpanding(pulseExpanding)
+ }
+
+ private fun setQsExpanded(qsExpanded: Boolean) {
+ if (qsExpanded) {
+ kosmos.shadeTestUtil.setQsExpansion(1f)
+ } else {
+ kosmos.shadeTestUtil.setQsExpansion(0f)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/ConvenienceExtensionsKtTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/ConvenienceExtensionsKtTest.kt
new file mode 100644
index 0000000..2d57e2f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/ConvenienceExtensionsKtTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ConvenienceExtensionsKtTest : SysuiTestCase() {
+
+ @Test
+ fun containsExactly_notDuplicatedElements_allSame_returnsTrue() {
+ val list = listOf(1, 2, 3)
+
+ assertThat(list.containsExactly(2, 1, 3)).isTrue()
+ }
+
+ @Test
+ fun containsExactly_duplicatedElements_allSame_returnsTrue() {
+ val list = listOf(1, 1, 2, 3, 3)
+
+ assertThat(list.containsExactly(1, 1, 2, 3, 3)).isTrue()
+ }
+
+ @Test
+ fun containsExactly_duplicatedElements_sameButNotDuplicated_returnsFalse() {
+ val list = listOf(1, 1, 2, 3, 3)
+
+ assertThat(list.containsExactly(1, 2, 3)).isFalse()
+ }
+
+ @Test
+ fun containsExactly_duplicatedElements_sameButNotSameAmount_returnsFalse() {
+ val list = listOf(1, 1, 2, 3, 3)
+
+ assertThat(list.containsExactly(1, 2, 2, 3, 3)).isFalse()
+ }
+
+ @Test
+ fun eachCountMap_returnsExpectedCount() {
+ val list = listOf(1, 3, 1, 3, 3, 3, 2)
+
+ assertThat(list.eachCountMap()).isEqualTo(mapOf(1 to 2, 2 to 1, 3 to 4))
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java
index 207c35d..90aecfb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java
@@ -21,43 +21,23 @@
import android.os.Build;
import android.os.PowerManager;
-import android.platform.test.flag.junit.FlagsParameterization;
-import android.platform.test.flag.junit.SetFlagsRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.List;
-
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
@SmallTest
-@RunWith(ParameterizedAndroidJunit4.class)
+@RunWith(AndroidJUnit4.class)
public class WakeLockTest extends SysuiTestCase {
- @Parameters(name = "{0}")
- public static List<FlagsParameterization> getFlags() {
- return FlagsParameterization.allCombinationsOf(
- Flags.FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD);
- }
-
- @Rule public final SetFlagsRule mSetFlagsRule;
-
- public WakeLockTest(FlagsParameterization flags) {
- mSetFlagsRule = new SetFlagsRule(SetFlagsRule.DefaultInitValueType.NULL_DEFAULT, flags);
- }
-
private static final String WHY = "test";
WakeLock mWakeLock;
PowerManager.WakeLock mInner;
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index e264264..5792175 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -203,12 +203,12 @@
fun onZenDataChanged(data: ZenData)
/** Update reactive axes for this clock */
- fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>)
+ fun onFontAxesChanged(axes: List<ClockFontAxisSetting>)
}
/** Axis setting value for a clock */
-data class ClockReactiveSetting(
- /** Axis key; matches ClockReactiveAxis.key */
+data class ClockFontAxisSetting(
+ /** Axis key; matches ClockFontAxis.key */
val key: String,
/** Value to set this axis to */
@@ -323,11 +323,11 @@
val isReactiveToTone: Boolean = true,
/** Font axes that can be modified on this clock */
- val axes: List<ClockReactiveAxis> = listOf(),
+ val axes: List<ClockFontAxis> = listOf(),
)
/** Represents an Axis that can be modified */
-data class ClockReactiveAxis(
+data class ClockFontAxis(
/** Axis key, not user renderable */
val key: String,
@@ -348,15 +348,17 @@
/** Description of the axis */
val description: String,
-)
+) {
+ fun toSetting() = ClockFontAxisSetting(key, currentValue)
+}
/** Axis user interaction modes */
enum class AxisType {
- /** Boolean toggle. Swaps between minValue & maxValue */
- Toggle,
+ /** Continuous range between minValue & maxValue. */
+ Float,
- /** Continuous slider between minValue & maxValue */
- Slider,
+ /** Only minValue & maxValue are valid. No intermediate values between them are allowed. */
+ Boolean,
}
/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */
@@ -404,7 +406,7 @@
data class ClockSettings(
val clockId: ClockId? = null,
val seedColor: Int? = null,
- val axes: List<ClockReactiveSetting>? = null,
+ val axes: List<ClockFontAxisSetting>? = null,
) {
// Exclude metadata from equality checks
var metadata: JSONObject = JSONObject()
diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
index ca62d28..f38f42d 100644
--- a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
@@ -23,4 +23,7 @@
<dimen name="keyguard_clock_top_margin">80dp</dimen>
<dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">155dp</dimen>
<dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">85dp</dimen>
+
+ <!-- The width of the shade overlay panel (both notifications and quick settings). -->
+ <dimen name="shade_panel_width">474dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw800dp/dimens.xml b/packages/SystemUI/res/values-sw800dp/dimens.xml
index 0d82217..8ae3a2e 100644
--- a/packages/SystemUI/res/values-sw800dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw800dp/dimens.xml
@@ -21,4 +21,7 @@
<!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
<dimen name="biometric_auth_pattern_view_size">348dp</dimen>
+
+ <!-- The width of the shade overlay panel (both notifications and quick settings). -->
+ <dimen name="shade_panel_width">392dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-w1000dp/dimens.xml b/packages/SystemUI/res/values-w1000dp/dimens.xml
new file mode 100644
index 0000000..b3f7acd
--- /dev/null
+++ b/packages/SystemUI/res/values-w1000dp/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <!-- The width of the shade overlay panel (both notifications and quick settings). -->
+ <dimen name="shade_panel_width">474dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index f96a0b9..d4a52c3 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -338,6 +338,9 @@
<!-- Whether to show the full screen user switcher. -->
<bool name="config_enableFullscreenUserSwitcher">false</bool>
+ <!-- Whether to go to the launcher when unlocking via an occluding app -->
+ <bool name="config_goToHomeFromOccludedApps">false</bool>
+
<!-- Determines whether the shell features all run on another thread. -->
<bool name="config_enableShellMainThread">true</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c2d942f..de547ad 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -577,6 +577,13 @@
<dimen name="notification_panel_margin_horizontal">0dp</dimen>
+ <!--
+ The width of the shade overlay panel (both notifications and quick settings). On Compact screens
+ in portrait orientation (< w600dp) this is ignored, and the shade layout takes up the full
+ screen width.
+ -->
+ <dimen name="shade_panel_width">412dp</dimen>
+
<dimen name="brightness_mirror_height">48dp</dimen>
<dimen name="volume_dialog_panel_transparent_padding_right">8dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 20e70e0..88ed4e3 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -286,4 +286,6 @@
<item type="id" name="snapshot_view_binding" />
<item type="id" name="snapshot_view_binding_root" />
+ <item type="id" name="brightness_dialog_slider" />
+
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5e4cb75..ab64ae5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3733,6 +3733,10 @@
<!-- Title at the top of the keyboard shortcut helper UI. The helper is a component
that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
<string name="shortcut_helper_title">Keyboard shortcuts</string>
+ <!-- Title at the top of the keyboard shortcut helper UI when in customize mode. The helper
+ is a component that shows the user which keyboard shortcuts they can use.
+ [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_customize_mode_title">Customize keyboard shortcuts</string>
<!-- Placeholder text shown in the search box of the keyboard shortcut helper, when the user
hasn't typed in anything in the search box yet. The helper is a component that shows the
user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
@@ -3744,6 +3748,14 @@
use. The helper shows shortcuts in categories, which can be collapsed or expanded.
[CHAR LIMIT=NONE] -->
<string name="shortcut_helper_content_description_collapse_icon">Collapse icon</string>
+ <!-- Description text of the button that allows user to customize shortcuts in keyboard
+ shortcut helper The helper is a component that shows the user which keyboard shortcuts
+ they can use. [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_customize_button_text">Customize</string>
+ <!-- Description text of the button that allows user to exit shortcut customization mode in
+ keyboard shortcut helper The helper is a component that shows the user which keyboard
+ shortcuts they can use. [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_done_button_text">Done</string>
<!-- Content description of the icon that allows to expand a keyboard shortcut helper category
panel. The helper is a component that shows the user which keyboard shortcuts they can
use. The helper shows shortcuts in categories, which can be collapsed or expanded.
@@ -3918,10 +3930,10 @@
</string>
<!-- Title for the Reset Tiles dialog in QS Edit mode. [CHAR LIMIT=NONE] -->
<string name="qs_edit_mode_reset_dialog_title">
- Reset tiles
+ Reset all tiles?
</string>
<!-- Content of the Reset Tiles dialog in QS Edit mode. [CHAR LIMIT=NONE] -->
<string name="qs_edit_mode_reset_dialog_content">
- Reset tiles to their original order and sizes?
+ All Quick Settings tiles will reset to the device’s original settings
</string>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 9b5d5b6..46e45aa 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -92,7 +92,6 @@
import com.android.systemui.statusbar.events.PrivacyDotViewController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.concurrency.ThreadFactory;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
@@ -147,7 +146,6 @@
private CameraAvailabilityListener mCameraListener;
private final UserTracker mUserTracker;
private final PrivacyDotViewController mDotViewController;
- private final ThreadFactory mThreadFactory;
private final DecorProviderFactory mDotFactory;
private final FaceScanningProviderFactory mFaceScanningFactory;
private final CameraProtectionLoader mCameraProtectionLoader;
@@ -172,7 +170,6 @@
private ViewCaptureAwareWindowManager mWindowManager;
private int mRotation;
private UserSettingObserver mColorInversionSetting;
- @Nullable
private DelayableExecutor mExecutor;
private Handler mHandler;
boolean mPendingConfigChange;
@@ -327,27 +324,28 @@
}
@Inject
- public ScreenDecorations(Context context,
+ public ScreenDecorations(
+ Context context,
SecureSettings secureSettings,
CommandRegistry commandRegistry,
UserTracker userTracker,
DisplayTracker displayTracker,
PrivacyDotViewController dotViewController,
- ThreadFactory threadFactory,
PrivacyDotDecorProviderFactory dotFactory,
FaceScanningProviderFactory faceScanningFactory,
ScreenDecorationsLogger logger,
FacePropertyRepository facePropertyRepository,
JavaAdapter javaAdapter,
CameraProtectionLoader cameraProtectionLoader,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+ @ScreenDecorationsThread Handler handler,
+ @ScreenDecorationsThread DelayableExecutor executor) {
mContext = context;
mSecureSettings = secureSettings;
mCommandRegistry = commandRegistry;
mUserTracker = userTracker;
mDisplayTracker = displayTracker;
mDotViewController = dotViewController;
- mThreadFactory = threadFactory;
mDotFactory = dotFactory;
mFaceScanningFactory = faceScanningFactory;
mCameraProtectionLoader = cameraProtectionLoader;
@@ -356,6 +354,8 @@
mFacePropertyRepository = facePropertyRepository;
mJavaAdapter = javaAdapter;
mWindowManager = viewCaptureAwareWindowManager;
+ mHandler = handler;
+ mExecutor = executor;
}
private final ScreenDecorCommand.Callback mScreenDecorCommandCallback = (cmd, pw) -> {
@@ -403,10 +403,7 @@
Log.i(TAG, "ScreenDecorations is disabled");
return;
}
- mHandler = mThreadFactory.buildHandlerOnNewThread("ScreenDecorations");
- mExecutor = mThreadFactory.buildDelayableExecutorOnHandler(mHandler);
mExecutor.execute(this::startOnScreenDecorationsThread);
- mDotViewController.setUiExecutor(mExecutor);
mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME,
() -> new ScreenDecorCommand(mScreenDecorCommandCallback));
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt
index 6fc50fb..6786a71 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt
@@ -17,16 +17,23 @@
package com.android.systemui
import android.content.Context
+import android.os.Handler
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.decor.FaceScanningProviderFactory
import com.android.systemui.decor.FaceScanningProviderFactoryImpl
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.ThreadFactory
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import dagger.multibindings.IntoSet
+import java.util.concurrent.Executor
+import javax.inject.Qualifier
+
+@Qualifier annotation class ScreenDecorationsThread
@Module
interface ScreenDecorationsModule {
@@ -41,6 +48,12 @@
@IntoSet
fun bindScreenDecorationsConfigListener(impl: ScreenDecorations): ConfigurationListener
+ @Binds
+ @ScreenDecorationsThread
+ fun screenDecorationsExecutor(
+ @ScreenDecorationsThread delayableExecutor: DelayableExecutor
+ ): Executor
+
companion object {
@Provides
@SysUISingleton
@@ -50,5 +63,22 @@
): FaceScanningProviderFactory {
return creator.create(context)
}
+
+ @Provides
+ @SysUISingleton
+ @ScreenDecorationsThread
+ fun screenDecorationsHandler(threadFactory: ThreadFactory): Handler {
+ return threadFactory.buildHandlerOnNewThread("ScreenDecorations")
+ }
+
+ @Provides
+ @SysUISingleton
+ @ScreenDecorationsThread
+ fun screenDecorationsDelayableExecutor(
+ @ScreenDecorationsThread handler: Handler,
+ threadFactory: ThreadFactory,
+ ): DelayableExecutor {
+ return threadFactory.buildDelayableExecutorOnHandler(handler)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 69ab976..12b5fc0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -33,7 +33,6 @@
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
-import android.hardware.biometrics.Flags;
import android.hardware.biometrics.PromptInfo;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -323,7 +322,7 @@
final boolean isLandscape = mContext.getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
mPromptSelectorInteractorProvider = promptSelectorInteractorProvider;
- mPromptSelectorInteractorProvider.get().setPrompt(mConfig.mPromptInfo, mEffectiveUserId,
+ mPromptSelectorInteractorProvider.get().setPrompt(mConfig.mPromptInfo, mConfig.mUserId,
getRequestId(), biometricModalities, mConfig.mOperationId, mConfig.mOpPackageName,
false /*onSwitchToCredential*/, isLandscape);
@@ -676,10 +675,8 @@
final Runnable endActionRunnable = () -> {
setVisibility(View.INVISIBLE);
- if (Flags.customBiometricPrompt()) {
// TODO(b/288175645): resetPrompt calls should be lifecycle aware
mPromptSelectorInteractorProvider.get().resetPrompt(getRequestId());
- }
removeWindowIfAttached();
};
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
index 4997370..b070068 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -3,6 +3,7 @@
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyResources
import android.content.Context
+import android.hardware.biometrics.Flags
import android.os.UserManager
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockscreenCredential
@@ -71,13 +72,22 @@
// Request LockSettingsService to return the Gatekeeper Password in the
// VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
// Gatekeeper Password and operationId.
- val effectiveUserId = request.userInfo.deviceCredentialOwnerId
+ var effectiveUserId = request.userInfo.userIdForPasswordEntry
val response =
- lockPatternUtils.verifyCredential(
- credential,
- effectiveUserId,
- LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE
- )
+ if (Flags.privateSpaceBp() && effectiveUserId != request.userInfo.userId) {
+ effectiveUserId = request.userInfo.userId
+ lockPatternUtils.verifyTiedProfileChallenge(
+ credential,
+ request.userInfo.userId,
+ LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
+ )
+ } else {
+ lockPatternUtils.verifyCredential(
+ credential,
+ effectiveUserId,
+ LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
+ )
+ }
if (response.isMatched) {
lockPatternUtils.userPresent(effectiveUserId)
@@ -91,7 +101,7 @@
lockPatternUtils.verifyGatekeeperPasswordHandle(
pwHandle,
request.operationInfo.gatekeeperChallenge,
- effectiveUserId
+ effectiveUserId,
)
val hat = gkResponse.gatekeeperHAT
lockPatternUtils.removeGatekeeperPasswordHandle(pwHandle)
@@ -108,7 +118,7 @@
CredentialStatus.Fail.Throttled(
applicationContext.getString(
R.string.biometric_dialog_credential_too_many_attempts,
- remaining / 1000
+ remaining / 1000,
)
)
)
@@ -129,10 +139,10 @@
applicationContext.getString(
R.string.biometric_dialog_credential_attempts_before_wipe,
numAttempts,
- maxAttempts
+ maxAttempts,
),
remainingAttempts,
- fetchFinalAttemptMessageOrNull(request, remainingAttempts)
+ fetchFinalAttemptMessageOrNull(request, remainingAttempts),
)
)
}
@@ -150,9 +160,9 @@
devicePolicyManager,
userManager.getUserTypeForWipe(
devicePolicyManager,
- request.userInfo.deviceCredentialOwnerId
+ request.userInfo.deviceCredentialOwnerId,
),
- remainingAttempts
+ remainingAttempts,
)
} else {
null
@@ -205,7 +215,7 @@
}
private fun Context.getLastAttemptBeforeWipeDeviceMessage(
- request: BiometricPromptRequest.Credential,
+ request: BiometricPromptRequest.Credential
): String {
val id =
when (request) {
@@ -249,7 +259,7 @@
}
private fun Context.getLastAttemptBeforeWipeUserMessage(
- request: BiometricPromptRequest.Credential,
+ request: BiometricPromptRequest.Credential
): String {
val resId =
when (request) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index 6da5e42..008fb26 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -16,7 +16,6 @@
package com.android.systemui.biometrics.domain.interactor
-import android.hardware.biometrics.Flags
import android.hardware.biometrics.PromptInfo
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.biometrics.Utils
@@ -104,6 +103,7 @@
constructor(
fingerprintPropertyRepository: FingerprintPropertyRepository,
private val displayStateInteractor: DisplayStateInteractor,
+ private val credentialInteractor: CredentialInteractor,
private val promptRepository: PromptRepository,
private val lockPatternUtils: LockPatternUtils,
) : PromptSelectorInteractor {
@@ -177,7 +177,7 @@
override fun setPrompt(
promptInfo: PromptInfo,
- effectiveUserId: Int,
+ userId: Int,
requestId: Long,
modalities: BiometricModalities,
challenge: Long,
@@ -185,10 +185,10 @@
onSwitchToCredential: Boolean,
isLandscape: Boolean,
) {
+ val effectiveUserId = credentialInteractor.getCredentialOwnerOrSelfId(userId)
val hasCredentialViewShown = promptKind.value.isCredential()
val showBpForCredential =
- Flags.customBiometricPrompt() &&
- !Utils.isBiometricAllowed(promptInfo) &&
+ !Utils.isBiometricAllowed(promptInfo) &&
isDeviceCredentialAllowed(promptInfo) &&
promptInfo.contentView != null &&
!promptInfo.isContentViewMoreOptionsButtonUsed
@@ -211,10 +211,7 @@
PromptKind.Biometric.PaneType.ONE_PANE_NO_SENSOR_LANDSCAPE
else -> PromptKind.Biometric.PaneType.TWO_PANE_LANDSCAPE
}
- PromptKind.Biometric(
- modalities,
- paneType = paneType,
- )
+ PromptKind.Biometric(modalities, paneType = paneType)
} else {
PromptKind.Biometric(modalities)
}
@@ -224,7 +221,7 @@
promptRepository.setPrompt(
promptInfo = promptInfo,
- userId = effectiveUserId,
+ userId = userId,
requestId = requestId,
gatekeeperChallenge = challenge,
kind = kind,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 2df5f16..db4b0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -22,7 +22,6 @@
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricPrompt
-import android.hardware.biometrics.Flags
import android.hardware.face.FaceManager
import android.util.Log
import android.view.MotionEvent
@@ -93,7 +92,7 @@
val attributes =
view.context.obtainStyledAttributes(
R.style.TextAppearance_AuthCredential_Indicator,
- intArrayOf(android.R.attr.textColor)
+ intArrayOf(android.R.attr.textColor),
)
val textColorHint = attributes.getColor(0, 0)
attributes.recycle()
@@ -130,13 +129,13 @@
object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(
host: View,
- info: AccessibilityNodeInfoCompat
+ info: AccessibilityNodeInfoCompat,
) {
super.onInitializeAccessibilityNodeInfo(host, info)
info.addAction(
AccessibilityActionCompat(
AccessibilityNodeInfoCompat.ACTION_CLICK,
- view.context.getString(R.string.biometric_dialog_cancel_authentication)
+ view.context.getString(R.string.biometric_dialog_cancel_authentication),
)
)
}
@@ -189,13 +188,11 @@
subtitleView.text = viewModel.subtitle.first()
descriptionView.text = viewModel.description.first()
- if (Flags.customBiometricPrompt()) {
- BiometricCustomizedViewBinder.bind(
- customizedViewContainer,
- viewModel.contentView.first(),
- legacyCallback
- )
- }
+ BiometricCustomizedViewBinder.bind(
+ customizedViewContainer,
+ viewModel.contentView.first(),
+ legacyCallback,
+ )
// set button listeners
negativeButton.setOnClickListener { legacyCallback.onButtonNegative() }
@@ -233,10 +230,7 @@
lifecycleScope.launch {
viewModel.hideSensorIcon.collect { showWithoutIcon ->
if (!showWithoutIcon) {
- PromptIconViewBinder.bind(
- iconView,
- viewModel,
- )
+ PromptIconViewBinder.bind(iconView, viewModel)
}
}
}
@@ -421,7 +415,7 @@
launch {
viewModel.onAnnounceAccessibilityHint(
event,
- accessibilityManager.isTouchExplorationEnabled
+ accessibilityManager.isTouchExplorationEnabled,
)
}
false
@@ -444,10 +438,7 @@
haptics.flag,
)
} else {
- vibratorHelper.performHapticFeedback(
- view,
- haptics.constant,
- )
+ vibratorHelper.performHapticFeedback(view, haptics.constant)
}
}
is PromptViewModel.HapticsToPlay.MSDL -> {
@@ -561,14 +552,12 @@
viewModel.showAuthenticated(
modality = authenticatedModality,
dismissAfterDelay = 500,
- helpMessage = if (msgId != null) applicationContext.getString(msgId) else ""
+ helpMessage = if (msgId != null) applicationContext.getString(msgId) else "",
)
}
}
- private fun getHelpForSuccessfulAuthentication(
- authenticatedModality: BiometricModality,
- ): Int? {
+ private fun getHelpForSuccessfulAuthentication(authenticatedModality: BiometricModality): Int? {
// for coex, show a message when face succeeds after fingerprint has also started
if (authenticatedModality != BiometricModality.Face) {
return null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index 3ea91f0..39543e7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -1,6 +1,5 @@
package com.android.systemui.biometrics.ui.binder
-import android.hardware.biometrics.Flags
import android.view.View
import android.view.ViewGroup
import android.widget.Button
@@ -67,7 +66,7 @@
updateForContentDimensions(
containerWidth,
containerHeight,
- 0 // animateDurationMs
+ 0, // animateDurationMs
)
}
}
@@ -81,13 +80,11 @@
subtitleView.textOrHide = header.subtitle
descriptionView.textOrHide = header.description
- if (Flags.customBiometricPrompt()) {
- BiometricCustomizedViewBinder.bind(
- customizedViewContainer,
- header.contentView,
- legacyCallback
- )
- }
+ BiometricCustomizedViewBinder.bind(
+ customizedViewContainer,
+ header.contentView,
+ legacyCallback,
+ )
iconView?.setImageDrawable(header.icon)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
index 761c3da..0c5c723 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
@@ -2,7 +2,6 @@
import android.content.Context
import android.graphics.drawable.Drawable
-import android.hardware.biometrics.Flags.customBiometricPrompt
import android.hardware.biometrics.PromptContentView
import android.text.InputType
import com.android.internal.widget.LockPatternView
@@ -36,21 +35,17 @@
val header: Flow<CredentialHeaderViewModel> =
combine(
credentialInteractor.prompt.filterIsInstance<BiometricPromptRequest.Credential>(),
- credentialInteractor.showTitleOnly
+ credentialInteractor.showTitleOnly,
) { request, showTitleOnly ->
- val flagEnabled = customBiometricPrompt()
- val showTitleOnlyForCredential = showTitleOnly && flagEnabled
BiometricPromptHeaderViewModelImpl(
request,
user = request.userInfo,
title = request.title,
- subtitle = if (showTitleOnlyForCredential) "" else request.subtitle,
- contentView =
- if (flagEnabled && !showTitleOnlyForCredential) request.contentView else null,
- description =
- if (flagEnabled && request.contentView != null) "" else request.description,
+ subtitle = if (showTitleOnly) "" else request.subtitle,
+ contentView = if (!showTitleOnly) request.contentView else null,
+ description = if (request.contentView != null) "" else request.description,
icon = applicationContext.asLockIcon(request.userInfo.deviceCredentialOwnerId),
- showEmergencyCallButton = request.showEmergencyCallButton
+ showEmergencyCallButton = request.showEmergencyCallButton,
)
}
@@ -125,7 +120,7 @@
/** Check a pattern and update [validatedAttestation] or [remainingAttempts]. */
suspend fun checkCredential(
pattern: List<LockPatternView.Cell>,
- header: CredentialHeaderViewModel
+ header: CredentialHeaderViewModel,
) = checkCredential(credentialInteractor.checkCredential(header.asRequest(), pattern = pattern))
private suspend fun checkCredential(result: CredentialStatus) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 0ac9405..cbf783d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -27,7 +27,6 @@
import android.graphics.drawable.Drawable
import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.BiometricPrompt
-import android.hardware.biometrics.Flags.customBiometricPrompt
import android.hardware.biometrics.PromptContentView
import android.os.UserHandle
import android.text.TextPaint
@@ -145,13 +144,13 @@
rotatedBounds,
params.naturalDisplayWidth,
params.naturalDisplayHeight,
- rotation.ordinal
+ rotation.ordinal,
)
Rect(
rotatedBounds.left,
rotatedBounds.top,
params.logicalDisplayWidth - rotatedBounds.right,
- params.logicalDisplayHeight - rotatedBounds.bottom
+ params.logicalDisplayHeight - rotatedBounds.bottom,
)
}
.distinctUntilChanged()
@@ -263,7 +262,7 @@
promptKind,
displayStateInteractor.isLargeScreen,
displayStateInteractor.currentRotation,
- modalities
+ modalities,
) { forceLarge, promptKind, isLargeScreen, rotation, modalities ->
when {
forceLarge ||
@@ -351,7 +350,7 @@
0,
0,
landscapeMediumHorizontalPadding,
- landscapeMediumBottomPadding
+ landscapeMediumBottomPadding,
)
}
PromptPosition.Left ->
@@ -365,7 +364,7 @@
landscapeMediumHorizontalPadding,
0,
0,
- landscapeMediumBottomPadding
+ landscapeMediumBottomPadding,
)
}
PromptPosition.Top ->
@@ -474,7 +473,7 @@
promptSelectorInteractor.prompt
.map {
when {
- !(customBiometricPrompt()) || it == null -> Pair(null, "")
+ it == null -> Pair(null, "")
else -> context.getUserBadgedLogoInfo(it, iconProvider, activityTaskManager)
}
}
@@ -490,9 +489,7 @@
/** Custom content view for the prompt. */
val contentView: Flow<PromptContentView?> =
- promptSelectorInteractor.prompt
- .map { if (customBiometricPrompt()) it?.contentView else null }
- .distinctUntilChanged()
+ promptSelectorInteractor.prompt.map { it?.contentView }.distinctUntilChanged()
private val originalDescription =
promptSelectorInteractor.prompt.map { it?.description ?: "" }.distinctUntilChanged()
@@ -521,7 +518,7 @@
val attributes =
context.obtainStyledAttributes(
R.style.TextAppearance_AuthCredential_Title,
- intArrayOf(android.R.attr.textSize)
+ intArrayOf(android.R.attr.textSize),
)
val paint = TextPaint()
paint.textSize = attributes.getDimensionPixelSize(0, 0).toFloat()
@@ -566,7 +563,7 @@
private fun getHorizontalPadding(
size: PromptSize,
modalities: BiometricModalities,
- hasOnlyOneLineTitle: Boolean
+ hasOnlyOneLineTitle: Boolean,
) =
if (size.isSmall) {
-smallHorizontalGuidelinePadding
@@ -582,21 +579,13 @@
/** If the indicator (help, error) message should be shown. */
val isIndicatorMessageVisible: Flow<Boolean> =
- combine(
- size,
- position,
- message,
- ) { size, _, message ->
+ combine(size, position, message) { size, _, message ->
size.isMedium && message.message.isNotBlank()
}
/** If the auth is pending confirmation and the confirm button should be shown. */
val isConfirmButtonVisible: Flow<Boolean> =
- combine(
- size,
- position,
- isPendingConfirmation,
- ) { size, _, isPendingConfirmation ->
+ combine(size, position, isPendingConfirmation) { size, _, isPendingConfirmation ->
size.isNotSmall && isPendingConfirmation
}
@@ -606,24 +595,22 @@
/** If the negative button should be shown. */
val isNegativeButtonVisible: Flow<Boolean> =
- combine(
+ combine(size, position, isAuthenticated, promptSelectorInteractor.isCredentialAllowed) {
size,
- position,
- isAuthenticated,
- promptSelectorInteractor.isCredentialAllowed,
- ) { size, _, authState, credentialAllowed ->
+ _,
+ authState,
+ credentialAllowed ->
size.isNotSmall && authState.isNotAuthenticated && !credentialAllowed
}
/** If the cancel button should be shown (. */
val isCancelButtonVisible: Flow<Boolean> =
- combine(
+ combine(size, position, isAuthenticated, isNegativeButtonVisible, isConfirmButtonVisible) {
size,
- position,
- isAuthenticated,
- isNegativeButtonVisible,
- isConfirmButtonVisible,
- ) { size, _, authState, showNegativeButton, showConfirmButton ->
+ _,
+ authState,
+ showNegativeButton,
+ showConfirmButton ->
size.isNotSmall && authState.isAuthenticated && !showNegativeButton && showConfirmButton
}
@@ -633,33 +620,28 @@
* fingerprint sensor.
*/
val canTryAgainNow: Flow<Boolean> =
- combine(
- _canTryAgainNow,
+ combine(_canTryAgainNow, size, position, isAuthenticated, isRetrySupported) {
+ readyToTryAgain,
size,
- position,
- isAuthenticated,
- isRetrySupported,
- ) { readyToTryAgain, size, _, authState, supportsRetry ->
+ _,
+ authState,
+ supportsRetry ->
readyToTryAgain && size.isNotSmall && supportsRetry && authState.isNotAuthenticated
}
/** If the try again button show be shown (only the button, see [canTryAgainNow]). */
val isTryAgainButtonVisible: Flow<Boolean> =
- combine(
- canTryAgainNow,
- modalities,
- ) { tryAgainIsPossible, modalities ->
+ combine(canTryAgainNow, modalities) { tryAgainIsPossible, modalities ->
tryAgainIsPossible && modalities.hasFaceOnly
}
/** If the credential fallback button show be shown. */
val isCredentialButtonVisible: Flow<Boolean> =
- combine(
+ combine(size, position, isAuthenticated, promptSelectorInteractor.isCredentialAllowed) {
size,
- position,
- isAuthenticated,
- promptSelectorInteractor.isCredentialAllowed,
- ) { size, _, authState, credentialAllowed ->
+ _,
+ authState,
+ credentialAllowed ->
size.isMedium && authState.isNotAuthenticated && credentialAllowed
}
@@ -759,10 +741,7 @@
*
* Ignored if the user has already authenticated.
*/
- suspend fun showTemporaryHelp(
- message: String,
- messageAfterHelp: String = "",
- ) = coroutineScope {
+ suspend fun showTemporaryHelp(message: String, messageAfterHelp: String = "") = coroutineScope {
if (_isAuthenticated.value.isAuthenticated) {
return@coroutineScope
}
@@ -910,13 +889,13 @@
udfpsUtils.getTouchInNativeCoordinates(
event.getPointerId(0),
event,
- udfpsOverlayParams.value
+ udfpsOverlayParams.value,
)
if (
!udfpsUtils.isWithinSensorArea(
event.getPointerId(0),
event,
- udfpsOverlayParams.value
+ udfpsOverlayParams.value,
)
) {
_accessibilityHint.emit(
@@ -925,7 +904,7 @@
context,
scaledTouch.x,
scaledTouch.y,
- udfpsOverlayParams.value
+ udfpsOverlayParams.value,
)
)
}
@@ -948,10 +927,7 @@
if (msdlFeedback()) {
HapticsToPlay.MSDL(MSDLToken.UNLOCK, authInteractionProperties)
} else {
- HapticsToPlay.HapticConstant(
- HapticFeedbackConstants.BIOMETRIC_CONFIRM,
- flag = null,
- )
+ HapticsToPlay.HapticConstant(HapticFeedbackConstants.BIOMETRIC_CONFIRM, flag = null)
}
_hapticsToPlay.value = haptics
}
@@ -961,10 +937,7 @@
if (msdlFeedback()) {
HapticsToPlay.MSDL(MSDLToken.FAILURE, authInteractionProperties)
} else {
- HapticsToPlay.HapticConstant(
- HapticFeedbackConstants.BIOMETRIC_REJECT,
- flag = null,
- )
+ HapticsToPlay.HapticConstant(HapticFeedbackConstants.BIOMETRIC_REJECT, flag = null)
}
_hapticsToPlay.value = haptics
}
@@ -1006,7 +979,7 @@
private fun Context.getUserBadgedLogoInfo(
prompt: BiometricPromptRequest.Biometric,
iconProvider: IconProvider,
- activityTaskManager: ActivityTaskManager
+ activityTaskManager: ActivityTaskManager,
): Pair<Drawable?, String> {
var icon: Drawable? =
if (prompt.logoBitmap != null) BitmapDrawable(resources, prompt.logoBitmap) else null
@@ -1045,7 +1018,7 @@
packageManager
.getUserBadgedLabel(
packageManager.getApplicationLabel(appInfo),
- UserHandle.of(userId)
+ UserHandle.of(userId),
)
.toString()
}
@@ -1070,7 +1043,7 @@
private fun BiometricPromptRequest.Biometric.getApplicationInfo(
context: Context,
- componentNameForLogo: ComponentName?
+ componentNameForLogo: ComponentName?,
): ApplicationInfo? {
val packageName =
when {
@@ -1088,7 +1061,7 @@
try {
context.packageManager.getApplicationInfo(
packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
+ PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER,
)
} catch (e: PackageManager.NameNotFoundException) {
Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName", e)
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
index 8639ee5..02161d2 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
@@ -20,21 +20,31 @@
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.PlatformSlider
+import com.android.compose.ui.graphics.drawInOverlay
import com.android.systemui.Flags
import com.android.systemui.brightness.shared.model.GammaBrightness
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
@@ -42,12 +52,13 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig
import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.res.R
import com.android.systemui.utils.PolicyRestriction
-import com.android.app.tracing.coroutines.launchTraced as launch
@Composable
private fun BrightnessSlider(
@@ -104,7 +115,7 @@
}
},
modifier =
- modifier.clickable(enabled = isRestricted) {
+ modifier.sysuiResTag("slider").clickable(enabled = isRestricted) {
if (restriction is PolicyRestriction.Restricted) {
onRestrictedClick(restriction)
}
@@ -127,27 +138,55 @@
)
}
+private val sliderBackgroundFrameSize = 8.dp
+
+private fun Modifier.sliderBackground(color: Color) = drawWithCache {
+ val offsetAround = sliderBackgroundFrameSize.toPx()
+ val newSize = Size(size.width + 2 * offsetAround, size.height + 2 * offsetAround)
+ val offset = Offset(-offsetAround, -offsetAround)
+ val cornerRadius = CornerRadius(offsetAround + size.height / 2)
+ onDrawBehind {
+ drawRoundRect(color = color, topLeft = offset, size = newSize, cornerRadius = cornerRadius)
+ }
+}
+
@Composable
-fun BrightnessSliderContainer(viewModel: BrightnessSliderViewModel, modifier: Modifier = Modifier) {
- val state by viewModel.currentBrightness.collectAsStateWithLifecycle()
- val gamma = state.value
+fun BrightnessSliderContainer(
+ viewModel: BrightnessSliderViewModel,
+ modifier: Modifier = Modifier,
+ containerColor: Color = colorResource(R.color.shade_scrim_background_dark),
+) {
+ val gamma = viewModel.currentBrightness.value
val coroutineScope = rememberCoroutineScope()
val restriction by
viewModel.policyRestriction.collectAsStateWithLifecycle(
initialValue = PolicyRestriction.NoRestriction
)
- BrightnessSlider(
- gammaValue = gamma,
- valueRange = viewModel.minBrightness.value..viewModel.maxBrightness.value,
- label = viewModel.label,
- icon = viewModel.icon,
- restriction = restriction,
- onRestrictedClick = viewModel::showPolicyRestrictionDialog,
- onDrag = { coroutineScope.launch { viewModel.onDrag(Drag.Dragging(GammaBrightness(it))) } },
- onStop = { coroutineScope.launch { viewModel.onDrag(Drag.Stopped(GammaBrightness(it))) } },
- modifier = modifier.fillMaxWidth(),
- formatter = viewModel::formatValue,
- hapticsViewModelFactory = viewModel.hapticsViewModelFactory,
- )
+ DisposableEffect(Unit) { onDispose { viewModel.setIsDragging(false) } }
+
+ Box(modifier = modifier.fillMaxWidth().sysuiResTag("brightness_slider")) {
+ BrightnessSlider(
+ gammaValue = gamma,
+ valueRange = viewModel.minBrightness.value..viewModel.maxBrightness.value,
+ label = viewModel.label,
+ icon = viewModel.icon,
+ restriction = restriction,
+ onRestrictedClick = viewModel::showPolicyRestrictionDialog,
+ onDrag = {
+ viewModel.setIsDragging(true)
+ coroutineScope.launch { viewModel.onDrag(Drag.Dragging(GammaBrightness(it))) }
+ },
+ onStop = {
+ viewModel.setIsDragging(false)
+ coroutineScope.launch { viewModel.onDrag(Drag.Stopped(GammaBrightness(it))) }
+ },
+ modifier =
+ Modifier.then(if (viewModel.showMirror) Modifier.drawInOverlay() else Modifier)
+ .sliderBackground(containerColor)
+ .fillMaxWidth(),
+ formatter = viewModel::formatValue,
+ hapticsViewModelFactory = viewModel.hapticsViewModelFactory,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
index 074ac50..a61ce8f 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
@@ -16,36 +16,50 @@
package com.android.systemui.brightness.ui.viewmodel
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
import com.android.systemui.brightness.domain.interactor.BrightnessPolicyEnforcementInteractor
import com.android.systemui.brightness.domain.interactor.ScreenBrightnessInteractor
import com.android.systemui.brightness.shared.model.GammaBrightness
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
import com.android.systemui.utils.PolicyRestriction
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.stateIn
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
-@SysUISingleton
+/**
+ * View Model for a brightness slider.
+ *
+ * If this brightness slider supports mirroring (show on top of current activity while dragging),
+ * then:
+ * * [showMirror] will be true while dragging
+ * * [BrightnessMirrorShowingInteractor.isShowing] will track if the mirror should show (for (other
+ * parts of SystemUI to act accordingly).
+ */
class BrightnessSliderViewModel
-@Inject
+@AssistedInject
constructor(
private val screenBrightnessInteractor: ScreenBrightnessInteractor,
private val brightnessPolicyEnforcementInteractor: BrightnessPolicyEnforcementInteractor,
- @Application private val applicationScope: CoroutineScope,
val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
-) {
- val currentBrightness =
- screenBrightnessInteractor.gammaBrightness.stateIn(
- applicationScope,
- SharingStarted.WhileSubscribed(),
+ private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor,
+ @Assisted private val supportsMirroring: Boolean,
+) : ExclusiveActivatable() {
+
+ private val hydrator = Hydrator("BrightnessSliderViewModel.hydrator")
+
+ val currentBrightness by
+ hydrator.hydratedStateOf(
+ "currentBrightness",
GammaBrightness(0),
+ screenBrightnessInteractor.gammaBrightness,
)
val maxBrightness = screenBrightnessInteractor.maxGammaBrightness
@@ -82,8 +96,26 @@
// This is not finalized UI so using fixed string
return "$percentage%"
}
+
+ fun setIsDragging(dragging: Boolean) {
+ brightnessMirrorShowingInteractor.setMirrorShowing(dragging && supportsMirroring)
+ }
+
+ val showMirror by
+ hydrator.hydratedStateOf("showMirror", brightnessMirrorShowingInteractor.isShowing)
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(supportsMirroring: Boolean): BrightnessSliderViewModel
+ }
}
+fun BrightnessSliderViewModel.Factory.create() = create(supportsMirroring = true)
+
/** Represents a drag event in a brightness slider. */
sealed interface Drag {
val brightness: GammaBrightness
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
index 572794d..cf80b7d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
@@ -36,7 +36,7 @@
/** Converts from span to communal content size. */
fun toSize(span: Int): CommunalContentSize {
return entries.find { it.span == span }
- ?: throw Exception("Invalid span for communal content size")
+ ?: throw IllegalArgumentException("$span is not a valid span size")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index bf93469..609b733 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -72,6 +72,7 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
import com.android.systemui.statusbar.dagger.StartCentralSurfacesModule;
+import com.android.systemui.statusbar.notification.dagger.ReferenceNotificationsModule;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeServiceHost;
import com.android.systemui.statusbar.phone.HeadsUpModule;
@@ -145,6 +146,7 @@
QSModule.class,
RearDisplayModule.class,
RecentsModule.class,
+ ReferenceNotificationsModule.class,
ReferenceScreenshotModule.class,
RotationLockModule.class,
RotationLockNewModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 6fb6236..4cdf286 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -29,7 +29,7 @@
import com.android.systemui.dagger.qualifiers.PerUser
import com.android.systemui.dreams.AssistantAttentionMonitor
import com.android.systemui.dreams.DreamMonitor
-import com.android.systemui.dreams.homecontrols.HomeControlsDreamStartable
+import com.android.systemui.dreams.homecontrols.system.HomeControlsDreamStartable
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.haptics.msdl.MSDLCoreStartable
import com.android.systemui.keyboard.KeyboardUI
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 59c8f06..cb649f2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -70,6 +70,9 @@
import com.android.systemui.inputmethod.InputMethodModule;
import com.android.systemui.keyboard.KeyboardModule;
import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule;
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
import com.android.systemui.keyguard.ui.composable.LockscreenContent;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.log.dagger.MonitorLog;
@@ -227,6 +230,7 @@
InputMethodModule.class,
KeyEventRepositoryModule.class,
KeyboardModule.class,
+ KeyguardDataQuickAffordanceModule.class,
LetterboxModule.class,
LogModule.class,
MediaProjectionActivitiesModule.class,
@@ -428,6 +432,11 @@
sysuiUiBgExecutor));
}
+ @Provides
+ static KeyguardQuickAffordancesMetricsLogger providesKeyguardQuickAffordancesMetricsLogger() {
+ return new KeyguardQuickAffordancesMetricsLoggerImpl();
+ }
+
@Binds
abstract FgsManagerController bindFgsManagerController(FgsManagerControllerImpl impl);
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
index 782bce4..41a59a9 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -19,10 +19,12 @@
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.util.kotlin.FlowDumperImpl
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -46,16 +48,17 @@
class DeviceEntryHapticsInteractor
@Inject
constructor(
- deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
- deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
- deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor,
- fingerprintPropertyRepository: FingerprintPropertyRepository,
biometricSettingsRepository: BiometricSettingsRepository,
+ deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor,
+ deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+ deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
keyEventInteractor: KeyEventInteractor,
+ private val logger: BiometricUnlockLogger,
powerInteractor: PowerInteractor,
private val systemClock: SystemClock,
- private val logger: BiometricUnlockLogger,
-) {
+ dumpManager: DumpManager,
+) : FlowDumperImpl(dumpManager) {
private val powerButtonSideFpsEnrolled =
combineTransform(
fingerprintPropertyRepository.sensorType,
@@ -86,7 +89,7 @@
powerButtonSideFpsEnrolled,
powerButtonDown,
lastPowerButtonWakeup,
- ::Triple
+ ::Triple,
)
)
.filter { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
@@ -100,14 +103,19 @@
}
allowHaptic
}
- .map {} // map to Unit
+ // map to Unit
+ .map {}
+ .dumpWhileCollecting("playSuccessHaptic")
private val playErrorHapticForBiometricFailure: Flow<Unit> =
merge(
deviceEntryFingerprintAuthInteractor.fingerprintFailure,
deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure,
)
- .map {} // map to Unit
+ // map to Unit
+ .map {}
+ .dumpWhileCollecting("playErrorHapticForBiometricFailure")
+
val playErrorHaptic: Flow<Unit> =
playErrorHapticForBiometricFailure
.sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair))
@@ -118,7 +126,9 @@
}
allowHaptic
}
- .map {} // map to Unit
+ // map to Unit
+ .map {}
+ .dumpWhileCollecting("playErrorHaptic")
private val recentPowerButtonPressThresholdMs = 400L
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
index 0b9336f..b2d4405 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
@@ -16,18 +16,49 @@
package com.android.systemui.deviceentry.domain.interactor
+import androidx.annotation.VisibleForTesting
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardBypassInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_ONLY_WAKE
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_SHOW_BOUNCER
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
+import com.android.systemui.statusbar.phone.DozeScrimController
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
+import com.android.systemui.util.kotlin.combine
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
/**
* Hosts application business logic related to the source of the user entering the device. Note: The
@@ -42,13 +73,184 @@
class DeviceEntrySourceInteractor
@Inject
constructor(
+ authenticationInteractor: AuthenticationInteractor,
+ authController: AuthController,
+ alternateBouncerInteractor: AlternateBouncerInteractor,
+ deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+ dozeScrimController: DozeScrimController,
+ keyguardBypassInteractor: KeyguardBypassInteractor,
+ keyguardUpdateMonitor: KeyguardUpdateMonitor,
keyguardInteractor: KeyguardInteractor,
-) {
+ sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor,
+ sceneInteractor: SceneInteractor,
+ dumpManager: DumpManager,
+) : FlowDumperImpl(dumpManager) {
+ private val isShowingBouncerScene: Flow<Boolean> =
+ sceneInteractor.transitionState
+ .map { transitionState ->
+ transitionState.isIdle(Scenes.Bouncer) ||
+ transitionState.isTransitioning(null, Scenes.Bouncer)
+ }
+ .distinctUntilChanged()
+ .dumpWhileCollecting("isShowingBouncerScene")
+
+ private val isUnlockedWithStrongFaceUnlock =
+ deviceEntryFaceAuthInteractor.authenticationStatus
+ .map { status ->
+ (status as? SuccessFaceAuthenticationStatus)?.successResult?.isStrongBiometric
+ ?: false
+ }
+ .dumpWhileCollecting("unlockedWithStrongFaceUnlock")
+
+ private val isUnlockedWithStrongFingerprintUnlock =
+ deviceEntryFingerprintAuthInteractor.authenticationStatus
+ .map { status ->
+ (status as? SuccessFingerprintAuthenticationStatus)?.isStrongBiometric ?: false
+ }
+ .dumpWhileCollecting("unlockedWithStrongFingerprintUnlock")
+
+ private val faceWakeAndUnlockMode: Flow<BiometricUnlockMode> =
+ combine(
+ alternateBouncerInteractor.isVisible,
+ keyguardBypassInteractor.isBypassAvailable,
+ isUnlockedWithStrongFaceUnlock,
+ sceneContainerOcclusionInteractor.isOccludingActivityShown,
+ sceneInteractor.currentScene,
+ isShowingBouncerScene,
+ ) {
+ isAlternateBouncerVisible,
+ isBypassAvailable,
+ isFaceStrongBiometric,
+ isOccluded,
+ currentScene,
+ isShowingBouncerScene ->
+ val isUnlockingAllowed =
+ keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(isFaceStrongBiometric)
+ val bypass = isBypassAvailable || authController.isUdfpsFingerDown()
+
+ when {
+ !keyguardUpdateMonitor.isDeviceInteractive ->
+ when {
+ !isUnlockingAllowed -> if (bypass) MODE_SHOW_BOUNCER else MODE_NONE
+ bypass -> MODE_WAKE_AND_UNLOCK_PULSING
+ else -> MODE_ONLY_WAKE
+ }
+
+ isUnlockingAllowed && currentScene == Scenes.Dream ->
+ if (bypass) MODE_WAKE_AND_UNLOCK_FROM_DREAM else MODE_ONLY_WAKE
+
+ isUnlockingAllowed && isOccluded -> MODE_UNLOCK_COLLAPSING
+
+ (isShowingBouncerScene || isAlternateBouncerVisible) && isUnlockingAllowed ->
+ MODE_DISMISS_BOUNCER
+
+ isUnlockingAllowed && bypass -> MODE_UNLOCK_COLLAPSING
+
+ bypass -> MODE_SHOW_BOUNCER
+
+ else -> MODE_NONE
+ }
+ }
+ .map { biometricModeIntToObject(it) }
+ .dumpWhileCollecting("faceWakeAndUnlockMode")
+
+ private val fingerprintWakeAndUnlockMode: Flow<BiometricUnlockMode> =
+ combine(
+ alternateBouncerInteractor.isVisible,
+ authenticationInteractor.authenticationMethod,
+ sceneInteractor.currentScene,
+ isUnlockedWithStrongFingerprintUnlock,
+ isShowingBouncerScene,
+ ) {
+ alternateBouncerVisible,
+ authenticationMethod,
+ currentScene,
+ isFingerprintStrongBiometric,
+ isShowingBouncerScene ->
+ val unlockingAllowed =
+ keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ isFingerprintStrongBiometric
+ )
+ when {
+ !keyguardUpdateMonitor.isDeviceInteractive ->
+ when {
+ dozeScrimController.isPulsing && unlockingAllowed ->
+ MODE_WAKE_AND_UNLOCK_PULSING
+
+ unlockingAllowed || !authenticationMethod.isSecure ->
+ MODE_WAKE_AND_UNLOCK
+
+ else -> MODE_SHOW_BOUNCER
+ }
+
+ unlockingAllowed && currentScene == Scenes.Dream ->
+ MODE_WAKE_AND_UNLOCK_FROM_DREAM
+
+ isShowingBouncerScene && unlockingAllowed -> MODE_DISMISS_BOUNCER
+
+ unlockingAllowed -> MODE_UNLOCK_COLLAPSING
+
+ currentScene != Scenes.Bouncer && !alternateBouncerVisible -> MODE_SHOW_BOUNCER
+
+ else -> MODE_NONE
+ }
+ }
+ .map { biometricModeIntToObject(it) }
+ .dumpWhileCollecting("fingerprintWakeAndUnlockMode")
+
+ @VisibleForTesting
+ private val biometricUnlockStateOnKeyguardDismissed =
+ merge(
+ fingerprintWakeAndUnlockMode
+ .filter { BiometricUnlockMode.dismissesKeyguard(it) }
+ .map { mode ->
+ BiometricUnlockModel(mode, BiometricUnlockSource.FINGERPRINT_SENSOR)
+ },
+ faceWakeAndUnlockMode
+ .filter { BiometricUnlockMode.dismissesKeyguard(it) }
+ .map { mode -> BiometricUnlockModel(mode, BiometricUnlockSource.FACE_SENSOR) },
+ )
+ .dumpWhileCollecting("biometricUnlockState")
+
+ private val deviceEntryFingerprintAuthWakeAndUnlockEvents:
+ Flow<SuccessFingerprintAuthenticationStatus> =
+ deviceEntryFingerprintAuthInteractor.authenticationStatus
+ .filterIsInstance<SuccessFingerprintAuthenticationStatus>()
+ .dumpWhileCollecting("deviceEntryFingerprintAuthSuccessEvents")
+
+ private val deviceEntryFaceAuthWakeAndUnlockEvents: Flow<SuccessFaceAuthenticationStatus> =
+ deviceEntryFaceAuthInteractor.authenticationStatus
+ .filterIsInstance<SuccessFaceAuthenticationStatus>()
+ .sampleFilter(
+ combine(
+ sceneContainerOcclusionInteractor.isOccludingActivityShown,
+ keyguardBypassInteractor.isBypassAvailable,
+ keyguardBypassInteractor.canBypass,
+ ::Triple,
+ )
+ ) { (isOccludingActivityShown, isBypassAvailable, canBypass) ->
+ isOccludingActivityShown || !isBypassAvailable || canBypass
+ }
+ .dumpWhileCollecting("deviceEntryFaceAuthSuccessEvents")
+
+ private val deviceEntryBiometricAuthSuccessEvents =
+ merge(deviceEntryFingerprintAuthWakeAndUnlockEvents, deviceEntryFaceAuthWakeAndUnlockEvents)
+ .dumpWhileCollecting("deviceEntryBiometricAuthSuccessEvents")
+
val deviceEntryFromBiometricSource: Flow<BiometricUnlockSource> =
- keyguardInteractor.biometricUnlockState
- .filter { BiometricUnlockMode.dismissesKeyguard(it.mode) }
- .map { it.source }
- .filterNotNull()
+ if (SceneContainerFlag.isEnabled) {
+ deviceEntryBiometricAuthSuccessEvents
+ .sample(biometricUnlockStateOnKeyguardDismissed)
+ .map { it.source }
+ .filterNotNull()
+ } else {
+ keyguardInteractor.biometricUnlockState
+ .filter { BiometricUnlockMode.dismissesKeyguard(it.mode) }
+ .map { it.source }
+ .filterNotNull()
+ }
+ .dumpWhileCollecting("deviceEntryFromBiometricSource")
private val attemptEnterDeviceFromDeviceEntryIcon: MutableSharedFlow<Unit> = MutableSharedFlow()
val deviceEntryFromDeviceEntryIcon: Flow<Unit> =
@@ -60,4 +262,18 @@
suspend fun attemptEnterDeviceFromDeviceEntryIcon() {
attemptEnterDeviceFromDeviceEntryIcon.emit(Unit)
}
+
+ private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockMode {
+ return when (value) {
+ MODE_NONE -> BiometricUnlockMode.NONE
+ MODE_WAKE_AND_UNLOCK -> BiometricUnlockMode.WAKE_AND_UNLOCK
+ MODE_WAKE_AND_UNLOCK_PULSING -> BiometricUnlockMode.WAKE_AND_UNLOCK_PULSING
+ MODE_SHOW_BOUNCER -> BiometricUnlockMode.SHOW_BOUNCER
+ MODE_ONLY_WAKE -> BiometricUnlockMode.ONLY_WAKE
+ MODE_UNLOCK_COLLAPSING -> BiometricUnlockMode.UNLOCK_COLLAPSING
+ MODE_WAKE_AND_UNLOCK_FROM_DREAM -> BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM
+ MODE_DISMISS_BOUNCER -> BiometricUnlockMode.DISMISS_BOUNCER
+ else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index a20556c..15631f8 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.content.Intent
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -34,6 +35,7 @@
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.res.R
import com.android.systemui.util.kotlin.combine
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -48,7 +50,6 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Business logic for handling authentication events when an app is occluding the lockscreen. */
@ExperimentalCoroutinesApi
@@ -123,19 +124,28 @@
.ifKeyguardOccludedByApp(/* elseFlow */ flowOf(null))
init {
- scope.launch {
- // On fingerprint success when the screen is on and not dreaming, go to the home screen
- fingerprintUnlockSuccessEvents
- .sample(
- combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair)
- )
- .collect { (interactive, dreaming) ->
- if (interactive && !dreaming) {
- goToHomeScreen()
+ // This seems undesirable in most cases, except when a video is playing and can PiP when
+ // unlocked. It was originally added for tablets, so allow it there
+ if (context.resources.getBoolean(R.bool.config_goToHomeFromOccludedApps)) {
+ scope.launch {
+ // On fingerprint success when the screen is on and not dreaming, go to the home
+ // screen
+ fingerprintUnlockSuccessEvents
+ .sample(
+ combine(
+ powerInteractor.isInteractive,
+ keyguardInteractor.isDreaming,
+ ::Pair,
+ )
+ )
+ .collect { (interactive, dreaming) ->
+ if (interactive && !dreaming) {
+ goToHomeScreen()
+ }
+ // don't go to the home screen if the authentication is from
+ // AOD/dozing/off/dreaming
}
- // don't go to the home screen if the authentication is from
- // AOD/dozing/off/dreaming
- }
+ }
}
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt
index 7253621..80eb9ee 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt
@@ -19,7 +19,9 @@
import android.annotation.SuppressLint
import android.content.Context
import android.view.Display
+import android.view.LayoutInflater
import android.view.WindowManager
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -30,9 +32,7 @@
import com.google.common.collect.Table
import java.io.PrintWriter
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Provides per display instances of [DisplayWindowProperties]. */
interface DisplayWindowPropertiesRepository {
@@ -55,6 +55,7 @@
@Background private val backgroundApplicationScope: CoroutineScope,
private val globalContext: Context,
private val globalWindowManager: WindowManager,
+ private val globalLayoutInflater: LayoutInflater,
private val displayRepository: DisplayRepository,
) : DisplayWindowPropertiesRepository, CoreStartable {
@@ -93,12 +94,14 @@
windowType = windowType,
context = globalContext,
windowManager = globalWindowManager,
+ layoutInflater = globalLayoutInflater,
)
} else {
val context = createWindowContext(display, windowType)
@SuppressLint("NonInjectedService") // Need to manually get the service
val windowManager = context.getSystemService(WindowManager::class.java) as WindowManager
- DisplayWindowProperties(displayId, windowType, context, windowManager)
+ val layoutInflater = LayoutInflater.from(context)
+ DisplayWindowProperties(displayId, windowType, context, windowManager, layoutInflater)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt b/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt
index 6acc296..3f1c088 100644
--- a/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt
@@ -17,6 +17,7 @@
package com.android.systemui.display.shared.model
import android.content.Context
+import android.view.LayoutInflater
import android.view.WindowManager
/** Represents a display specific group of window related properties. */
@@ -40,4 +41,7 @@
* associated with this instance.
*/
val windowManager: WindowManager,
+
+ /** The [LayoutInflater] to be used with the associated [Context]. */
+ val layoutInflater: LayoutInflater,
)
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index a45ad15..3171bbc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -33,9 +33,10 @@
import com.android.systemui.dreams.DreamOverlayService;
import com.android.systemui.dreams.SystemDialogsCloser;
import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
-import com.android.systemui.dreams.homecontrols.DreamServiceDelegate;
-import com.android.systemui.dreams.homecontrols.DreamServiceDelegateImpl;
import com.android.systemui.dreams.homecontrols.HomeControlsDreamService;
+import com.android.systemui.dreams.homecontrols.dagger.HomeControlsDataSourceModule;
+import com.android.systemui.dreams.homecontrols.dagger.HomeControlsRemoteServiceComponent;
+import com.android.systemui.dreams.homecontrols.system.HomeControlsRemoteService;
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.pipeline.shared.TileSpec;
import com.android.systemui.qs.shared.model.TileCategory;
@@ -61,13 +62,15 @@
* Dagger Module providing Dream-related functionality.
*/
@Module(includes = {
- RegisteredComplicationsModule.class,
- LowLightDreamModule.class,
- ScrimModule.class
- },
+ RegisteredComplicationsModule.class,
+ LowLightDreamModule.class,
+ ScrimModule.class,
+ HomeControlsDataSourceModule.class,
+},
subcomponents = {
- ComplicationComponent.class,
- DreamOverlayComponent.class,
+ ComplicationComponent.class,
+ DreamOverlayComponent.class,
+ HomeControlsRemoteServiceComponent.class,
})
public interface DreamModule {
String DREAM_ONLY_ENABLED_FOR_DOCK_USER = "dream_only_enabled_for_dock_user";
@@ -113,6 +116,15 @@
HomeControlsDreamService service);
/**
+ * Provides Home Controls Remote Service
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(HomeControlsRemoteService.class)
+ Service bindHomeControlsRemoteService(
+ HomeControlsRemoteService service);
+
+ /**
* Provides a touch inset manager for dreams.
*/
@Provides
@@ -202,10 +214,4 @@
QSTilePolicy.NoRestrictions.INSTANCE
);
}
-
-
- /** Provides delegate to allow for testing of dream service */
- @Binds
- DreamServiceDelegate bindDreamDelegate(DreamServiceDelegateImpl impl);
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt
deleted file mode 100644
index 2cfb02e..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.dreams.homecontrols
-
-import android.app.Activity
-import android.service.dreams.DreamService
-
-/** Provides abstraction for [DreamService] methods, so they can be mocked in tests. */
-interface DreamServiceDelegate {
- /** Wrapper for [DreamService.getActivity] which can be mocked in tests. */
- fun getActivity(dreamService: DreamService): Activity?
-
- /** Wrapper for [DreamService.wakeUp] which can be mocked in tests. */
- fun wakeUp(dreamService: DreamService)
-
- /** Wrapper for [DreamService.finish] which can be mocked in tests. */
- fun finish(dreamService: DreamService)
-
- /** Wrapper for [DreamService.getRedirectWake] which can be mocked in tests. */
- fun redirectWake(dreamService: DreamService): Boolean
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt
deleted file mode 100644
index 7dc5434..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.dreams.homecontrols
-
-import android.app.Activity
-import android.service.dreams.DreamService
-import javax.inject.Inject
-
-class DreamServiceDelegateImpl @Inject constructor() : DreamServiceDelegate {
- override fun getActivity(dreamService: DreamService): Activity {
- return dreamService.activity
- }
-
- override fun finish(dreamService: DreamService) {
- dreamService.finish()
- }
-
- override fun wakeUp(dreamService: DreamService) {
- dreamService.wakeUp()
- }
-
- override fun redirectWake(dreamService: DreamService): Boolean {
- return dreamService.redirectWake
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
index 6b14ebf..9eec1b6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -16,75 +16,109 @@
package com.android.systemui.dreams.homecontrols
+import android.annotation.SuppressLint
import android.content.Intent
import android.os.PowerManager
import android.service.controls.ControlsProviderService
import android.service.dreams.DreamService
import android.window.TaskFragmentInfo
-import com.android.systemui.controls.settings.ControlsSettingsRepository
-import com.android.systemui.coroutines.newTracingContext
-import com.android.systemui.dagger.qualifiers.Background
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ServiceLifecycleDispatcher
+import androidx.lifecycle.lifecycleScope
import com.android.systemui.dreams.DreamLogger
-import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
-import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
+import com.android.systemui.dreams.homecontrols.service.TaskFragmentComponent
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsDataSource
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.DreamLog
+import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.wakelock.WakeLock
-import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
-import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+/**
+ * [DreamService] which embeds the user's chosen home controls app to allow it to display as a
+ * screensaver. This service will run in the foreground user context.
+ */
class HomeControlsDreamService
@Inject
-constructor(
- private val controlsSettingsRepository: ControlsSettingsRepository,
- private val taskFragmentFactory: TaskFragmentComponent.Factory,
- private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
- private val wakeLockBuilder: WakeLock.Builder,
- private val dreamServiceDelegate: DreamServiceDelegate,
- @Background private val bgDispatcher: CoroutineDispatcher,
- @DreamLog logBuffer: LogBuffer
-) : DreamService() {
+constructor(private val factory: HomeControlsDreamServiceImpl.Factory) :
+ DreamService(), LifecycleOwner {
- private val serviceJob = SupervisorJob()
- private val serviceScope =
- CoroutineScope(bgDispatcher + serviceJob + newTracingContext("HomeControlsDreamService"))
+ private val dispatcher = ServiceLifecycleDispatcher(this)
+ override val lifecycle: Lifecycle
+ get() = dispatcher.lifecycle
+
+ private val impl: HomeControlsDreamServiceImpl by lazy { factory.create(this, this) }
+
+ override fun onCreate() {
+ dispatcher.onServicePreSuperOnCreate()
+ super.onCreate()
+ }
+
+ override fun onDreamingStarted() {
+ dispatcher.onServicePreSuperOnStart()
+ super.onDreamingStarted()
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ impl.onAttachedToWindow()
+ }
+
+ override fun onDetachedFromWindow() {
+ dispatcher.onServicePreSuperOnDestroy()
+ super.onDetachedFromWindow()
+ impl.onDetachedFromWindow()
+ }
+}
+
+/**
+ * Implementation of the home controls dream service, which allows for injecting a [DreamService]
+ * and [LifecycleOwner] for testing.
+ */
+class HomeControlsDreamServiceImpl
+@AssistedInject
+constructor(
+ private val taskFragmentFactory: TaskFragmentComponent.Factory,
+ private val wakeLockBuilder: WakeLock.Builder,
+ private val powerManager: PowerManager,
+ private val systemClock: SystemClock,
+ private val dataSource: HomeControlsDataSource,
+ @DreamLog logBuffer: LogBuffer,
+ @Assisted private val service: DreamService,
+ @Assisted lifecycleOwner: LifecycleOwner,
+) : LifecycleOwner by lifecycleOwner {
+
private val logger = DreamLogger(logBuffer, TAG)
private lateinit var taskFragmentComponent: TaskFragmentComponent
private val wakeLock: WakeLock by lazy {
wakeLockBuilder
- .setMaxTimeout(NO_TIMEOUT)
+ .setMaxTimeout(WakeLock.Builder.NO_TIMEOUT)
.setTag(TAG)
.setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
.build()
}
- override fun onAttachedToWindow() {
- super.onAttachedToWindow()
- val activity = dreamServiceDelegate.getActivity(this)
+ fun onAttachedToWindow() {
+ val activity = service.activity
if (activity == null) {
- finish()
+ service.finish()
return
}
-
- // Start monitoring package updates to possibly restart the dream if the home controls
- // package is updated while we are dreaming.
- serviceScope.launch { homeControlsComponentInteractor.monitorUpdatesAndRestart() }
-
taskFragmentComponent =
taskFragmentFactory
.create(
activity = activity,
onCreateCallback = { launchActivity() },
onInfoChangedCallback = this::onTaskFragmentInfoChanged,
- hide = { endDream(false) }
+ hide = { endDream(false) },
)
.apply { createTaskFragment() }
@@ -99,53 +133,61 @@
}
private fun endDream(handleRedirect: Boolean) {
- homeControlsComponentInteractor.onDreamEndUnexpectedly()
- if (handleRedirect && dreamServiceDelegate.redirectWake(this)) {
- dreamServiceDelegate.wakeUp(this)
- serviceScope.launch {
+ pokeUserActivity()
+ if (handleRedirect && service.redirectWake) {
+ service.wakeUp()
+ lifecycleScope.launch {
delay(ACTIVITY_RESTART_DELAY)
launchActivity()
}
} else {
- dreamServiceDelegate.finish(this)
+ service.finish()
}
}
private fun launchActivity() {
- val setting = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
- val componentName = homeControlsComponentInteractor.panelComponent.value
- logger.d("Starting embedding $componentName")
- val intent =
- Intent().apply {
- component = componentName
- putExtra(ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, setting)
- putExtra(
- ControlsProviderService.EXTRA_CONTROLS_SURFACE,
- ControlsProviderService.CONTROLS_SURFACE_DREAM
- )
- }
- taskFragmentComponent.startActivityInTaskFragment(intent)
- }
-
- override fun onDetachedFromWindow() {
- super.onDetachedFromWindow()
- wakeLock.release(TAG)
- taskFragmentComponent.destroy()
- serviceScope.launch {
- delay(CANCELLATION_DELAY_AFTER_DETACHED)
- serviceJob.cancel("Dream detached from window")
+ lifecycleScope.launch {
+ val (componentName, setting) = dataSource.componentInfo.first()
+ logger.d("Starting embedding $componentName")
+ val intent =
+ Intent().apply {
+ component = componentName
+ putExtra(
+ ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
+ setting,
+ )
+ putExtra(
+ ControlsProviderService.EXTRA_CONTROLS_SURFACE,
+ ControlsProviderService.CONTROLS_SURFACE_DREAM,
+ )
+ }
+ taskFragmentComponent.startActivityInTaskFragment(intent)
}
}
- private companion object {
- /**
- * Defines how long after the dream ends that we should keep monitoring for package updates
- * to attempt a restart of the dream. This should be larger than
- * [MAX_UPDATE_CORRELATION_DELAY] as it also includes the time the package update takes to
- * complete.
- */
- val CANCELLATION_DELAY_AFTER_DETACHED = 5.seconds
+ fun onDetachedFromWindow() {
+ wakeLock.release(TAG)
+ taskFragmentComponent.destroy()
+ }
+ @SuppressLint("MissingPermission")
+ private fun pokeUserActivity() {
+ powerManager.userActivity(
+ systemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_OTHER,
+ PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS,
+ )
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ service: DreamService,
+ lifecycleOwner: LifecycleOwner,
+ ): HomeControlsDreamServiceImpl
+ }
+
+ companion object {
/**
* Defines the delay after wakeup where we should attempt to restart the embedded activity.
* When a wakeup is redirected, the dream service may keep running. In this case, we should
@@ -153,6 +195,6 @@
* after the wakeup transition has played.
*/
val ACTIVITY_RESTART_DELAY = 334.milliseconds
- const val TAG = "HomeControlsDreamService"
+ private const val TAG = "HomeControlsDreamServiceImpl"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/dagger/HomeControlsDataSourceModule.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/dagger/HomeControlsDataSourceModule.kt
new file mode 100644
index 0000000..3a2791f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/dagger/HomeControlsDataSourceModule.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dreams.homecontrols.dagger
+
+import com.android.systemui.Flags.homeControlsDreamHsum
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dreams.homecontrols.service.RemoteHomeControlsDataSourceDelegator
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsDataSource
+import com.android.systemui.dreams.homecontrols.system.LocalHomeControlsDataSourceDelegator
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+
+@Module
+interface HomeControlsDataSourceModule {
+ companion object {
+ @Provides
+ @SysUISingleton
+ fun providesHomeControlsDataSource(
+ localSource: Lazy<LocalHomeControlsDataSourceDelegator>,
+ remoteSource: Lazy<RemoteHomeControlsDataSourceDelegator>,
+ ): HomeControlsDataSource {
+ return if (homeControlsDreamHsum()) {
+ remoteSource.get()
+ } else {
+ localSource.get()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/dagger/HomeControlsRemoteServiceComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/dagger/HomeControlsRemoteServiceComponent.kt
new file mode 100644
index 0000000..500d15e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/dagger/HomeControlsRemoteServiceComponent.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dreams.homecontrols.dagger
+
+import android.content.Context
+import android.content.Intent
+import android.os.IBinder
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dreams.homecontrols.service.HomeControlsRemoteProxy
+import com.android.systemui.dreams.homecontrols.shared.IHomeControlsRemoteProxy
+import com.android.systemui.dreams.homecontrols.system.HomeControlsRemoteService
+import com.android.systemui.util.service.ObservableServiceConnection
+import com.android.systemui.util.service.Observer
+import com.android.systemui.util.service.PersistentConnectionManager
+import com.android.systemui.util.service.dagger.ObservableServiceModule
+import dagger.BindsInstance
+import dagger.Module
+import dagger.Provides
+import dagger.Subcomponent
+import javax.inject.Named
+
+/**
+ * This component is responsible for generating the connection to the home controls remote service
+ * which runs in the SYSTEM_USER context and provides the data needed to run the home controls dream
+ * in the foreground user context.
+ */
+@Subcomponent(
+ modules =
+ [
+ ObservableServiceModule::class,
+ HomeControlsRemoteServiceComponent.HomeControlsRemoteServiceModule::class,
+ ]
+)
+interface HomeControlsRemoteServiceComponent {
+ /** Creates a [HomeControlsRemoteServiceComponent]. */
+ @Subcomponent.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance callback: ObservableServiceConnection.Callback<HomeControlsRemoteProxy>
+ ): HomeControlsRemoteServiceComponent
+ }
+
+ /** A [PersistentConnectionManager] pointing to the home controls remote service. */
+ val connectionManager: PersistentConnectionManager<HomeControlsRemoteProxy>
+
+ /** Scoped module providing specific components for the [ObservableServiceConnection]. */
+ @Module
+ interface HomeControlsRemoteServiceModule {
+ companion object {
+ @Provides
+ @Named(ObservableServiceModule.SERVICE_CONNECTION)
+ fun providesConnection(
+ connection: ObservableServiceConnection<HomeControlsRemoteProxy>,
+ callback: ObservableServiceConnection.Callback<HomeControlsRemoteProxy>,
+ ): ObservableServiceConnection<HomeControlsRemoteProxy> {
+ connection.addCallback(callback)
+ return connection
+ }
+
+ /** Provides the wrapper around the home controls remote binder */
+ @Provides
+ fun providesTransformer(
+ factory: HomeControlsRemoteProxy.Factory
+ ): ObservableServiceConnection.ServiceTransformer<HomeControlsRemoteProxy> {
+ return ObservableServiceConnection.ServiceTransformer { service: IBinder ->
+ factory.create(IHomeControlsRemoteProxy.Stub.asInterface(service))
+ }
+ }
+
+ /** Provides the intent to connect to [HomeControlsRemoteService] */
+ @Provides
+ fun providesIntent(@Application context: Context): Intent {
+ return Intent(context, HomeControlsRemoteService::class.java)
+ }
+
+ /** Provides no-op [Observer] since the remote service is in the same package */
+ @Provides
+ @Named(ObservableServiceModule.OBSERVER)
+ fun providesObserver(): Observer {
+ return object : Observer {
+ override fun addCallback(callback: Observer.Callback?) {
+ // no-op, do nothing
+ }
+
+ override fun removeCallback(callback: Observer.Callback?) {
+ // no-op, do nothing
+ }
+ }
+ }
+
+ /**
+ * Provides a name that will be used by [PersistentConnectionManager] when logging
+ * state.
+ */
+ @Provides
+ @Named(ObservableServiceModule.DUMPSYS_NAME)
+ fun providesDumpsysName(): String {
+ return "HomeControlsRemoteService"
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
deleted file mode 100644
index 2034138..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.dreams.homecontrols.domain.interactor
-
-import android.annotation.SuppressLint
-import android.app.DreamManager
-import android.content.ComponentName
-import android.os.PowerManager
-import android.os.UserHandle
-import com.android.systemui.common.domain.interactor.PackageChangeInteractor
-import com.android.systemui.common.shared.model.PackageChangeModel
-import com.android.systemui.controls.ControlsServiceInfo
-import com.android.systemui.controls.dagger.ControlsComponent
-import com.android.systemui.controls.management.ControlsListingController
-import com.android.systemui.controls.panels.AuthorizedPanelsRepository
-import com.android.systemui.controls.panels.SelectedComponentRepository
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.kotlin.getOrNull
-import com.android.systemui.util.kotlin.pairwiseBy
-import com.android.systemui.util.kotlin.sample
-import com.android.systemui.util.time.SystemClock
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
-import javax.inject.Inject
-import kotlin.math.abs
-import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.BufferOverflow
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.stateIn
-
-@SysUISingleton
-@OptIn(ExperimentalCoroutinesApi::class)
-class HomeControlsComponentInteractor
-@Inject
-constructor(
- private val selectedComponentRepository: SelectedComponentRepository,
- controlsComponent: ControlsComponent,
- authorizedPanelsRepository: AuthorizedPanelsRepository,
- userRepository: UserRepository,
- private val packageChangeInteractor: PackageChangeInteractor,
- private val systemClock: SystemClock,
- private val powerManager: PowerManager,
- private val dreamManager: DreamManager,
- @Background private val bgScope: CoroutineScope
-) {
- private val controlsListingController: ControlsListingController? =
- controlsComponent.getControlsListingController().getOrNull()
-
- /** Gets the current user's selected panel, or null if there isn't one */
- private val selectedPanel: Flow<SelectedComponentRepository.SelectedComponent?> =
- userRepository.selectedUserInfo
- .flatMapLatest { user ->
- selectedComponentRepository.selectedComponentFlow(user.userHandle)
- }
- .map { if (it?.isPanel == true) it else null }
-
- /** Gets the current user's authorized panels */
- private val allAuthorizedPanels: Flow<Set<String>> =
- userRepository.selectedUserInfo.flatMapLatest { user ->
- authorizedPanelsRepository.observeAuthorizedPanels(user.userHandle)
- }
-
- /** Gets all the available services from [ControlsListingController] */
- private fun allAvailableServices(): Flow<List<ControlsServiceInfo>> {
- if (controlsListingController == null) {
- return emptyFlow()
- }
- return conflatedCallbackFlow {
- val listener =
- object : ControlsListingController.ControlsListingCallback {
- override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
- trySend(serviceInfos)
- }
- }
- controlsListingController.addCallback(listener)
- awaitClose { controlsListingController.removeCallback(listener) }
- }
- .onStart { emit(controlsListingController.getCurrentServices()) }
- }
-
- /** Gets all panels which are available and authorized by the user */
- private val allAvailableAndAuthorizedPanels: Flow<List<PanelComponent>> =
- combine(
- allAvailableServices(),
- allAuthorizedPanels,
- ) { serviceInfos, authorizedPanels ->
- serviceInfos.mapNotNull {
- val panelActivity = it.panelActivity
- if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
- PanelComponent(it.componentName, panelActivity)
- } else {
- null
- }
- }
- }
-
- val panelComponent: StateFlow<ComponentName?> =
- combine(
- allAvailableAndAuthorizedPanels,
- selectedPanel,
- ) { panels, selected ->
- val item =
- panels.firstOrNull { it.componentName == selected?.componentName }
- ?: panels.firstOrNull()
- item?.panelActivity
- }
- .stateIn(bgScope, SharingStarted.Eagerly, null)
-
- private val taskFragmentFinished =
- MutableSharedFlow<Long>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
-
- fun onDreamEndUnexpectedly() {
- powerManager.userActivity(
- systemClock.uptimeMillis(),
- PowerManager.USER_ACTIVITY_EVENT_OTHER,
- PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS,
- )
- taskFragmentFinished.tryEmit(systemClock.currentTimeMillis())
- }
-
- /**
- * Monitors if the current home panel package is updated and causes the dream to finish, and
- * attempts to restart the dream in this case.
- */
- @SuppressLint("MissingPermission")
- suspend fun monitorUpdatesAndRestart() {
- taskFragmentFinished.resetReplayCache()
- panelComponent
- .flatMapLatest { component ->
- if (component == null) return@flatMapLatest emptyFlow()
- packageChangeInteractor.packageChanged(UserHandle.CURRENT, component.packageName)
- }
- .filter { it.isUpdate() }
- // Wait for an UpdatedStarted - UpdateFinished pair to ensure the update has finished.
- .pairwiseBy(::validateUpdatePair)
- .filterNotNull()
- .sample(taskFragmentFinished, ::Pair)
- .filter { (updateStarted, lastFinishedTimestamp) ->
- abs(updateStarted.timeMillis - lastFinishedTimestamp) <=
- MAX_UPDATE_CORRELATION_DELAY.inWholeMilliseconds
- }
- .collect { dreamManager.startDream() }
- }
-
- private data class PanelComponent(
- val componentName: ComponentName,
- val panelActivity: ComponentName,
- )
-
- companion object {
- /**
- * The maximum delay between a package update **starting** and the task fragment finishing
- * which causes us to correlate the package update as the cause of the task fragment
- * finishing.
- */
- val MAX_UPDATE_CORRELATION_DELAY = 500.milliseconds
- }
-}
-
-private fun PackageChangeModel.isUpdate() =
- this is PackageChangeModel.UpdateStarted || this is PackageChangeModel.UpdateFinished
-
-private fun validateUpdatePair(
- updateStarted: PackageChangeModel,
- updateFinished: PackageChangeModel
-): PackageChangeModel.UpdateStarted? =
- when {
- !updateStarted.isSamePackage(updateFinished) -> null
- updateStarted !is PackageChangeModel.UpdateStarted -> null
- updateFinished !is PackageChangeModel.UpdateFinished -> null
- else -> updateStarted
- }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
new file mode 100644
index 0000000..2bcfea8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dreams.homecontrols.service
+
+import android.content.ComponentName
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dreams.homecontrols.shared.IHomeControlsRemoteProxy
+import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.stateIn
+
+/** Class to wrap [IHomeControlsRemoteProxy], which exposes the current user's home controls info */
+class HomeControlsRemoteProxy
+@AssistedInject
+constructor(
+ @Background bgScope: CoroutineScope,
+ dumpManager: DumpManager,
+ @Assisted private val proxy: IHomeControlsRemoteProxy,
+) : FlowDumperImpl(dumpManager) {
+
+ private companion object {
+ const val TAG = "HomeControlsRemoteProxy"
+ }
+
+ val componentInfo: Flow<HomeControlsComponentInfo> =
+ conflatedCallbackFlow {
+ val listener =
+ object : IOnControlsSettingsChangeListener.Stub() {
+ override fun onControlsSettingsChanged(
+ panelComponent: ComponentName?,
+ allowTrivialControlsOnLockscreen: Boolean,
+ ) {
+ trySendWithFailureLogging(
+ HomeControlsComponentInfo(
+ panelComponent,
+ allowTrivialControlsOnLockscreen,
+ ),
+ TAG,
+ )
+ }
+ }
+ proxy.registerListenerForCurrentUser(listener)
+ awaitClose { proxy.unregisterListenerForCurrentUser(listener) }
+ }
+ .distinctUntilChanged()
+ .stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
+ .dumpValue("componentInfo")
+ .filterNotNull()
+
+ @AssistedFactory
+ interface Factory {
+ fun create(proxy: IHomeControlsRemoteProxy): HomeControlsRemoteProxy
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegator.kt
new file mode 100644
index 0000000..b14903d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegator.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dreams.homecontrols.service
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dreams.DreamLogger
+import com.android.systemui.dreams.homecontrols.dagger.HomeControlsRemoteServiceComponent
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsDataSource
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.dagger.DreamLog
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import com.android.systemui.util.service.ObservableServiceConnection
+import com.android.systemui.util.service.PersistentConnectionManager
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.dropWhile
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Queries a remote service for [HomeControlsComponentInfo] necessary to show the home controls
+ * dream.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class RemoteHomeControlsDataSourceDelegator
+@Inject
+constructor(
+ @Background bgScope: CoroutineScope,
+ serviceFactory: HomeControlsRemoteServiceComponent.Factory,
+ @DreamLog logBuffer: LogBuffer,
+ dumpManager: DumpManager,
+) : HomeControlsDataSource, FlowDumperImpl(dumpManager) {
+ private val logger = DreamLogger(logBuffer, TAG)
+
+ private val connectionManager: PersistentConnectionManager<HomeControlsRemoteProxy> by lazy {
+ serviceFactory.create(callback).connectionManager
+ }
+
+ private val proxyState =
+ MutableStateFlow<HomeControlsRemoteProxy?>(null)
+ .apply {
+ subscriptionCount
+ .map { it > 0 }
+ .dropWhile { !it }
+ .distinctUntilChanged()
+ .onEach { active ->
+ logger.d({ "Remote service connection active: $bool1" }) { bool1 = active }
+ if (active) {
+ connectionManager.start()
+ } else {
+ connectionManager.stop()
+ }
+ }
+ .launchIn(bgScope)
+ }
+ .dumpValue("proxyState")
+
+ private val callback: ObservableServiceConnection.Callback<HomeControlsRemoteProxy> =
+ object : ObservableServiceConnection.Callback<HomeControlsRemoteProxy> {
+ override fun onConnected(
+ connection: ObservableServiceConnection<HomeControlsRemoteProxy>?,
+ proxy: HomeControlsRemoteProxy,
+ ) {
+ logger.d("Service connected")
+ proxyState.value = proxy
+ }
+
+ override fun onDisconnected(
+ connection: ObservableServiceConnection<HomeControlsRemoteProxy>?,
+ reason: Int,
+ ) {
+ logger.d({ "Service disconnected with reason $int1" }) { int1 = reason }
+ proxyState.value = null
+ }
+ }
+
+ override val componentInfo: Flow<HomeControlsComponentInfo> =
+ proxyState
+ .filterNotNull()
+ .flatMapLatest { proxy: HomeControlsRemoteProxy -> proxy.componentInfo }
+ .dumpWhileCollecting("componentInfo")
+
+ private companion object {
+ const val TAG = "HomeControlsRemoteDataSourceDelegator"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/TaskFragmentComponent.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
rename to packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/TaskFragmentComponent.kt
index d547de2..67de30c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/TaskFragmentComponent.kt
@@ -14,29 +14,18 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.homecontrols
+package com.android.systemui.dreams.homecontrols.service
import android.app.Activity
-import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration
import android.content.Intent
import android.graphics.Rect
import android.os.Binder
import android.window.TaskFragmentCreationParams
import android.window.TaskFragmentInfo
import android.window.TaskFragmentOperation
-import android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT
-import android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK
import android.window.TaskFragmentOrganizer
-import android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE
-import android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE
-import android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN
import android.window.TaskFragmentTransaction
-import android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK
-import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED
-import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR
-import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED
-import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED
-import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED
import android.window.WindowContainerTransaction
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -65,7 +54,7 @@
activity: Activity,
@Assisted("onCreateCallback") onCreateCallback: FragmentInfoCallback,
@Assisted("onInfoChangedCallback") onInfoChangedCallback: FragmentInfoCallback,
- hide: () -> Unit
+ hide: () -> Unit,
): TaskFragmentComponent
}
@@ -90,26 +79,28 @@
change.taskFragmentInfo?.let { taskFragmentInfo ->
if (taskFragmentInfo.fragmentToken == fragmentToken) {
when (change.type) {
- TYPE_TASK_FRAGMENT_APPEARED -> {
+ TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED -> {
resultT.addTaskFragmentOperation(
fragmentToken,
- TaskFragmentOperation.Builder(OP_TYPE_REORDER_TO_TOP_OF_TASK)
- .build()
+ TaskFragmentOperation.Builder(
+ TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK
+ )
+ .build(),
)
onCreateCallback(taskFragmentInfo)
}
- TYPE_TASK_FRAGMENT_INFO_CHANGED -> {
+ TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED -> {
onInfoChangedCallback(taskFragmentInfo)
}
- TYPE_TASK_FRAGMENT_VANISHED -> {
+ TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED -> {
hide()
}
- TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED -> {}
- TYPE_TASK_FRAGMENT_ERROR -> {
+ TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED -> {}
+ TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR -> {
hide()
}
- TYPE_ACTIVITY_REPARENTED_TO_TASK -> {}
+ TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK -> {}
else ->
throw IllegalArgumentException(
"Unknown TaskFragmentEvent=" + change.type
@@ -121,8 +112,8 @@
organizer.onTransactionHandled(
transaction.transactionToken,
resultT,
- TASK_FRAGMENT_TRANSIT_CHANGE,
- false
+ TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+ false,
)
}
@@ -132,15 +123,15 @@
TaskFragmentCreationParams.Builder(
organizer.organizerToken,
fragmentToken,
- activity.activityToken!!
+ activity.activityToken!!,
)
.setInitialRelativeBounds(Rect())
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
.build()
organizer.applyTransaction(
WindowContainerTransaction().createTaskFragment(fragmentOptions),
- TASK_FRAGMENT_TRANSIT_CHANGE,
- false
+ TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+ false,
)
}
@@ -151,8 +142,8 @@
fun startActivityInTaskFragment(intent: Intent) {
organizer.applyTransaction(
WindowContainerTransaction().startActivity(intent),
- TASK_FRAGMENT_TRANSIT_OPEN,
- false
+ TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN,
+ false,
)
}
@@ -162,10 +153,13 @@
WindowContainerTransaction()
.addTaskFragmentOperation(
fragmentToken,
- TaskFragmentOperation.Builder(OP_TYPE_DELETE_TASK_FRAGMENT).build()
+ TaskFragmentOperation.Builder(
+ TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT
+ )
+ .build(),
),
- TASK_FRAGMENT_TRANSIT_CLOSE,
- false
+ TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE,
+ false,
)
organizer.unregisterOrganizer()
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxy.aidl b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxy.aidl
new file mode 100644
index 0000000..115b62c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxy.aidl
@@ -0,0 +1,9 @@
+package com.android.systemui.dreams.homecontrols.shared;
+
+import android.os.IRemoteCallback;
+import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener;
+
+oneway interface IHomeControlsRemoteProxy {
+ void registerListenerForCurrentUser(in IOnControlsSettingsChangeListener callback);
+ void unregisterListenerForCurrentUser(in IOnControlsSettingsChangeListener callback);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IOnControlsSettingsChangeListener.aidl b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IOnControlsSettingsChangeListener.aidl
new file mode 100644
index 0000000..99e5fae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IOnControlsSettingsChangeListener.aidl
@@ -0,0 +1,7 @@
+package com.android.systemui.dreams.homecontrols.shared;
+
+import android.content.ComponentName;
+
+oneway interface IOnControlsSettingsChangeListener {
+ void onControlsSettingsChanged(in ComponentName panelComponent, boolean allowTrivialControlsOnLockscreen);
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsComponentInfo.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsComponentInfo.kt
index 06592b1..b9e5080 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsComponentInfo.kt
@@ -14,13 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.qs.ui.viewmodel
+package com.android.systemui.dreams.homecontrols.shared.model
-import com.android.systemui.kosmos.Kosmos
+import android.content.ComponentName
-val Kosmos.quickSettingsShadeUserActionsViewModel: QuickSettingsShadeUserActionsViewModel by
- Kosmos.Fixture {
- QuickSettingsShadeUserActionsViewModel(
- quickSettingsContainerViewModel = quickSettingsContainerViewModel,
- )
- }
+data class HomeControlsComponentInfo(
+ val componentName: ComponentName?,
+ val allowTrivialControlsOnLockscreen: Boolean,
+)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsDataSource.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsDataSource.kt
index 06592b1..8187c54 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsDataSource.kt
@@ -14,13 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.qs.ui.viewmodel
+package com.android.systemui.dreams.homecontrols.shared.model
-import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.flow.Flow
-val Kosmos.quickSettingsShadeUserActionsViewModel: QuickSettingsShadeUserActionsViewModel by
- Kosmos.Fixture {
- QuickSettingsShadeUserActionsViewModel(
- quickSettingsContainerViewModel = quickSettingsContainerViewModel,
- )
- }
+/** Source of data for home controls dream to get the necessary information it needs. */
+interface HomeControlsDataSource {
+ val componentInfo: Flow<HomeControlsComponentInfo>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsDreamStartable.kt
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
rename to packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsDreamStartable.kt
index 03f58ac..644d5fd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsDreamStartable.kt
@@ -14,24 +14,28 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.homecontrols
+package com.android.systemui.dreams.homecontrols.system
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.service.controls.flags.Flags.homePanelDream
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.homeControlsDreamHsum
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.dreams.homecontrols.HomeControlsDreamService
+import com.android.systemui.dreams.homecontrols.system.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.settings.UserContextProvider
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
class HomeControlsDreamStartable
@Inject
constructor(
context: Context,
- private val packageManager: PackageManager,
+ private val systemPackageManager: PackageManager,
+ private val userContextProvider: UserContextProvider,
private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
@Background private val bgScope: CoroutineScope,
) : CoreStartable {
@@ -57,10 +61,16 @@
} else {
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
}
+ val packageManager =
+ if (homeControlsDreamHsum()) {
+ userContextProvider.userContext.packageManager
+ } else {
+ systemPackageManager
+ }
packageManager.setComponentEnabledSetting(
componentName,
packageState,
- PackageManager.DONT_KILL_APP
+ PackageManager.DONT_KILL_APP,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt
new file mode 100644
index 0000000..a65d216
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dreams.homecontrols.system
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.IBinder
+import android.os.RemoteCallbackList
+import android.os.RemoteException
+import android.util.Log
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleService
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.controls.settings.ControlsSettingsRepository
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dreams.DreamLogger
+import com.android.systemui.dreams.homecontrols.shared.IHomeControlsRemoteProxy
+import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener
+import com.android.systemui.dreams.homecontrols.system.domain.interactor.HomeControlsComponentInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.dagger.DreamLog
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.atomic.AtomicInteger
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.launch
+
+/**
+ * Service which exports the current home controls component name, for use in SystemUI processes
+ * running in other users. This service should only run in the system user.
+ */
+class HomeControlsRemoteService
+@Inject
+constructor(binderFactory: HomeControlsRemoteServiceBinder.Factory) : LifecycleService() {
+ val binder by lazy { binderFactory.create(this) }
+
+ override fun onBind(intent: Intent): IBinder? {
+ super.onBind(intent)
+ return binder
+ }
+}
+
+class HomeControlsRemoteServiceBinder
+@AssistedInject
+constructor(
+ private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
+ private val controlsSettingsRepository: ControlsSettingsRepository,
+ @Background private val bgContext: CoroutineContext,
+ @DreamLog logBuffer: LogBuffer,
+ @Assisted lifecycleOwner: LifecycleOwner,
+) : IHomeControlsRemoteProxy.Stub(), LifecycleOwner by lifecycleOwner {
+ private val logger = DreamLogger(logBuffer, TAG)
+ private val callbacks =
+ object : RemoteCallbackList<IOnControlsSettingsChangeListener>() {
+ override fun onCallbackDied(listener: IOnControlsSettingsChangeListener?) {
+ if (callbackCount.decrementAndGet() == 0) {
+ logger.d("Cancelling collection due to callback death")
+ collectionJob?.cancel()
+ collectionJob = null
+ }
+ }
+ }
+ private val callbackCount = AtomicInteger(0)
+ private var collectionJob: Job? = null
+
+ override fun registerListenerForCurrentUser(listener: IOnControlsSettingsChangeListener?) {
+ if (listener == null) return
+ logger.d("Register listener")
+ val registered = callbacks.register(listener)
+ if (registered && callbackCount.getAndIncrement() == 0) {
+ // If the first listener, start the collection job. This will also take
+ // care of notifying the listener of the initial state.
+ logger.d("Starting collection")
+ collectionJob =
+ lifecycleScope.launch(bgContext) {
+ combine(
+ homeControlsComponentInteractor.panelComponent,
+ controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen,
+ ) { panelComponent, allowTrivialControls ->
+ callbacks.notifyAllCallbacks(panelComponent, allowTrivialControls)
+ }
+ .launchIn(this)
+ }
+ } else if (registered) {
+ // If not the first listener, notify the listener of the current value immediately.
+ listener.notify(
+ homeControlsComponentInteractor.panelComponent.value,
+ controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value,
+ )
+ }
+ }
+
+ override fun unregisterListenerForCurrentUser(listener: IOnControlsSettingsChangeListener?) {
+ if (listener == null) return
+ logger.d("Unregister listener")
+ if (callbacks.unregister(listener) && callbackCount.decrementAndGet() == 0) {
+ logger.d("Cancelling collection due to unregister")
+ collectionJob?.cancel()
+ collectionJob = null
+ }
+ }
+
+ private companion object {
+ const val TAG = "HomeControlsRemoteServiceBinder"
+ }
+
+ private fun IOnControlsSettingsChangeListener.notify(
+ panelComponent: ComponentName?,
+ allowTrivialControlsOnLockscreen: Boolean,
+ ) {
+ try {
+ onControlsSettingsChanged(panelComponent, allowTrivialControlsOnLockscreen)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Error notifying callback", e)
+ }
+ }
+
+ private fun RemoteCallbackList<IOnControlsSettingsChangeListener>.notifyAllCallbacks(
+ panelComponent: ComponentName?,
+ allowTrivialControlsOnLockscreen: Boolean,
+ ) {
+ val itemCount = beginBroadcast()
+ try {
+ for (i in 0 until itemCount) {
+ getBroadcastItem(i).notify(panelComponent, allowTrivialControlsOnLockscreen)
+ }
+ } finally {
+ finishBroadcast()
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(lifecycleOwner: LifecycleOwner): HomeControlsRemoteServiceBinder
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/LocalHomeControlsDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/LocalHomeControlsDataSourceDelegator.kt
new file mode 100644
index 0000000..ca255fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/LocalHomeControlsDataSourceDelegator.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dreams.homecontrols.system
+
+import com.android.systemui.controls.settings.ControlsSettingsRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsDataSource
+import com.android.systemui.dreams.homecontrols.system.domain.interactor.HomeControlsComponentInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/**
+ * Queries local data sources for the [HomeControlsComponentInfo] necessary to show the home
+ * controls dream.
+ */
+@SysUISingleton
+class LocalHomeControlsDataSourceDelegator
+@Inject
+constructor(
+ homeControlsComponentInteractor: HomeControlsComponentInteractor,
+ controlsSettingsRepository: ControlsSettingsRepository,
+) : HomeControlsDataSource {
+ override val componentInfo: Flow<HomeControlsComponentInfo> =
+ combine(
+ homeControlsComponentInteractor.panelComponent,
+ controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen,
+ ) { panelComponent, allowActionOnTrivialControlsInLockscreen ->
+ HomeControlsComponentInfo(panelComponent, allowActionOnTrivialControlsInLockscreen)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractor.kt
new file mode 100644
index 0000000..31bd708
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractor.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dreams.homecontrols.system.domain.interactor
+
+import android.content.ComponentName
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.getOrNull
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
+class HomeControlsComponentInteractor
+@Inject
+constructor(
+ private val selectedComponentRepository: SelectedComponentRepository,
+ controlsComponent: ControlsComponent,
+ authorizedPanelsRepository: AuthorizedPanelsRepository,
+ userRepository: UserRepository,
+ @Background private val bgScope: CoroutineScope,
+) {
+ private val controlsListingController: ControlsListingController? =
+ controlsComponent.getControlsListingController().getOrNull()
+
+ /** Gets the current user's selected panel, or null if there isn't one */
+ private val selectedPanel: Flow<SelectedComponentRepository.SelectedComponent?> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { user ->
+ selectedComponentRepository.selectedComponentFlow(user.userHandle)
+ }
+ .map { if (it?.isPanel == true) it else null }
+
+ /** Gets the current user's authorized panels */
+ private val allAuthorizedPanels: Flow<Set<String>> =
+ userRepository.selectedUserInfo.flatMapLatest { user ->
+ authorizedPanelsRepository.observeAuthorizedPanels(user.userHandle)
+ }
+
+ /** Gets all the available services from [ControlsListingController] */
+ private fun allAvailableServices(): Flow<List<ControlsServiceInfo>> {
+ if (controlsListingController == null) {
+ return emptyFlow()
+ }
+ return conflatedCallbackFlow {
+ val listener =
+ object : ControlsListingController.ControlsListingCallback {
+ override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+ trySend(serviceInfos)
+ }
+ }
+ controlsListingController.addCallback(listener)
+ awaitClose { controlsListingController.removeCallback(listener) }
+ }
+ .onStart { emit(controlsListingController.getCurrentServices()) }
+ }
+
+ /** Gets all panels which are available and authorized by the user */
+ private val allAvailableAndAuthorizedPanels: Flow<List<PanelComponent>> =
+ combine(allAvailableServices(), allAuthorizedPanels) { serviceInfos, authorizedPanels ->
+ serviceInfos.mapNotNull {
+ val panelActivity = it.panelActivity
+ if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
+ PanelComponent(it.componentName, panelActivity)
+ } else {
+ null
+ }
+ }
+ }
+
+ val panelComponent: StateFlow<ComponentName?> =
+ combine(allAvailableAndAuthorizedPanels, selectedPanel) { panels, selected ->
+ val item =
+ panels.firstOrNull { it.componentName == selected?.componentName }
+ ?: panels.firstOrNull()
+ item?.panelActivity
+ }
+ .stateIn(bgScope, SharingStarted.Eagerly, null)
+
+ private data class PanelComponent(
+ val componentName: ComponentName,
+ val panelActivity: ComponentName,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
index 7905950..ed7d182 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
@@ -38,7 +38,7 @@
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.transform
-/** A view-model to trigger haptics feedback on Quick Settings tiles */
+/** A view-model to trigger haptic feedback on Quick Settings tiles */
@OptIn(ExperimentalCoroutinesApi::class)
class TileHapticsViewModel
@AssistedInject
@@ -149,7 +149,7 @@
onActivityLaunchTransitionEnd = ::onActivityLaunchTransitionEnd,
)
- /** Models the state of toggle haptics to play */
+ /** Models the state of haptics to play */
enum class TileHapticsState {
TOGGLE_ON,
TOGGLE_OFF,
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
index d8d4bd6..a89ec70 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
@@ -17,7 +17,6 @@
package com.android.systemui.inputdevice.tutorial.data.repository
import android.content.Context
-import androidx.annotation.VisibleForTesting
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
@@ -37,12 +36,12 @@
class TutorialSchedulerRepository(
private val applicationContext: Context,
backgroundScope: CoroutineScope,
- dataStoreName: String
+ dataStoreName: String,
) {
@Inject
constructor(
@Application applicationContext: Context,
- @Background backgroundScope: CoroutineScope
+ @Background backgroundScope: CoroutineScope,
) : this(applicationContext, backgroundScope, dataStoreName = DATASTORE_NAME)
private val Context.dataStore: DataStore<Preferences> by
@@ -73,7 +72,7 @@
private fun getSchedulerInfo(pref: Preferences): Map<DeviceType, DeviceSchedulerInfo> {
return mapOf(
DeviceType.KEYBOARD to getDeviceSchedulerInfo(pref, DeviceType.KEYBOARD),
- DeviceType.TOUCHPAD to getDeviceSchedulerInfo(pref, DeviceType.TOUCHPAD)
+ DeviceType.TOUCHPAD to getDeviceSchedulerInfo(pref, DeviceType.TOUCHPAD),
)
}
@@ -89,8 +88,7 @@
private fun getConnectKey(device: DeviceType) =
longPreferencesKey(device.name + CONNECT_TIME_SUFFIX)
- @VisibleForTesting
- suspend fun clearDataStore() {
+ suspend fun clear() {
applicationContext.dataStore.edit { it.clear() }
}
@@ -103,5 +101,5 @@
enum class DeviceType {
KEYBOARD,
- TOUCHPAD
+ TOUCHPAD,
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
index 3b4d00d..4a369e7 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
@@ -24,7 +24,10 @@
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD
import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.touchpad.data.repository.TouchpadRepository
+import java.io.PrintWriter
import java.time.Duration
import java.time.Instant
import javax.inject.Inject
@@ -37,6 +40,7 @@
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.runBlocking
/**
* When the first time a keyboard or touchpad is connected, wait for [LAUNCH_DELAY], and as soon as
@@ -50,7 +54,12 @@
touchpadRepository: TouchpadRepository,
private val repo: TutorialSchedulerRepository,
private val logger: InputDeviceTutorialLogger,
+ commandRegistry: CommandRegistry,
) {
+ init {
+ commandRegistry.registerCommand(COMMAND) { TutorialCommand() }
+ }
+
private val isAnyDeviceConnected =
mapOf(
KEYBOARD to keyboardRepository.isAnyKeyboardConnected,
@@ -118,8 +127,40 @@
return LAUNCH_DELAY.minus(elapsed).toKotlinDuration()
}
+ inner class TutorialCommand : Command {
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ if (args.isEmpty()) {
+ help(pw)
+ return
+ }
+ when (args[0]) {
+ "clear" ->
+ runBlocking {
+ repo.clear()
+ pw.println("Tutorial scheduler reset")
+ }
+ "info" ->
+ runBlocking {
+ pw.println("Keyboard connect time = ${repo.firstConnectionTime(KEYBOARD)}")
+ pw.println(" launch time = ${repo.launchTime(KEYBOARD)}")
+ pw.println("Touchpad connect time = ${repo.firstConnectionTime(TOUCHPAD)}")
+ pw.println(" launch time = ${repo.launchTime(TOUCHPAD)}")
+ }
+ else -> help(pw)
+ }
+ }
+
+ override fun help(pw: PrintWriter) {
+ pw.println("Usage: adb shell cmd statusbar $COMMAND <command>")
+ pw.println("Available commands:")
+ pw.println(" clear")
+ pw.println(" info")
+ }
+ }
+
companion object {
const val TAG = "TutorialSchedulerInteractor"
+ const val COMMAND = "peripheral_tutorial"
private val DEFAULT_LAUNCH_DELAY_SEC = 72.hours.inWholeSeconds
private val LAUNCH_DELAY: Duration
get() =
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
index 6dd56de..1b044de 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
@@ -77,7 +77,11 @@
}
}
val buttonAlpha by animateFloatAsState(if (actionState is Finished) 1f else 0f)
- DoneButton(onDoneButtonClicked, Modifier.graphicsLayer { alpha = buttonAlpha })
+ DoneButton(
+ onDoneButtonClicked = onDoneButtonClicked,
+ modifier = Modifier.graphicsLayer { alpha = buttonAlpha },
+ enabled = actionState is Finished,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialComponents.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialComponents.kt
index 01ad585..202dba3 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialComponents.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialComponents.kt
@@ -28,13 +28,17 @@
import com.android.systemui.res.R
@Composable
-fun DoneButton(onDoneButtonClicked: () -> Unit, modifier: Modifier = Modifier) {
+fun DoneButton(
+ onDoneButtonClicked: () -> Unit,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+) {
Row(
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
- modifier = modifier.fillMaxWidth()
+ modifier = modifier.fillMaxWidth(),
) {
- Button(onClick = onDoneButtonClicked) {
+ Button(onClick = onDoneButtonClicked, enabled = enabled) {
Text(stringResource(R.string.touchpad_tutorial_done_button))
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 5cade68..d537056 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -52,8 +52,10 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.OpenInNew
+import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.Search
+import androidx.compose.material.icons.filled.Tune
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
@@ -69,6 +71,7 @@
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -169,6 +172,7 @@
selectedCategoryType,
onCategorySelected = { selectedCategoryType = it },
onKeyboardSettingsClicked,
+ shortcutsUiState.isShortcutCustomizerFlagEnabled,
)
}
}
@@ -357,10 +361,29 @@
selectedCategoryType: ShortcutCategoryType?,
onCategorySelected: (ShortcutCategoryType?) -> Unit,
onKeyboardSettingsClicked: () -> Unit,
+ isShortcutCustomizerFlagEnabled: Boolean,
) {
val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType }
+ var isCustomizeModeEntered by remember { mutableStateOf(false) }
+ val isCustomizing by
+ remember(isCustomizeModeEntered, isShortcutCustomizerFlagEnabled) {
+ derivedStateOf { isCustomizeModeEntered && isCustomizeModeEntered }
+ }
+
Column(modifier = modifier.fillMaxSize().padding(horizontal = 24.dp)) {
- TitleBar()
+ Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
+ Box(modifier = Modifier.padding(start = 202.dp).width(412.dp)) {
+ TitleBar(isCustomizing)
+ }
+ Spacer(modifier = Modifier.weight(1f))
+ if (isShortcutCustomizerFlagEnabled) {
+ if (isCustomizeModeEntered) {
+ DoneButton(onClick = { isCustomizeModeEntered = false })
+ } else {
+ CustomizeButton(onClick = { isCustomizeModeEntered = true })
+ }
+ }
+ }
Spacer(modifier = Modifier.height(12.dp))
Row(Modifier.fillMaxWidth()) {
StartSidePanel(
@@ -372,13 +395,46 @@
onCategoryClicked = { onCategorySelected(it.type) },
)
Spacer(modifier = Modifier.width(24.dp))
- EndSidePanel(searchQuery, Modifier.fillMaxSize().padding(top = 8.dp), selectedCategory)
+ EndSidePanel(
+ searchQuery,
+ Modifier.fillMaxSize().padding(top = 8.dp),
+ selectedCategory,
+ isCustomizing = isCustomizing,
+ )
}
}
}
@Composable
-private fun EndSidePanel(searchQuery: String, modifier: Modifier, category: ShortcutCategoryUi?) {
+private fun CustomizeButton(onClick: () -> Unit) {
+ ShortcutHelperButton(
+ onClick = onClick,
+ color = MaterialTheme.colorScheme.secondaryContainer,
+ width = 133.dp,
+ iconSource = IconSource(imageVector = Icons.Default.Tune),
+ text = stringResource(id = R.string.shortcut_helper_customize_button_text),
+ contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
+ )
+}
+
+@Composable
+private fun DoneButton(onClick: () -> Unit) {
+ ShortcutHelperButton(
+ onClick = onClick,
+ color = MaterialTheme.colorScheme.primary,
+ width = 69.dp,
+ text = stringResource(R.string.shortcut_helper_done_button_text),
+ contentColor = MaterialTheme.colorScheme.onPrimary,
+ )
+}
+
+@Composable
+private fun EndSidePanel(
+ searchQuery: String,
+ modifier: Modifier,
+ category: ShortcutCategoryUi?,
+ isCustomizing: Boolean,
+) {
val listState = rememberLazyListState()
LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) }
if (category == null) {
@@ -387,7 +443,11 @@
}
LazyColumn(modifier = modifier, state = listState) {
items(category.subCategories) { subcategory ->
- SubCategoryContainerDualPane(searchQuery = searchQuery, subCategory = subcategory)
+ SubCategoryContainerDualPane(
+ searchQuery = searchQuery,
+ subCategory = subcategory,
+ isCustomizing = isCustomizing,
+ )
Spacer(modifier = Modifier.height(8.dp))
}
}
@@ -412,7 +472,11 @@
}
@Composable
-private fun SubCategoryContainerDualPane(searchQuery: String, subCategory: ShortcutSubCategory) {
+private fun SubCategoryContainerDualPane(
+ searchQuery: String,
+ subCategory: ShortcutSubCategory,
+ isCustomizing: Boolean,
+) {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(28.dp),
@@ -432,6 +496,7 @@
modifier = Modifier.padding(vertical = 8.dp),
searchQuery = searchQuery,
shortcut = shortcut,
+ isCustomizing = isCustomizing,
)
}
}
@@ -448,7 +513,12 @@
}
@Composable
-private fun Shortcut(modifier: Modifier, searchQuery: String, shortcut: ShortcutModel) {
+private fun Shortcut(
+ modifier: Modifier,
+ searchQuery: String,
+ shortcut: ShortcutModel,
+ isCustomizing: Boolean = false,
+) {
val interactionSource = remember { MutableInteractionSource() }
val isFocused by interactionSource.collectIsFocusedAsState()
val focusColor = MaterialTheme.colorScheme.secondary
@@ -471,7 +541,7 @@
ShortcutDescriptionText(searchQuery = searchQuery, shortcut = shortcut)
}
Spacer(modifier = Modifier.width(24.dp))
- ShortcutKeyCombinations(modifier = Modifier.weight(1f), shortcut = shortcut)
+ ShortcutKeyCombinations(modifier = Modifier.weight(1f), shortcut = shortcut, isCustomizing)
}
}
@@ -495,7 +565,11 @@
@OptIn(ExperimentalLayoutApi::class)
@Composable
-private fun ShortcutKeyCombinations(modifier: Modifier = Modifier, shortcut: ShortcutModel) {
+private fun ShortcutKeyCombinations(
+ modifier: Modifier = Modifier,
+ shortcut: ShortcutModel,
+ isCustomizing: Boolean = false,
+) {
FlowRow(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp),
@@ -507,6 +581,25 @@
}
ShortcutCommand(command)
}
+ if (isCustomizing) {
+ Spacer(modifier = Modifier.width(16.dp))
+ ShortcutHelperButton(
+ modifier =
+ Modifier.border(
+ width = 1.dp,
+ color = MaterialTheme.colorScheme.outline,
+ shape = CircleShape,
+ ),
+ onClick = {},
+ color = Color.Transparent,
+ width = 32.dp,
+ height = 32.dp,
+ iconSource = IconSource(imageVector = Icons.Default.Add),
+ contentColor = MaterialTheme.colorScheme.primary,
+ contentPaddingVertical = 0.dp,
+ contentPaddingHorizontal = 0.dp,
+ )
+ }
}
}
@@ -700,12 +793,18 @@
@Composable
@OptIn(ExperimentalMaterial3Api::class)
-private fun TitleBar() {
+private fun TitleBar(isCustomizing: Boolean = false) {
+ val text =
+ if (isCustomizing) {
+ stringResource(R.string.shortcut_helper_customize_mode_title)
+ } else {
+ stringResource(R.string.shortcut_helper_title)
+ }
CenterAlignedTopAppBar(
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color.Transparent),
title = {
Text(
- text = stringResource(R.string.shortcut_helper_title),
+ text = text,
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.headlineSmall,
)
@@ -753,14 +852,12 @@
@Composable
private fun KeyboardSettings(horizontalPadding: Dp, verticalPadding: Dp, onClick: () -> Unit) {
- val interactionSource = remember { MutableInteractionSource() }
ClickableShortcutSurface(
onClick = onClick,
shape = RoundedCornerShape(24.dp),
color = Color.Transparent,
modifier =
Modifier.semantics { role = Role.Button }.fillMaxWidth().padding(horizontal = 12.dp),
- interactionSource = interactionSource,
interactionsConfig =
InteractionsConfig(
hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
index f64d59a..435968e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
@@ -27,13 +27,24 @@
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.interaction.collectIsFocusedAsState
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ColorScheme
+import androidx.compose.material3.Icon
import androidx.compose.material3.LocalAbsoluteTonalElevation
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTonalElevationEnabled
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.material3.surfaceColorAtElevation
@@ -43,6 +54,7 @@
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
@@ -57,11 +69,16 @@
import androidx.compose.ui.node.DelegatableNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
-import com.android.compose.modifiers.thenIf
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.compose.modifiers.thenIf
+import com.android.systemui.keyboard.shortcut.ui.model.IconSource
/**
* A selectable surface with no default focus/hover indications.
@@ -175,6 +192,96 @@
}
}
+/**
+ * A composable that provides a button with a customizable icon and text, designed to be re-used
+ * across shortcut helper/customizer. Supports defaults hover/focus/pressed states used across
+ * shortcut helper.
+ *
+ * This button utilizes [ClickableShortcutSurface] to provide a clickable surface with hover and
+ * pressed states, and a focus outline.
+ *
+ * The content of the button can be an icon (from [IconSource]) and/or text.
+ *
+ * @param modifier The modifier to be applied to the button.
+ * @param onClick The callback function that will be invoked when the button is clicked.
+ * @param shape The shape of the button. Defaults to a rounded corner shape used across shortcut
+ * helper.
+ * @param color The background color of the button.
+ * @param width The width of the button.
+ * @param height The height of the button. Defaults to 40.dp as often used in shortcut helper
+ * @param iconSource The source of the icon to be displayed. Defaults to an empty [IconSource].
+ * @param text The text to be displayed. Defaults to null.
+ * @param contentColor The color of the icon and text.
+ * @param contentPaddingHorizontal The horizontal padding of the content. Defaults to 16.dp.
+ * @param contentPaddingVertical The vertical padding of the content. Defaults to 10.dp.
+ */
+@Composable
+fun ShortcutHelperButton(
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit,
+ shape: Shape = RoundedCornerShape(360.dp),
+ color: Color,
+ width: Dp,
+ height: Dp = 40.dp,
+ iconSource: IconSource = IconSource(),
+ text: String? = null,
+ contentColor: Color,
+ contentPaddingHorizontal: Dp = 16.dp,
+ contentPaddingVertical: Dp = 10.dp,
+) {
+ ClickableShortcutSurface(
+ onClick = onClick,
+ shape = shape,
+ color = color,
+ modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
+ interactionsConfig =
+ InteractionsConfig(
+ hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
+ hoverOverlayAlpha = 0.11f,
+ pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
+ pressedOverlayAlpha = 0.15f,
+ focusOutlineColor = MaterialTheme.colorScheme.secondary,
+ focusOutlineStrokeWidth = 3.dp,
+ focusOutlinePadding = 2.dp,
+ surfaceCornerRadius = 28.dp,
+ focusOutlineCornerRadius = 33.dp,
+ ),
+ ) {
+ Row(
+ modifier =
+ Modifier.padding(
+ horizontal = contentPaddingHorizontal,
+ vertical = contentPaddingVertical,
+ ),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ if (iconSource.imageVector != null) {
+ Icon(
+ tint = contentColor,
+ imageVector = iconSource.imageVector,
+ contentDescription = null,
+ modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
+ )
+ }
+
+ if (iconSource.imageVector != null && text != null) {
+ Spacer(modifier = Modifier.weight(1f))
+ }
+
+ if (text != null) {
+ Text(
+ text,
+ color = contentColor,
+ fontSize = 14.sp,
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.wrapContentSize(Alignment.Center),
+ )
+ }
+ }
+ }
+}
+
@Composable
private fun surfaceColorAtElevation(color: Color, elevation: Dp): Color {
return MaterialTheme.colorScheme.applyTonalElevation(color, elevation)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
index 8f23261..02b0b43 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
@@ -24,6 +24,7 @@
val searchQuery: String,
val shortcutCategories: List<ShortcutCategoryUi>,
val defaultSelectedCategory: ShortcutCategoryType?,
+ val isShortcutCustomizerFlagEnabled: Boolean = false,
) : ShortcutsUiState
data object Inactive : ShortcutsUiState
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index 20d09ed..912bfe9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -26,6 +26,7 @@
import androidx.compose.material.icons.filled.Tv
import androidx.compose.material.icons.filled.VerticalSplit
import com.android.compose.ui.graphics.painter.DrawablePainter
+import com.android.systemui.Flags.keyboardShortcutHelperShortcutCustomizer
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
@@ -86,6 +87,7 @@
searchQuery = query,
shortcutCategories = shortcutCategoriesUi,
defaultSelectedCategory = getDefaultSelectedCategory(filteredCategories),
+ isShortcutCustomizerFlagEnabled = keyboardShortcutHelperShortcutCustomizer(),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index d0a40ec..7638079 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -61,8 +61,6 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModelModule;
import com.android.systemui.log.SessionTracker;
@@ -239,12 +237,6 @@
/** */
@Provides
- static KeyguardQuickAffordancesMetricsLogger providesKeyguardQuickAffordancesMetricsLogger() {
- return new KeyguardQuickAffordancesMetricsLoggerImpl();
- }
-
- /** */
- @Provides
@SysUISingleton
static ThreadAssert providesThreadAssert() {
return new ThreadAssert();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index e68d799..4d999df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -106,7 +106,7 @@
trySendWithFailureLogging(
getFpSensorType(),
TAG,
- "onAllAuthenticatorsRegistered, emitting fpSensorType"
+ "onAllAuthenticatorsRegistered, emitting fpSensorType",
)
}
}
@@ -114,7 +114,7 @@
trySendWithFailureLogging(
getFpSensorType(),
TAG,
- "initial value for fpSensorType"
+ "initial value for fpSensorType",
)
awaitClose { authController.removeCallback(callback) }
}
@@ -134,7 +134,7 @@
trySendWithFailureLogging(
keyguardUpdateMonitor.isFingerprintLockedOut,
TAG,
- "onLockedOutStateChanged"
+ "onLockedOutStateChanged",
)
}
val callback =
@@ -154,7 +154,7 @@
.stateIn(
scope,
started = Eagerly,
- initialValue = keyguardUpdateMonitor.isFingerprintLockedOut
+ initialValue = keyguardUpdateMonitor.isFingerprintLockedOut,
)
}
@@ -165,13 +165,13 @@
object : KeyguardUpdateMonitorCallback() {
override fun onBiometricRunningStateChanged(
running: Boolean,
- biometricSourceType: BiometricSourceType?
+ biometricSourceType: BiometricSourceType?,
) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
trySendWithFailureLogging(
running,
TAG,
- "Fingerprint running state changed"
+ "Fingerprint running state changed",
)
}
}
@@ -180,7 +180,7 @@
trySendWithFailureLogging(
keyguardUpdateMonitor.isFingerprintDetectionRunning,
TAG,
- "Initial fingerprint running state"
+ "Initial fingerprint running state",
)
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
@@ -193,11 +193,7 @@
.map { it.isEngaged }
.filterNotNull()
.map { it }
- .stateIn(
- scope = scope,
- started = WhileSubscribed(),
- initialValue = false,
- )
+ .stateIn(scope = scope, started = WhileSubscribed(), initialValue = false)
// TODO(b/322555228) Remove after consolidating device entry auth messages with BP auth messages
// in BiometricStatusRepository
@@ -232,10 +228,7 @@
) {
sendUpdateIfFingerprint(
biometricSourceType,
- ErrorFingerprintAuthenticationStatus(
- msgId,
- errString,
- ),
+ ErrorFingerprintAuthenticationStatus(msgId, errString),
)
}
@@ -246,15 +239,12 @@
) {
sendUpdateIfFingerprint(
biometricSourceType,
- HelpFingerprintAuthenticationStatus(
- msgId,
- helpString,
- ),
+ HelpFingerprintAuthenticationStatus(msgId, helpString),
)
}
override fun onBiometricAuthFailed(
- biometricSourceType: BiometricSourceType,
+ biometricSourceType: BiometricSourceType
) {
sendUpdateIfFingerprint(
biometricSourceType,
@@ -270,14 +260,14 @@
biometricSourceType,
AcquiredFingerprintAuthenticationStatus(
AuthenticationReason.DeviceEntryAuthentication,
- acquireInfo
+ acquireInfo,
),
)
}
private fun sendUpdateIfFingerprint(
biometricSourceType: BiometricSourceType,
- authenticationStatus: FingerprintAuthenticationStatus
+ authenticationStatus: FingerprintAuthenticationStatus,
) {
if (biometricSourceType != BiometricSourceType.FINGERPRINT) {
return
@@ -285,13 +275,14 @@
trySendWithFailureLogging(
authenticationStatus,
TAG,
- "new fingerprint authentication status"
+ "new fingerprint authentication status",
)
}
}
keyguardUpdateMonitor.registerCallback(callback)
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
+ .flowOn(mainDispatcher)
.buffer(capacity = 4)
override val shouldUpdateIndicatorVisibility: Flow<Boolean> =
@@ -302,7 +293,7 @@
shouldUpdateIndicatorVisibility,
TAG,
"Error sending shouldUpdateIndicatorVisibility " +
- "$shouldUpdateIndicatorVisibility"
+ "$shouldUpdateIndicatorVisibility",
)
}
@@ -310,7 +301,7 @@
object : KeyguardUpdateMonitorCallback() {
override fun onBiometricRunningStateChanged(
running: Boolean,
- biometricSourceType: BiometricSourceType?
+ biometricSourceType: BiometricSourceType?,
) {
sendShouldUpdateIndicatorVisibility(true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepository.kt
new file mode 100644
index 0000000..be4ab4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepository.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.data.repository
+
+import android.annotation.IntDef
+import android.content.res.Resources
+import android.provider.Settings
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+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.keyguard.shared.model.DevicePosture
+import com.android.systemui.keyguard.shared.model.DevicePosture.UNKNOWN
+import com.android.systemui.res.R
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class KeyguardBypassRepository
+@Inject
+constructor(
+ @Main resources: Resources,
+ biometricSettingsRepository: BiometricSettingsRepository,
+ devicePostureRepository: DevicePostureRepository,
+ dumpManager: DumpManager,
+ private val tunerService: TunerService,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+) : FlowDumperImpl(dumpManager) {
+
+ @get:BypassOverride
+ private val bypassOverride: Int by lazy {
+ resources.getInteger(R.integer.config_face_unlock_bypass_override)
+ }
+
+ private val configFaceAuthSupportedPosture: DevicePosture by lazy {
+ DevicePosture.toPosture(resources.getInteger(R.integer.config_face_auth_supported_posture))
+ }
+
+ private val dismissByDefault: Int by lazy {
+ if (resources.getBoolean(com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) {
+ 1
+ } else {
+ 0
+ }
+ }
+
+ private var bypassEnabledSetting: Flow<Boolean> =
+ callbackFlow {
+ val updateBypassSetting = { state: Boolean ->
+ trySendWithFailureLogging(state, TAG, "Error sending bypassSetting $state")
+ }
+
+ val tunable =
+ TunerService.Tunable { key, _ ->
+ updateBypassSetting(tunerService.getValue(key, dismissByDefault) != 0)
+ }
+
+ updateBypassSetting(false)
+ tunerService.addTunable(tunable, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
+ awaitClose { tunerService.removeTunable(tunable) }
+ }
+ .flowOn(backgroundDispatcher)
+ .dumpWhileCollecting("bypassEnabledSetting")
+
+ val overrideFaceBypassSetting: Flow<Boolean> =
+ when (bypassOverride) {
+ FACE_UNLOCK_BYPASS_ALWAYS -> flowOf(true)
+ FACE_UNLOCK_BYPASS_NEVER -> flowOf(false)
+ else -> bypassEnabledSetting
+ }
+
+ val isPostureAllowedForFaceAuth: Flow<Boolean> =
+ when (configFaceAuthSupportedPosture) {
+ UNKNOWN -> flowOf(true)
+ else ->
+ devicePostureRepository.currentDevicePosture
+ .map { posture -> posture == configFaceAuthSupportedPosture }
+ .distinctUntilChanged()
+ }
+
+ /**
+ * Whether bypass is available.
+ *
+ * Bypass is the ability to skip the lockscreen when the device is unlocked using non-primary
+ * authentication types like face unlock, instead of requiring the user to explicitly dismiss
+ * the lockscreen by swiping after the device is already unlocked.
+ *
+ * "Available" refers to a combination of the user setting to skip the lockscreen being set,
+ * whether hard-wired OEM-overridable configs allow the feature, whether a foldable is in the
+ * right foldable posture, and other such things. It does _not_ model this based on more
+ * runtime-like states of the UI.
+ */
+ val isBypassAvailable: Flow<Boolean> =
+ combine(
+ overrideFaceBypassSetting,
+ biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
+ isPostureAllowedForFaceAuth,
+ ) {
+ bypassOverride: Boolean,
+ isFaceEnrolledAndEnabled: Boolean,
+ isPostureAllowedForFaceAuth: Boolean ->
+ bypassOverride && isFaceEnrolledAndEnabled && isPostureAllowedForFaceAuth
+ }
+ .distinctUntilChanged()
+ .dumpWhileCollecting("isBypassAvailable")
+
+ @IntDef(FACE_UNLOCK_BYPASS_NO_OVERRIDE, FACE_UNLOCK_BYPASS_ALWAYS, FACE_UNLOCK_BYPASS_NEVER)
+ @Retention(AnnotationRetention.SOURCE)
+ private annotation class BypassOverride
+
+ companion object {
+ private const val FACE_UNLOCK_BYPASS_NO_OVERRIDE = 0
+ private const val FACE_UNLOCK_BYPASS_ALWAYS = 1
+ private const val FACE_UNLOCK_BYPASS_NEVER = 2
+
+ private const val TAG = "KeyguardBypassRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index d49550e..d0de21b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -36,12 +36,14 @@
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.kotlin.FlowDumperImpl
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -64,7 +66,14 @@
configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
dumpManager: DumpManager,
userHandle: UserHandle,
-) {
+) : FlowDumperImpl(dumpManager) {
+ /**
+ * Whether a quick affordance is being launched. Quick Affordances are interactive lockscreen UI
+ * elements that allow the user to perform quick actions without unlocking their device.
+ */
+ val launchingAffordance: MutableStateFlow<Boolean> =
+ MutableStateFlow(false).dumpValue("launchingAffordance")
+
// Configs for all keyguard quick affordances, mapped by the quick affordance ID as key
private val configsByAffordanceId: Map<String, KeyguardQuickAffordanceConfig> =
configs.associateBy { it.key }
@@ -112,11 +121,7 @@
}
}
}
- .stateIn(
- scope = scope,
- started = SharingStarted.Eagerly,
- initialValue = emptyMap(),
- )
+ .stateIn(scope = scope, started = SharingStarted.Eagerly, initialValue = emptyMap())
init {
legacySettingSyncer.startSyncing()
@@ -144,14 +149,8 @@
* Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
* IDs should be descending priority order.
*/
- fun setSelections(
- slotId: String,
- affordanceIds: List<String>,
- ) {
- selectionManager.value.setSelections(
- slotId = slotId,
- affordanceIds = affordanceIds,
- )
+ fun setSelections(slotId: String, affordanceIds: List<String>) {
+ selectionManager.value.setSelections(slotId = slotId, affordanceIds = affordanceIds)
}
/**
@@ -222,10 +221,7 @@
val (slotId, slotCapacity) = parseSlot(unparsedSlot)
check(!seenSlotIds.contains(slotId)) { "Duplicate slot \"$slotId\"!" }
seenSlotIds.add(slotId)
- KeyguardSlotPickerRepresentation(
- id = slotId,
- maxSelectedAffordances = slotCapacity,
- )
+ KeyguardSlotPickerRepresentation(id = slotId, maxSelectedAffordances = slotCapacity)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/PulseExpansionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/PulseExpansionRepository.kt
new file mode 100644
index 0000000..7699bab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/PulseExpansionRepository.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class PulseExpansionRepository @Inject constructor(dumpManager: DumpManager) :
+ FlowDumperImpl(dumpManager) {
+ /**
+ * Whether the notification panel is expanding from the user swiping downward on a notification
+ * from the pulsing state, or swiping anywhere on the screen when face bypass is enabled
+ */
+ val isPulseExpanding: MutableStateFlow<Boolean> =
+ MutableStateFlow(false).dumpValue("pulseExpanding")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index b60e98a..f3eeed2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -39,7 +39,6 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import javax.inject.Inject
@@ -52,7 +51,6 @@
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
@SysUISingleton
class FromLockscreenTransitionInteractor
@@ -176,99 +174,89 @@
if (SceneContainerFlag.isEnabled) return
var transitionId: UUID? = null
scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
- shadeRepository.legacyShadeExpansion
- .sampleCombine(
- transitionInteractor.startedKeyguardTransitionStep,
- keyguardInteractor.statusBarState,
- keyguardInteractor.isKeyguardDismissible,
- keyguardInteractor.isKeyguardOccluded,
- )
- .collect {
- (
- shadeExpansion,
- startedStep,
- statusBarState,
- isKeyguardUnlocked,
- isKeyguardOccluded) ->
- val id = transitionId
- val currentTransitionInfo =
- internalTransitionInteractor.currentTransitionInfoInternal()
- if (id != null) {
- if (startedStep.to == KeyguardState.PRIMARY_BOUNCER) {
- // An existing `id` means a transition is started, and calls to
- // `updateTransition` will control it until FINISHED or CANCELED
- var nextState =
- if (shadeExpansion == 0f) {
- TransitionState.FINISHED
- } else if (shadeExpansion == 1f) {
- TransitionState.CANCELED
- } else {
- TransitionState.RUNNING
- }
+ shadeRepository.legacyShadeExpansion.collect { shadeExpansion ->
+ val statusBarState = keyguardInteractor.statusBarState.value
+ val isKeyguardUnlocked = keyguardInteractor.isKeyguardDismissible.value
+ val isKeyguardOccluded = keyguardInteractor.isKeyguardOccluded.value
+ val startedStep = transitionInteractor.startedKeyguardTransitionStep.value
- // startTransition below will issue the CANCELED directly
- if (nextState != TransitionState.CANCELED) {
- transitionRepository.updateTransition(
- id,
- // This maps the shadeExpansion to a much faster curve, to match
- // the existing logic
- 1f -
- MathUtils.constrainedMap(0f, 1f, 0.95f, 1f, shadeExpansion),
- nextState,
- )
+ val id = transitionId
+ val currentTransitionInfo =
+ internalTransitionInteractor.currentTransitionInfoInternal()
+ if (id != null) {
+ if (startedStep.to == KeyguardState.PRIMARY_BOUNCER) {
+ // An existing `id` means a transition is started, and calls to
+ // `updateTransition` will control it until FINISHED or CANCELED
+ var nextState =
+ if (shadeExpansion == 0f) {
+ TransitionState.FINISHED
+ } else if (shadeExpansion == 1f) {
+ TransitionState.CANCELED
+ } else {
+ TransitionState.RUNNING
}
- if (
- nextState == TransitionState.CANCELED ||
- nextState == TransitionState.FINISHED
- ) {
- transitionId = null
- }
-
- // If canceled, just put the state back
- // TODO(b/278086361): This logic should happen in
- // FromPrimaryBouncerInteractor.
- if (nextState == TransitionState.CANCELED) {
- transitionRepository.startTransition(
- TransitionInfo(
- ownerName =
- "$name " +
- "(on behalf of FromPrimaryBouncerInteractor)",
- from = KeyguardState.PRIMARY_BOUNCER,
- to =
- if (isKeyguardOccluded) KeyguardState.OCCLUDED
- else KeyguardState.LOCKSCREEN,
- modeOnCanceled = TransitionModeOnCanceled.REVERSE,
- animator =
- getDefaultAnimatorForTransitionsToState(
- KeyguardState.LOCKSCREEN
- )
- .apply { duration = 100L },
- )
- )
- }
+ // startTransition below will issue the CANCELED directly
+ if (nextState != TransitionState.CANCELED) {
+ transitionRepository.updateTransition(
+ id,
+ // This maps the shadeExpansion to a much faster curve, to match
+ // the existing logic
+ 1f - MathUtils.constrainedMap(0f, 1f, 0.95f, 1f, shadeExpansion),
+ nextState,
+ )
}
- } else {
- // TODO (b/251849525): Remove statusbarstate check when that state is
- // integrated into KeyguardTransitionRepository
+
if (
- // Use currentTransitionInfo to decide whether to start the transition.
- currentTransitionInfo.to == KeyguardState.LOCKSCREEN &&
- shadeExpansion > 0f &&
- shadeExpansion < 1f &&
- shadeRepository.legacyShadeTracking.value &&
- !isKeyguardUnlocked &&
- statusBarState == KEYGUARD
+ nextState == TransitionState.CANCELED ||
+ nextState == TransitionState.FINISHED
) {
- transitionId =
- startTransitionTo(
- toState = KeyguardState.PRIMARY_BOUNCER,
- animator = null, // transition will be manually controlled,
- ownerReason = "#listenForLockscreenToPrimaryBouncerDragging",
+ transitionId = null
+ }
+
+ // If canceled, just put the state back
+ // TODO(b/278086361): This logic should happen in
+ // FromPrimaryBouncerInteractor.
+ if (nextState == TransitionState.CANCELED) {
+ transitionRepository.startTransition(
+ TransitionInfo(
+ ownerName =
+ "$name " + "(on behalf of FromPrimaryBouncerInteractor)",
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to =
+ if (isKeyguardOccluded) KeyguardState.OCCLUDED
+ else KeyguardState.LOCKSCREEN,
+ modeOnCanceled = TransitionModeOnCanceled.REVERSE,
+ animator =
+ getDefaultAnimatorForTransitionsToState(
+ KeyguardState.LOCKSCREEN
+ )
+ .apply { duration = 100L },
)
+ )
}
}
+ } else {
+ // TODO (b/251849525): Remove statusbarstate check when that state is
+ // integrated into KeyguardTransitionRepository
+ if (
+ // Use currentTransitionInfo to decide whether to start the transition.
+ currentTransitionInfo.to == KeyguardState.LOCKSCREEN &&
+ shadeExpansion > 0f &&
+ shadeExpansion < 1f &&
+ shadeRepository.legacyShadeTracking.value &&
+ !isKeyguardUnlocked &&
+ statusBarState == KEYGUARD
+ ) {
+ transitionId =
+ startTransitionTo(
+ toState = KeyguardState.PRIMARY_BOUNCER,
+ animator = null, // transition will be manually controlled,
+ ownerReason = "#listenForLockscreenToPrimaryBouncerDragging",
+ )
+ }
}
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBypassInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBypassInteractor.kt
new file mode 100644
index 0000000..d793064
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBypassInteractor.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.domain.interactor
+
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.data.repository.KeyguardBypassRepository
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import com.android.systemui.util.kotlin.combine
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class KeyguardBypassInteractor
+@Inject
+constructor(
+ keyguardBypassRepository: KeyguardBypassRepository,
+ alternateBouncerInteractor: AlternateBouncerInteractor,
+ keyguardQuickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
+ pulseExpansionInteractor: PulseExpansionInteractor,
+ sceneInteractor: SceneInteractor,
+ shadeInteractor: ShadeInteractor,
+ dumpManager: DumpManager,
+) : FlowDumperImpl(dumpManager) {
+
+ /**
+ * Whether bypassing the keyguard is enabled by the user in user settings (skipping the
+ * lockscreen when authenticating using secondary authentication types like face unlock).
+ */
+ val isBypassAvailable: Flow<Boolean> =
+ keyguardBypassRepository.isBypassAvailable.dumpWhileCollecting("isBypassAvailable")
+
+ /**
+ * Models whether bypass is unavailable (no secondary authentication types enrolled), or if the
+ * keyguard can be bypassed as a combination of the settings toggle value set by the user and
+ * other factors related to device state.
+ */
+ val canBypass: Flow<Boolean> =
+ isBypassAvailable
+ .flatMapLatest { isBypassAvailable ->
+ if (isBypassAvailable) {
+ combine(
+ sceneInteractor.currentScene.map { scene -> scene == Scenes.Bouncer },
+ alternateBouncerInteractor.isVisible,
+ sceneInteractor.currentScene.map { scene -> scene == Scenes.Lockscreen },
+ keyguardQuickAffordanceInteractor.launchingAffordance,
+ pulseExpansionInteractor.isPulseExpanding,
+ shadeInteractor.isQsExpanded,
+ ) {
+ isBouncerShowing,
+ isAlternateBouncerShowing,
+ isOnLockscreenScene,
+ isLaunchingAffordance,
+ isPulseExpanding,
+ isQsExpanded ->
+ when {
+ isBouncerShowing -> true
+ isAlternateBouncerShowing -> true
+ !isOnLockscreenScene -> false
+ isLaunchingAffordance -> false
+ isPulseExpanding -> false
+ isQsExpanded -> false
+ else -> true
+ }
+ }
+ } else {
+ flowOf(false)
+ }
+ }
+ .dumpWhileCollecting("canBypass")
+
+ companion object {
+ private const val TAG: String = "KeyguardBypassInteractor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index b24ca1a..2e0a160 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -284,7 +284,7 @@
}
/** Observable for the [StatusBarState] */
- val statusBarState: Flow<StatusBarState> = repository.statusBarState
+ val statusBarState: StateFlow<StatusBarState> = repository.statusBarState
/** Observable for [BiometricUnlockModel] when biometrics are used to unlock the device. */
val biometricUnlockState: StateFlow<BiometricUnlockModel> = repository.biometricUnlockState
@@ -350,23 +350,21 @@
val dismissAlpha: Flow<Float> =
shadeRepository.legacyShadeExpansion
.sampleCombine(
- statusBarState,
keyguardTransitionInteractor.currentKeyguardState,
keyguardTransitionInteractor.transitionState,
isKeyguardDismissible,
keyguardTransitionInteractor.isFinishedIn(Scenes.Communal, GLANCEABLE_HUB),
)
- .filter { (_, _, _, step, _, _) -> !step.transitionState.isTransitioning() }
+ .filter { (_, _, step, _, _) -> !step.transitionState.isTransitioning() }
.transform {
(
legacyShadeExpansion,
- statusBarState,
currentKeyguardState,
step,
isKeyguardDismissible,
onGlanceableHub) ->
if (
- statusBarState == StatusBarState.KEYGUARD &&
+ statusBarState.value == StatusBarState.KEYGUARD &&
isKeyguardDismissible &&
currentKeyguardState == LOCKSCREEN &&
legacyShadeExpansion != 1f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 26bf26b..21afd3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -61,6 +61,8 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
@@ -91,6 +93,11 @@
@Application private val appContext: Context,
private val sceneInteractor: Lazy<SceneInteractor>,
) {
+ /**
+ * Whether a quick affordance is being launched. Quick Affordances are interactive lockscreen UI
+ * elements that allow the user to perform quick actions without unlocking their device.
+ */
+ val launchingAffordance: StateFlow<Boolean> = repository.get().launchingAffordance.asStateFlow()
/**
* Whether the UI should use the long press gesture to activate quick affordances.
@@ -167,11 +174,7 @@
* @param expandable An optional [Expandable] for the activity- or dialog-launch animation
* @param slotId The id of the lockscreen slot that the affordance is in
*/
- fun onQuickAffordanceTriggered(
- configKey: String,
- expandable: Expandable?,
- slotId: String,
- ) {
+ fun onQuickAffordanceTriggered(configKey: String, expandable: Expandable?, slotId: String) {
val (decodedSlotId, decodedConfigKey) = configKey.decode()
val config =
repository.get().selections.value[decodedSlotId]?.find { it.key == decodedConfigKey }
@@ -191,10 +194,7 @@
)
is KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled -> Unit
is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog ->
- showDialog(
- result.dialog,
- result.expandable,
- )
+ showDialog(result.dialog, result.expandable)
}
}
@@ -225,12 +225,7 @@
selections.add(affordanceId)
- repository
- .get()
- .setSelections(
- slotId = slotId,
- affordanceIds = selections,
- )
+ repository.get().setSelections(slotId = slotId, affordanceIds = selections)
logger.logQuickAffordanceSelected(slotId, affordanceId)
metricsLogger.logOnShortcutSelected(slotId, affordanceId)
@@ -274,12 +269,7 @@
.getOrDefault(slotId, emptyList())
.toMutableList()
return if (selections.remove(affordanceId)) {
- repository
- .get()
- .setSelections(
- slotId = slotId,
- affordanceIds = selections,
- )
+ repository.get().setSelections(slotId = slotId, affordanceIds = selections)
true
} else {
false
@@ -399,11 +389,15 @@
intent,
true /* dismissShade */,
expandable?.activityTransitionController(),
- true /* showOverLockscreenWhenLocked */,
+ true, /* showOverLockscreenWhenLocked */
)
}
}
+ fun setLaunchingAffordance(isLaunchingAffordance: Boolean) {
+ repository.get().launchingAffordance.value = isLaunchingAffordance
+ }
+
private fun String.encode(slotId: String): String {
return "$slotId$DELIMITER$this"
}
@@ -444,19 +438,19 @@
),
KeyguardPickerFlag(
name = Contract.FlagsTable.FLAG_NAME_MONOCHROMATIC_THEME,
- value = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME)
+ value = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME),
),
KeyguardPickerFlag(
name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_PICKER_UI_FOR_AIWP,
- value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_UI_FOR_AIWP)
+ value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_UI_FOR_AIWP),
),
KeyguardPickerFlag(
name = Contract.FlagsTable.FLAG_NAME_PAGE_TRANSITIONS,
- value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PAGE_TRANSITIONS)
+ value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PAGE_TRANSITIONS),
),
KeyguardPickerFlag(
name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_PICKER_PREVIEW_ANIMATION,
- value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PREVIEW_ANIMATION)
+ value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PREVIEW_ANIMATION),
),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PulseExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PulseExpansionInteractor.kt
new file mode 100644
index 0000000..377d7ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PulseExpansionInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.data.repository.PulseExpansionRepository
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class PulseExpansionInteractor
+@Inject
+constructor(private val repository: PulseExpansionRepository, dumpManager: DumpManager) :
+ FlowDumperImpl(dumpManager) {
+ /**
+ * Whether the notification panel is expanding from the user swiping downward on a notification
+ * from the pulsing state, or swiping anywhere on the screen when face bypass is enabled
+ */
+ val isPulseExpanding: StateFlow<Boolean> =
+ repository.isPulseExpanding.asStateFlow().dumpValue("isPulseExpanding")
+
+ /** Updates whether a pulse expansion is occurring. */
+ fun setPulseExpanding(pulseExpanding: Boolean) {
+ repository.isPulseExpanding.value = pulseExpanding
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
index e404f27..2e3a095 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt
@@ -20,13 +20,13 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.shade.data.repository.FlingInfo
import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.util.kotlin.Utils.Companion.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/**
@@ -39,8 +39,8 @@
constructor(
@Background backgroundScope: CoroutineScope,
shadeRepository: ShadeRepository,
- transitionInteractor: KeyguardTransitionInteractor,
- keyguardInteractor: KeyguardInteractor,
+ private val transitionInteractor: KeyguardTransitionInteractor,
+ private val keyguardInteractor: KeyguardInteractor,
) {
/**
* Emits a [FlingInfo] whenever a swipe to dismiss gesture has started a fling animation on the
@@ -50,20 +50,15 @@
* LOCKSCREEN -> GONE, and by [KeyguardSurfaceBehindInteractor] to match the surface remote
* animation's velocity to the fling velocity, if applicable.
*/
- val dismissFling =
+ val dismissFling: StateFlow<FlingInfo?> =
shadeRepository.currentFling
- .sample(
- transitionInteractor.startedKeyguardTransitionStep,
- keyguardInteractor.isKeyguardDismissible,
- keyguardInteractor.statusBarState,
- )
- .filter { (flingInfo, startedStep, keyguardDismissable, statusBarState) ->
+ .filter { flingInfo ->
flingInfo != null &&
!flingInfo.expand &&
- statusBarState != StatusBarState.SHADE_LOCKED &&
- startedStep.to == KeyguardState.LOCKSCREEN &&
- keyguardDismissable
+ keyguardInteractor.statusBarState.value != StatusBarState.SHADE_LOCKED &&
+ transitionInteractor.startedKeyguardTransitionStep.value.to ==
+ KeyguardState.LOCKSCREEN &&
+ keyguardInteractor.isKeyguardDismissible.value
}
- .map { (flingInfo, _) -> flingInfo }
.stateIn(backgroundScope, SharingStarted.Eagerly, null)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 40d4193..0d81604 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -27,6 +27,7 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
@@ -81,6 +82,7 @@
private val communalInteractor: CommunalInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
+ private val pulseExpansionInteractor: PulseExpansionInteractor,
notificationShadeWindowModel: NotificationShadeWindowModel,
private val aodNotificationIconViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
private val alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel,
@@ -371,7 +373,7 @@
/** Is there an expanded pulse, are we animating in response? */
private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> {
- return notificationsKeyguardInteractor.isPulseExpanding
+ return pulseExpansionInteractor.isPulseExpanding
.pairwise(initialValue = null)
// If pulsing changes, start animating, unless it's the first emission
.map { (prev, expanding) -> AnimatableEvent(expanding, startAnimating = prev != null) }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index e4738a2..9c8e84f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -59,6 +59,9 @@
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
@@ -76,6 +79,7 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
@@ -131,7 +135,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
@SuppressLint("ValidFragment")
class QSFragmentCompose
@@ -149,11 +152,11 @@
private lateinit var viewModel: QSFragmentComposeViewModel
- private val qsHeight = MutableStateFlow(0)
private val qqsVisible = MutableStateFlow(false)
private val qqsPositionOnRoot = Rect()
private val composeViewPositionOnScreen = Rect()
private val scrollState = ScrollState(0)
+ private val locationTemp = IntArray(2)
// Inside object for namespacing
private val notificationScrimClippingParams =
@@ -247,7 +250,11 @@
Modifier.notificationScrimClip {
notificationScrimClippingParams.params
}
- },
+ }
+ // Disable touches in the whole composable while the mirror is showing.
+ // While the mirror is showing, an ancestor of the ComposeView is made
+ // alpha 0, but touches are still being captured by the composables.
+ .gesturesDisabled(viewModel.showingMirror),
) {
val isEditing by
viewModel.containerViewModel.editModeViewModel.isEditing
@@ -324,8 +331,27 @@
}
override fun getQsMinExpansionHeight(): Int {
- // TODO (b/353253277) implement split screen
- return viewModel.qqsHeight
+ return if (viewModel.isInSplitShade) {
+ getQsMinExpansionHeightForSplitShade()
+ } else {
+ viewModel.qqsHeight
+ }
+ }
+
+ /**
+ * Returns the min expansion height for split shade.
+ *
+ * On split shade, QS is always expanded and goes from the top of the screen to the bottom of
+ * the QS container.
+ */
+ private fun getQsMinExpansionHeightForSplitShade(): Int {
+ view?.getLocationOnScreen(locationTemp)
+ val top = locationTemp.get(1)
+ // We want to get the original top position, so we subtract any translation currently set.
+ val originalTop = (top - (view?.translationY ?: 0f)).toInt()
+ // On split shade the QS view doesn't start at the top of the screen, so we need to add the
+ // top margin.
+ return originalTop + (view?.height ?: 0)
}
override fun getDesiredHeight(): Int {
@@ -629,14 +655,15 @@
)
}
}
- }
- QuickSettingsTheme {
- FooterActions(
- viewModel = viewModel.footerActionsViewModel,
- qsVisibilityLifecycleOwner = this@QSFragmentCompose,
- modifier =
- Modifier.sysuiResTag("qs_footer_actions").element(ElementKeys.FooterActions),
- )
+ QuickSettingsTheme {
+ FooterActions(
+ viewModel = viewModel.footerActionsViewModel,
+ qsVisibilityLifecycleOwner = this@QSFragmentCompose,
+ modifier =
+ Modifier.sysuiResTag("qs_footer_actions")
+ .element(ElementKeys.FooterActions),
+ )
+ }
}
}
}
@@ -871,3 +898,19 @@
return super.onInterceptTouchEvent(ev)
}
}
+
+private fun Modifier.gesturesDisabled(disabled: Boolean) =
+ if (disabled) {
+ pointerInput(Unit) {
+ awaitPointerEventScope {
+ // we should wait for all new pointer events
+ while (true) {
+ awaitPointerEvent(pass = PointerEventPass.Initial)
+ .changes
+ .forEach(PointerInputChange::consume)
+ }
+ }
+ }
+ } else {
+ this
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index d571dd0..d30c6be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -26,6 +26,7 @@
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.lifecycle.LifecycleCoroutineScope
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.BouncerPanelExpansionCalculator
import com.android.systemui.Dumpable
import com.android.systemui.animation.ShadeInterpolation
@@ -65,13 +66,12 @@
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
@OptIn(ExperimentalCoroutinesApi::class)
class QSFragmentComposeViewModel
@AssistedInject
constructor(
- val containerViewModel: QuickSettingsContainerViewModel,
+ containerViewModelFactory: QuickSettingsContainerViewModel.Factory,
@Main private val resources: Resources,
footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
@@ -87,6 +87,8 @@
@Assisted private val lifecycleScope: LifecycleCoroutineScope,
) : Dumpable, ExclusiveActivatable() {
+ val containerViewModel = containerViewModelFactory.create(true)
+
private val hydrator = Hydrator("QSFragmentComposeViewModel.hydrator")
val footerActionsViewModel =
@@ -257,6 +259,9 @@
.onStart { emit(sysuiStatusBarStateController.state) },
)
+ val showingMirror: Boolean
+ get() = containerViewModel.brightnessSliderViewModel.showMirror
+
private val isKeyguardState: Boolean
get() = statusBarState == StatusBarState.KEYGUARD
@@ -321,6 +326,7 @@
coroutineScope {
launch { hydrateSquishinessInteractor() }
launch { hydrator.activate() }
+ launch { containerViewModel.activate() }
awaitCancellation()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 978a353..d107222 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -18,12 +18,12 @@
import android.graphics.drawable.Animatable
import android.text.TextUtils
+import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -32,8 +32,9 @@
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -44,6 +45,7 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.Shape
@@ -57,7 +59,9 @@
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.semantics.toggleableState
import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import com.android.compose.modifiers.size
import com.android.compose.modifiers.thenIf
import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
@@ -88,12 +92,14 @@
) {
// Icon
val longPressLabel = longPressLabel().takeIf { onLongClick != null }
+ val animatedBackgroundColor by
+ animateColorAsState(colors.iconBackground, label = "QSTileDualTargetBackgroundColor")
Box(
modifier =
Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClick != null) {
Modifier.clip(iconShape)
.verticalSquish(squishiness)
- .background(colors.iconBackground)
+ .drawBehind { drawRect(animatedBackgroundColor) }
.combinedClickable(
onClick = toggleClick!!,
onLongClick = onLongClick,
@@ -117,6 +123,7 @@
SmallTileContent(
icon = icon,
color = colors.icon,
+ size = { CommonTileDefaults.LargeTileIconSize },
modifier = Modifier.align(Alignment.Center),
)
}
@@ -139,18 +146,22 @@
modifier: Modifier = Modifier,
accessibilityUiState: AccessibilityUiState? = null,
) {
+ val animatedLabelColor by animateColorAsState(colors.label, label = "QSTileLabelColor")
+ val animatedSecondaryLabelColor by
+ animateColorAsState(colors.secondaryLabel, label = "QSTileSecondaryLabelColor")
Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) {
- Text(
+ BasicText(
label,
style = MaterialTheme.typography.labelLarge,
- color = colors.label,
+ color = { animatedLabelColor },
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (!TextUtils.isEmpty(secondaryLabel)) {
- Text(
+ BasicText(
secondaryLabel ?: "",
- color = colors.secondaryLabel,
+ color = { animatedSecondaryLabelColor },
+ maxLines = 1,
style = MaterialTheme.typography.bodyMedium,
modifier =
Modifier.thenIf(
@@ -170,9 +181,11 @@
modifier: Modifier = Modifier,
icon: Icon,
color: Color,
+ size: () -> Dp = { CommonTileDefaults.IconSize },
animateToEnd: Boolean = false,
) {
- val iconModifier = modifier.size(CommonTileDefaults.IconSize)
+ val animatedColor by animateColorAsState(color, label = "QSTileIconColor")
+ val iconModifier = modifier.size({ size().roundToPx() }, { size().roundToPx() })
val context = LocalContext.current
val loadedDrawable =
remember(icon, context) {
@@ -182,7 +195,7 @@
}
}
if (loadedDrawable !is Animatable) {
- Icon(icon = icon, tint = color, modifier = iconModifier)
+ Icon(icon = icon, tint = animatedColor, modifier = iconModifier)
} else if (icon is Icon.Resource) {
val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
val painter =
@@ -198,14 +211,15 @@
Image(
painter = painter,
contentDescription = icon.contentDescription?.load(),
- colorFilter = ColorFilter.tint(color = color),
+ colorFilter = ColorFilter.tint(color = animatedColor),
modifier = iconModifier,
)
}
}
object CommonTileDefaults {
- val IconSize = 24.dp
+ val IconSize = 32.dp
+ val LargeTileIconSize = 28.dp
val ToggleTargetSize = 56.dp
val TileHeight = 72.dp
val TilePadding = 8.dp
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index 418ed0b..b5cec12 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -20,6 +20,7 @@
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
@@ -54,7 +55,6 @@
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
-import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
@@ -69,21 +69,22 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
-import androidx.compose.ui.BiasAlignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInRoot
@@ -103,6 +104,7 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastMap
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.bounceable
import com.android.compose.modifiers.height
import com.android.systemui.common.ui.compose.load
@@ -134,9 +136,10 @@
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.model.groupAndSort
import com.android.systemui.res.R
+import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.collectLatest
object TileType
@@ -222,7 +225,7 @@
if (dragIsInProgress) {
RemoveTileTarget()
} else {
- Text(text = "Hold and drag to rearrange tiles.")
+ Text(text = stringResource(id = R.string.drag_to_rearrange_tiles))
}
}
}
@@ -240,7 +243,9 @@
spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
modifier = modifier.fillMaxSize(),
) {
- EditGridHeader { Text(text = "Hold and drag to add tiles.") }
+ EditGridHeader {
+ Text(text = stringResource(id = R.string.drag_to_add_tiles))
+ }
AvailableTileGrid(otherTiles, selectionState, columns, listState)
}
@@ -286,7 +291,7 @@
.padding(10.dp),
) {
Icon(imageVector = Icons.Default.Clear, contentDescription = null)
- Text(text = "Remove")
+ Text(text = stringResource(id = R.string.qs_customize_remove))
}
}
@@ -409,7 +414,7 @@
/**
* Adds a list of [GridCell] to the lazy grid
*
- * @param cells the pairs of [GridCell] to [BounceableTileViewModel]
+ * @param cells the pairs of [GridCell] to [AnimatableTileViewModel]
* @param dragAndDropState the [DragAndDropState] for this grid
* @param selectionState the [MutableSelectionState] for this grid
* @param onToggleSize the callback when a tile's size is toggled
@@ -545,9 +550,27 @@
selectionState::unSelect,
)
.tileBackground(colors.background)
- .tilePadding()
) {
- EditTile(tile = cell.tile, iconOnly = cell.isIcon)
+ val targetValue = if (cell.isIcon) 0f else 1f
+ val animatedProgress = remember { Animatable(targetValue) }
+
+ if (selected) {
+ val resizingState = selectionState.resizingState
+ LaunchedEffect(targetValue, resizingState) {
+ if (resizingState == null) {
+ animatedProgress.animateTo(targetValue)
+ } else {
+ snapshotFlow { resizingState.progression }
+ .collectLatest { animatedProgress.snapTo(it) }
+ }
+ }
+ }
+
+ EditTile(
+ tile = cell.tile,
+ tileWidths = { tileWidths },
+ progress = { animatedProgress.value },
+ )
}
}
}
@@ -612,45 +635,72 @@
}
@Composable
-fun BoxScope.EditTile(
+fun EditTile(
tile: EditTileViewModel,
- iconOnly: Boolean,
+ tileWidths: () -> TileWidths?,
+ progress: () -> Float,
colors: TileColors = EditModeTileDefaults.editTileColors(),
) {
- // Animated horizontal alignment from center (0f) to start (-1f)
- val alignmentValue by
- animateFloatAsState(
- targetValue = if (iconOnly) 0f else -1f,
- label = "QSEditTileContentAlignment",
- )
- val alignment by remember {
- derivedStateOf { BiasAlignment(horizontalBias = alignmentValue, verticalBias = 0f) }
- }
- // Icon
- Box(Modifier.size(ToggleTargetSize).align(alignment)) {
- SmallTileContent(
- icon = tile.icon,
- color = colors.icon,
- animateToEnd = true,
- modifier = Modifier.align(Alignment.Center),
- )
- }
+ val iconSizeDiff = CommonTileDefaults.IconSize - CommonTileDefaults.LargeTileIconSize
+ Row(
+ horizontalArrangement = spacedBy(6.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ Modifier.layout { measurable, constraints ->
+ // Always display the tile using the large size and trust the parent composable
+ // to clip the content as needed. This stop the labels from being truncated.
+ val width = tileWidths()?.max ?: constraints.maxWidth
+ val placeable =
+ measurable.measure(constraints.copy(minWidth = width, maxWidth = width))
+ val currentProgress = progress()
+ val startPadding =
+ if (currentProgress == 0f) {
+ // Find the center of the max width when the tile is icon only
+ iconHorizontalCenter(constraints.maxWidth)
+ } else {
+ // Find the center of the minimum width to hold the same position as the
+ // tile is resized.
+ val basePadding =
+ tileWidths()?.min?.let { iconHorizontalCenter(it) } ?: 0f
+ // Large tiles, represented with a progress of 1f, have a 0.dp padding
+ basePadding * (1f - currentProgress)
+ }
- // Labels, positioned after the icon
- AnimatedVisibility(visible = !iconOnly, enter = fadeIn(), exit = fadeOut()) {
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ placeable.place(startPadding.roundToInt(), 0)
+ }
+ }
+ .tilePadding(),
+ ) {
+ // Icon
+ Box(Modifier.size(ToggleTargetSize)) {
+ SmallTileContent(
+ icon = tile.icon,
+ color = colors.icon,
+ animateToEnd = true,
+ size = { CommonTileDefaults.IconSize - iconSizeDiff * progress() },
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
+
+ // Labels, positioned after the icon
LargeTileLabels(
label = tile.label.text,
secondaryLabel = tile.appName?.text,
colors = colors,
- modifier = Modifier.padding(start = ToggleTargetSize + TileArrangementPadding),
+ modifier = Modifier.weight(1f).graphicsLayer { alpha = progress() },
)
}
}
+private fun MeasureScope.iconHorizontalCenter(containerSize: Int): Float {
+ return (containerSize - ToggleTargetSize.roundToPx()) / 2f -
+ CommonTileDefaults.TilePadding.toPx()
+}
+
private fun Modifier.tileBackground(color: Color): Modifier {
- return drawBehind {
- drawRoundRect(SolidColor(color), cornerRadius = CornerRadius(InactiveCornerRadius.toPx()))
- }
+ // Clip tile contents from overflowing past the tile
+ return clip(RoundedCornerShape(InactiveCornerRadius)).drawBehind { drawRect(color) }
}
private object EditModeTileDefaults {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index e1583e3..5bebdbc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -21,6 +21,7 @@
import android.content.res.Resources
import android.service.quicksettings.Tile.STATE_ACTIVE
import android.service.quicksettings.Tile.STATE_INACTIVE
+import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
@@ -61,6 +62,7 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.Expandable
import com.android.compose.animation.bounceable
import com.android.compose.modifiers.thenIf
@@ -74,6 +76,7 @@
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.panels.ui.compose.BounceableInfo
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel
import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
@@ -82,7 +85,6 @@
import com.android.systemui.res.R
import java.util.function.Supplier
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
private const val TEST_TAG_SMALL = "qs_tile_small"
private const val TEST_TAG_LARGE = "qs_tile_large"
@@ -128,14 +130,18 @@
// TODO(b/361789146): Draw the shapes instead of clipping
val tileShape = TileDefaults.animateTileShape(uiState.state)
-
- TileExpandable(
- color =
+ val animatedColor by
+ animateColorAsState(
if (iconOnly || !uiState.handlesSecondaryClick) {
colors.iconBackground
} else {
colors.background
},
+ label = "QSTileBackgroundColor",
+ )
+
+ TileExpandable(
+ color = { animatedColor },
shape = tileShape,
squishiness = squishiness,
hapticsViewModel = hapticsViewModel,
@@ -212,7 +218,7 @@
@Composable
private fun TileExpandable(
- color: Color,
+ color: () -> Color,
shape: Shape,
squishiness: () -> Float,
hapticsViewModel: TileHapticsViewModel?,
@@ -220,7 +226,7 @@
content: @Composable (Expandable) -> Unit,
) {
Expandable(
- color = color,
+ color = color(),
shape = shape,
modifier = modifier.clip(shape).verticalSquish(squishiness),
) {
@@ -238,7 +244,7 @@
) {
Box(
modifier =
- Modifier.height(CommonTileDefaults.TileHeight)
+ Modifier.height(TileHeight)
.fillMaxWidth()
.tileCombinedClickable(
onClick = onClick,
@@ -336,6 +342,16 @@
)
@Composable
+ fun inactiveDualTargetTileColors(): TileColors =
+ TileColors(
+ background = MaterialTheme.colorScheme.surfaceVariant,
+ iconBackground = MaterialTheme.colorScheme.surfaceContainerHighest,
+ label = MaterialTheme.colorScheme.onSurfaceVariant,
+ secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant,
+ icon = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+
+ @Composable
fun inactiveTileColors(): TileColors =
TileColors(
background = MaterialTheme.colorScheme.surfaceVariant,
@@ -365,7 +381,13 @@
activeTileColors()
}
}
- STATE_INACTIVE -> inactiveTileColors()
+ STATE_INACTIVE -> {
+ if (uiState.handlesSecondaryClick) {
+ inactiveDualTargetTileColors()
+ } else {
+ inactiveTileColors()
+ }
+ }
else -> unavailableTileColors()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
index a084bc2..9552aa9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
@@ -17,25 +17,30 @@
package com.android.systemui.qs.panels.ui.compose.selection
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.setValue
import com.android.systemui.qs.panels.ui.compose.selection.ResizingDefaults.RESIZING_THRESHOLD
class ResizingState(private val widths: TileWidths, private val onResize: () -> Unit) {
- // Total drag offset of this resize operation
- private var totalOffset = 0f
+ /** Total drag offset of this resize operation. */
+ private var totalOffset by mutableFloatStateOf(0f)
/** Width in pixels of the resizing tile. */
var width by mutableIntStateOf(widths.base)
+ /** Progression between icon (0) and large (1) sizes. */
+ val progression
+ get() = calculateProgression()
+
// Whether the tile is currently over the threshold and should be a large tile
- private var passedThreshold: Boolean = passedThreshold(calculateProgression(width))
+ private var passedThreshold: Boolean = passedThreshold(progression)
fun onDrag(offset: Float) {
totalOffset += offset
width = (widths.base + totalOffset).toInt().coerceIn(widths.min, widths.max)
- passedThreshold(calculateProgression(width)).let {
+ passedThreshold(progression).let {
// Resize if we went over the threshold
if (passedThreshold != it) {
passedThreshold = it
@@ -49,7 +54,7 @@
}
/** The progression of the resizing tile between an icon tile (0f) and a large tile (1f) */
- private fun calculateProgression(width: Int): Float {
+ private fun calculateProgression(): Float {
return ((width - widths.min) / (widths.max - widths.min).toFloat()).coerceIn(0f, 1f)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
index 9f13a37..8a345ce 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
@@ -16,7 +16,11 @@
package com.android.systemui.qs.panels.ui.compose.selection
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateIntAsState
+import androidx.compose.animation.core.spring
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
import androidx.compose.foundation.layout.Box
@@ -78,7 +82,6 @@
ResizingHandle(
enabled = selected,
selectionState = selectionState,
- transition = selectionAlpha,
tileWidths = tileWidths,
modifier =
// Higher zIndex to make sure the handle is drawn above the content
@@ -91,7 +94,6 @@
private fun ResizingHandle(
enabled: Boolean,
selectionState: MutableSelectionState,
- transition: () -> Float,
tileWidths: () -> TileWidths?,
modifier: Modifier = Modifier,
) {
@@ -126,19 +128,24 @@
}
}
) {
- ResizingDot(transition = transition, modifier = Modifier.align(Alignment.Center))
+ ResizingDot(enabled = enabled, modifier = Modifier.align(Alignment.Center))
}
}
@Composable
private fun ResizingDot(
- transition: () -> Float,
+ enabled: Boolean,
modifier: Modifier = Modifier,
color: Color = MaterialTheme.colorScheme.primary,
) {
+ val alpha by animateFloatAsState(if (enabled) 1f else 0f)
+ val radius by
+ animateDpAsState(
+ if (enabled) ResizingDotSize / 2 else 0.dp,
+ animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy),
+ )
Canvas(modifier = modifier.size(ResizingDotSize)) {
- val v = transition()
- drawCircle(color = color, radius = (ResizingDotSize / 2).toPx() * v, alpha = v)
+ drawCircle(color = color, radius = radius.toPx(), alpha = alpha)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/dialog/QSResetDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/dialog/QSResetDialogDelegate.kt
index 03fc425..cbece2c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/dialog/QSResetDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/dialog/QSResetDialogDelegate.kt
@@ -23,7 +23,7 @@
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.android.compose.PlatformButton
-import com.android.compose.PlatformOutlinedButton
+import com.android.compose.PlatformTextButton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dialog.ui.composable.AlertDialogContent
import com.android.systemui.qs.panels.domain.interactor.EditTilesResetInteractor
@@ -84,8 +84,8 @@
Text(stringResource(id = android.R.string.ok))
}
},
- neutralButton = {
- PlatformOutlinedButton(onClick = { dialog.dismiss() }) {
+ negativeButton = {
+ PlatformTextButton(onClick = { dialog.dismiss() }) {
Text(stringResource(id = android.R.string.cancel))
}
},
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
index 6ccd169..b1eb3bb3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
@@ -17,18 +17,33 @@
package com.android.systemui.qs.ui.viewmodel
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
-import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
-@SysUISingleton
class QuickSettingsContainerViewModel
-@Inject
+@AssistedInject
constructor(
- val brightnessSliderViewModel: BrightnessSliderViewModel,
+ brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory,
+ @Assisted supportsBrightnessMirroring: Boolean,
val tileGridViewModel: TileGridViewModel,
val editModeViewModel: EditModeViewModel,
val quickQuickSettingsViewModel: QuickQuickSettingsViewModel,
-)
+) : ExclusiveActivatable() {
+
+ val brightnessSliderViewModel =
+ brightnessSliderViewModelFactory.create(supportsBrightnessMirroring)
+
+ override suspend fun onActivated(): Nothing {
+ brightnessSliderViewModel.activate()
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(supportsBrightnessMirroring: Boolean): QuickSettingsContainerViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
index 31519a9..9a416d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
@@ -23,6 +23,7 @@
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.UserActionResult.HideOverlay
import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay
+import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
@@ -33,11 +34,10 @@
/** Models the UI state for the user actions for navigating to other scenes or overlays. */
class QuickSettingsShadeOverlayActionsViewModel
@AssistedInject
-constructor(private val containerViewModel: QuickSettingsContainerViewModel) :
- UserActionsViewModel() {
+constructor(private val editModeViewModel: EditModeViewModel) : UserActionsViewModel() {
override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
- containerViewModel.editModeViewModel.isEditing
+ editModeViewModel.isEditing
.map { isEditing ->
buildMap {
put(Swipe.Up, HideOverlay(Overlays.QuickSettingsShade))
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
index bed8574..8ef51af 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.ui.viewmodel
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -27,7 +28,6 @@
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Models UI state used to render the content of the quick settings shade overlay.
@@ -41,9 +41,11 @@
val shadeInteractor: ShadeInteractor,
val sceneInteractor: SceneInteractor,
val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
- val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
+ quickSettingsContainerViewModelFactory: QuickSettingsContainerViewModel.Factory,
) : ExclusiveActivatable() {
+ val quickSettingsContainerViewModel = quickSettingsContainerViewModelFactory.create(false)
+
override suspend fun onActivated(): Nothing {
coroutineScope {
launch {
@@ -68,6 +70,8 @@
)
}
}
+
+ launch { quickSettingsContainerViewModel.activate() }
}
awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
deleted file mode 100644
index d01b33b..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.ui.viewmodel
-
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-
-/**
- * Models UI state used to render the content of the quick settings shade scene.
- *
- * Different from [QuickSettingsShadeUserActionsViewModel], which only models user actions that can
- * be performed to navigate to other scenes.
- */
-class QuickSettingsShadeSceneContentViewModel
-@AssistedInject
-constructor(
- val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
-) {
-
- @AssistedFactory
- interface Factory {
- fun create(): QuickSettingsShadeSceneContentViewModel
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModel.kt
deleted file mode 100644
index bd1872d..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModel.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.ui.viewmodel
-
-import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.UserAction
-import com.android.compose.animation.scene.UserActionResult
-import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay
-import com.android.systemui.scene.shared.model.Overlays
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
-import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-import kotlinx.coroutines.flow.map
-
-/**
- * Models the UI state for the user actions that the user can perform to navigate to other scenes.
- *
- * Different from the [QuickSettingsShadeSceneContentViewModel] which models the _content_ of the
- * scene.
- */
-class QuickSettingsShadeUserActionsViewModel
-@AssistedInject
-constructor(
- val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
-) : UserActionsViewModel() {
-
- override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
- quickSettingsContainerViewModel.editModeViewModel.isEditing
- .map { editing ->
- buildMap {
- put(Swipe.Up, UserActionResult(SceneFamilies.Home))
- put(
- Swipe(
- direction = SwipeDirection.Down,
- fromSource = SceneContainerEdge.TopLeft
- ),
- ReplaceByOverlay(Overlays.NotificationsShade)
- )
- if (!editing) {
- put(Back, UserActionResult(SceneFamilies.Home))
- }
- }
- }
- .collect { actions -> setActions(actions) }
- }
-
- @AssistedFactory
- interface Factory {
- fun create(): QuickSettingsShadeUserActionsViewModel
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index a62edcb..8c004c4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -39,10 +39,16 @@
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+import androidx.compose.ui.platform.ComposeView;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel;
+import com.android.systemui.compose.ComposeInitializer;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.qs.flags.QSComposeFragment;
import com.android.systemui.res.R;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
@@ -65,6 +71,7 @@
private final AccessibilityManagerWrapper mAccessibilityMgr;
private Runnable mCancelTimeoutRunnable;
private final ShadeInteractor mShadeInteractor;
+ private final BrightnessSliderViewModel.Factory mBrightnessSliderViewModelFactory;
@Inject
public BrightnessDialog(
@@ -72,13 +79,15 @@
BrightnessController.Factory brightnessControllerFactory,
@Main DelayableExecutor mainExecutor,
AccessibilityManagerWrapper accessibilityMgr,
- ShadeInteractor shadeInteractor
+ ShadeInteractor shadeInteractor,
+ BrightnessSliderViewModel.Factory brightnessSliderViewModelFactory
) {
mToggleSliderFactory = brightnessSliderfactory;
mBrightnessControllerFactory = brightnessControllerFactory;
mMainExecutor = mainExecutor;
mAccessibilityMgr = accessibilityMgr;
mShadeInteractor = shadeInteractor;
+ mBrightnessSliderViewModelFactory = brightnessSliderViewModelFactory;
}
@@ -86,14 +95,28 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setWindowAttributes();
- setContentView(R.layout.brightness_mirror_container);
- setBrightnessDialogViewAttributes();
+ View view;
+ if (!QSComposeFragment.isEnabled()) {
+ setContentView(R.layout.brightness_mirror_container);
+ view = findViewById(R.id.brightness_mirror_container);
+ setDialogContent((FrameLayout) view);
+ } else {
+ ComposeView composeView = new ComposeView(this);
+ ComposeDialogComposableProvider.INSTANCE.setComposableBrightness(
+ composeView,
+ new ComposableProvider(mBrightnessSliderViewModelFactory)
+ );
+ composeView.setId(R.id.brightness_dialog_slider);
+ setContentView(composeView);
+ ((ViewGroup) composeView.getParent()).setClipChildren(false);
+ view = composeView;
+ }
+ setBrightnessDialogViewAttributes(view);
if (mShadeInteractor.isQsExpanded().getValue()) {
finish();
}
- View view = findViewById(R.id.brightness_mirror_container);
if (view != null) {
collectFlow(view, mShadeInteractor.isQsExpanded(), this::onShadeStateChange);
}
@@ -117,13 +140,27 @@
window.getDecorView();
window.setLayout(WRAP_CONTENT, WRAP_CONTENT);
getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false);
+ if (QSComposeFragment.isEnabled()) {
+ window.getDecorView().addOnAttachStateChangeListener(
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(@NonNull View v) {
+ ComposeInitializer.INSTANCE.onAttachedToWindow(v);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(@NonNull View v) {
+ ComposeInitializer.INSTANCE.onDetachedFromWindow(v);
+ }
+ });
+ }
}
- void setBrightnessDialogViewAttributes() {
- FrameLayout frame = findViewById(R.id.brightness_mirror_container);
+ void setBrightnessDialogViewAttributes(View container) {
// The brightness mirror container is INVISIBLE by default.
- frame.setVisibility(View.VISIBLE);
- ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) frame.getLayoutParams();
+ container.setVisibility(View.VISIBLE);
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) container.getLayoutParams();
int horizontalMargin =
getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
lp.leftMargin = horizontalMargin;
@@ -136,23 +173,6 @@
lp.topMargin = verticalMargin;
lp.bottomMargin = verticalMargin;
- frame.setLayoutParams(lp);
- Rect bounds = new Rect();
- frame.addOnLayoutChangeListener(
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- // Exclude this view (and its horizontal margins) from triggering gestures.
- // This prevents back gesture from being triggered by dragging close to the
- // edge of the slider (0% or 100%).
- bounds.set(-horizontalMargin, 0, right - left + horizontalMargin, bottom - top);
- v.setSystemGestureExclusionRects(List.of(bounds));
- });
-
- BrightnessSliderController controller = mToggleSliderFactory.create(this, frame);
- controller.init();
- frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
-
- mBrightnessController = mBrightnessControllerFactory.create(controller);
-
Configuration configuration = getResources().getConfiguration();
int orientation = configuration.orientation;
int windowWidth = getWindowAvailableWidth();
@@ -165,7 +185,23 @@
lp.width = windowWidth - horizontalMargin * 2;
}
- frame.setLayoutParams(lp);
+ container.setLayoutParams(lp);
+ Rect bounds = new Rect();
+ container.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ // Exclude this view (and its horizontal margins) from triggering gestures.
+ // This prevents back gesture from being triggered by dragging close to the
+ // edge of the slider (0% or 100%).
+ bounds.set(-horizontalMargin, 0, right - left + horizontalMargin, bottom - top);
+ v.setSystemGestureExclusionRects(List.of(bounds));
+ });
+ }
+
+ private void setDialogContent(FrameLayout frame) {
+ BrightnessSliderController controller = mToggleSliderFactory.create(this, frame);
+ controller.init();
+ frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
+ mBrightnessController = mBrightnessControllerFactory.create(controller);
}
private int getWindowAvailableWidth() {
@@ -181,7 +217,9 @@
@Override
protected void onStart() {
super.onStart();
- mBrightnessController.registerCallbacks();
+ if (!QSComposeFragment.isEnabled()) {
+ mBrightnessController.registerCallbacks();
+ }
MetricsLogger.visible(this, MetricsEvent.BRIGHTNESS_DIALOG);
}
@@ -203,7 +241,9 @@
protected void onStop() {
super.onStop();
MetricsLogger.hidden(this, MetricsEvent.BRIGHTNESS_DIALOG);
- mBrightnessController.unregisterCallbacks();
+ if (!QSComposeFragment.isEnabled()) {
+ mBrightnessController.unregisterCallbacks();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt
new file mode 100644
index 0000000..dde2ebc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.settings.brightness
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.ViewCompositionStrategy
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
+import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.qs.ui.composable.QuickSettingsShade
+
+object ComposeDialogComposableProvider {
+
+ fun setComposableBrightness(composeView: ComposeView, content: ComposableProvider) {
+ composeView.apply {
+ setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+ setContent { PlatformTheme { content.ProvideComposableContent() } }
+ }
+ }
+}
+
+@Composable
+private fun BrightnessSliderForDialog(
+ brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory
+) {
+ val viewModel =
+ rememberViewModel(traceName = "BrightnessDialog.viewModel") {
+ brightnessSliderViewModelFactory.create(false)
+ }
+ BrightnessSliderContainer(
+ viewModel = viewModel,
+ Modifier.fillMaxWidth().height(QuickSettingsShade.Dimensions.BrightnessSliderHeight),
+ )
+}
+
+class ComposableProvider(
+ private val brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory
+) {
+ @Composable
+ fun ProvideComposableContent() {
+ BrightnessSliderForDialog(brightnessSliderViewModelFactory)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt
index ef6e72f..de068a0 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt
@@ -23,9 +23,12 @@
@SysUISingleton
class BrightnessMirrorShowingInteractor
@Inject
-constructor(
- private val brightnessMirrorShowingRepository: BrightnessMirrorShowingRepository,
-) {
+constructor(private val brightnessMirrorShowingRepository: BrightnessMirrorShowingRepository) {
+ /**
+ * Whether a brightness mirror is showing (either as a compose overlay or as a separate mirror).
+ *
+ * This can be used to determine whether other views/composables have to be hidden.
+ */
val isShowing = brightnessMirrorShowingRepository.isShowing
fun setMirrorShowing(showing: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java b/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java
index fc61e90..1d81e40 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java
@@ -18,25 +18,33 @@
import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import dagger.Lazy;
+
import javax.inject.Inject;
+
/** Handles launching camera from Shade. */
@SysUISingleton
public class CameraLauncher {
private final CameraGestureHelper mCameraGestureHelper;
private final KeyguardBypassController mKeyguardBypassController;
+ private final Lazy<KeyguardQuickAffordanceInteractor> mKeyguardQuickAffordanceInteractorLazy;
private boolean mLaunchingAffordance;
@Inject
public CameraLauncher(
CameraGestureHelper cameraGestureHelper,
- KeyguardBypassController keyguardBypassController
+ KeyguardBypassController keyguardBypassController,
+ Lazy<KeyguardQuickAffordanceInteractor> keyguardQuickAffordanceInteractorLazy
) {
mCameraGestureHelper = cameraGestureHelper;
mKeyguardBypassController = keyguardBypassController;
+ mKeyguardQuickAffordanceInteractorLazy = keyguardQuickAffordanceInteractorLazy;
}
/** Launches the camera. */
@@ -54,7 +62,12 @@
*/
public void setLaunchingAffordance(boolean launchingAffordance) {
mLaunchingAffordance = launchingAffordance;
- mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
+ if (SceneContainerFlag.isEnabled()) {
+ mKeyguardQuickAffordanceInteractorLazy.get()
+ .setLaunchingAffordance(launchingAffordance);
+ } else {
+ mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
new file mode 100644
index 0000000..6fb3ca5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.content.Context
+import android.view.GestureDetector
+import android.view.GestureDetector.SimpleOnGestureListener
+import android.view.MotionEvent
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Accepts touch events, detects long press, and calls ShadeViewController#onStatusBarLongPress. */
+@SysUISingleton
+class LongPressGestureDetector
+@Inject
+constructor(context: Context, val shadeViewController: ShadeViewController) {
+ val gestureDetector =
+ GestureDetector(
+ context,
+ object : SimpleOnGestureListener() {
+ override fun onLongPress(event: MotionEvent) {
+ shadeViewController.onStatusBarLongPress(event)
+ }
+ },
+ )
+
+ /** Accepts touch events to detect long presses. */
+ fun handleTouch(ev: MotionEvent) {
+ gestureDetector.onTouchEvent(ev)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 083cf1f..0e82bf8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -164,9 +164,11 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.shared.model.WakefulnessModel;
+import com.android.systemui.qs.flags.QSComposeFragment;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.scene.shared.model.Scenes;
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.data.repository.FlingInfo;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
@@ -363,6 +365,7 @@
private final TouchHandler mTouchHandler = new TouchHandler();
private long mDownTime;
+ private long mStatusBarLongPressDowntime;
private boolean mTouchSlopExceededBeforeDown;
private float mOverExpansion;
private CentralSurfaces mCentralSurfaces;
@@ -664,6 +667,7 @@
};
private final ActivityStarter mActivityStarter;
+ private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor;
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@@ -757,7 +761,8 @@
PowerInteractor powerInteractor,
KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm,
NaturalScrollingSettingObserver naturalScrollingSettingObserver,
- MSDLPlayer msdlPlayer) {
+ MSDLPlayer msdlPlayer,
+ BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor) {
SceneContainerFlag.assertInLegacyMode();
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
@@ -944,6 +949,7 @@
},
mFalsingManager);
mActivityStarter = activityStarter;
+ mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor;
onFinishInflate();
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@@ -1185,6 +1191,12 @@
}
},
mMainDispatcher);
+ if (QSComposeFragment.isEnabled()) {
+ collectFlow(mView,
+ mBrightnessMirrorShowingInteractor.isShowing(),
+ isShowing -> setAlpha(isShowing ? 0 : 255, true)
+ );
+ }
}
@VisibleForTesting
@@ -3087,6 +3099,25 @@
}
}
+ /** @deprecated Temporary a11y solution until dual shade launch b/371224114 */
+ @Override
+ @Deprecated
+ public void onStatusBarLongPress(MotionEvent event) {
+ mShadeLog.d("Status Bar was long pressed.");
+ ShadeExpandsOnStatusBarLongPress.assertInNewMode();
+ mStatusBarLongPressDowntime = event.getDownTime();
+ if (isTracking()) {
+ onTrackingStopped(true);
+ }
+ if (isExpanded() && !mQsController.getExpanded()) {
+ mShadeLog.d("Status Bar was long pressed. Expanding to QS.");
+ expandToQs();
+ } else {
+ mShadeLog.d("Status Bar was long pressed. Expanding to Notifications.");
+ expandToNotifications();
+ }
+ }
+
@Override
public int getBarState() {
return mBarState;
@@ -3750,6 +3781,7 @@
private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
mShadeLog.logEndMotionEvent("endMotionEvent called", forceCancel, false);
mTrackingPointer = -1;
+ mStatusBarLongPressDowntime = 0L;
mAmbientState.setSwipingUp(false);
if ((isTracking() && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
|| Math.abs(y - mInitialExpandY) > mTouchSlop
@@ -5066,6 +5098,13 @@
}
return true;
}
+ // This touch session has already resulted in shade expansion. Ignore everything else.
+ if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+ && event.getActionMasked() != MotionEvent.ACTION_DOWN
+ && event.getDownTime() == mStatusBarLongPressDowntime) {
+ mShadeLog.d("Touch has same down time as Status Bar long press. Ignoring.");
+ return false;
+ }
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
handled = true;
@@ -5146,6 +5185,7 @@
mUpdateFlingOnLayout = false;
mMotionAborted = false;
mDownTime = mSystemClock.uptimeMillis();
+ mStatusBarLongPressDowntime = 0L;
mTouchAboveFalsingThreshold = false;
mCollapsedAndHeadsUpOnDown =
isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 365666d..be2bf82 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -54,8 +54,10 @@
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.qs.flags.QSComposeFragment;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
@@ -191,7 +193,8 @@
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor,
BouncerViewBinder bouncerViewBinder,
- @ShadeDisplayAware ConfigurationForwarder configurationForwarder) {
+ @ShadeDisplayAware ConfigurationForwarder configurationForwarder,
+ BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
@@ -232,6 +235,11 @@
mView,
notificationLaunchAnimationInteractor.isLaunchAnimationRunning(),
this::setExpandAnimationRunning);
+ if (QSComposeFragment.isEnabled()) {
+ collectFlow(mView,
+ brightnessMirrorShowingInteractor.isShowing(),
+ this::setBrightnessMirrorShowingForDepth);
+ }
var keyguardUnfoldTransition = unfoldComponent.map(
SysUIUnfoldComponent::getKeyguardUnfoldTransition);
@@ -703,6 +711,10 @@
}
}
+ private void setBrightnessMirrorShowingForDepth(boolean showing) {
+ mDepthController.setBrightnessMirrorVisible(showing);
+ }
+
@Override
public void dump(PrintWriter pw, String[] args) {
pw.print(" mExpandingBelowNotch=");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 437d32d..7a18d7c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -27,6 +27,7 @@
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.lifecycle.lifecycleScope
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.fragments.FragmentService
@@ -49,7 +50,6 @@
import java.util.function.Consumer
import javax.inject.Inject
import kotlin.reflect.KMutableProperty0
-import com.android.app.tracing.coroutines.launchTraced as launch
@VisibleForTesting internal const val INSET_DEBOUNCE_MILLIS = 500L
@@ -334,7 +334,7 @@
private data class Paddings(
val containerPadding: Int,
val notificationsMargin: Int,
- val qsContainerPadding: Int
+ val qsContainerPadding: Int,
)
private fun KMutableProperty0<Int>.setAndReportChange(newValue: Int): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index daea977..b085aec 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -98,6 +98,10 @@
/** Returns the ShadeHeadsUpTracker. */
val shadeHeadsUpTracker: ShadeHeadsUpTracker
+ @Deprecated("Temporary a11y solution until dual shade launch b/371224114")
+ /** Notifies the shade that a status bar detected a long press gesture. */
+ fun onStatusBarLongPress(event: MotionEvent)
+
/** Returns the ShadeFoldAnimator. */
@Deprecated("This interface is deprecated in Scene Container")
val shadeFoldAnimator: ShadeFoldAnimator
@@ -179,9 +183,7 @@
/** Returns the expanded height of the panel view. */
@Deprecated("deprecated by SceneContainerFlag.isEnabled") val panelViewExpandedHeight: Float
- /**
- * Returns true if heads up should be visible.
- */
+ /** Returns true if heads up should be visible. */
@Deprecated("deprecated by SceneContainerFlag.isEnabled.") fun shouldHeadsUpBeVisible(): Boolean
/** Return the fraction of the shade that's expanded, when in lockscreen. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 9322d31..53617d0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -35,60 +35,95 @@
ShadeLockscreenInteractor,
PanelExpansionInteractor {
@Deprecated("Use ShadeInteractor instead") override fun expandToNotifications() {}
+
@Deprecated("Use ShadeInteractor instead") override val isExpanded: Boolean = false
override val isPanelExpanded: Boolean = false
+
override fun animateCollapseQs(fullyCollapse: Boolean) {}
+
override fun canBeCollapsed(): Boolean = false
+
@Deprecated("Use ShadeAnimationInteractor instead") override val isCollapsing: Boolean = false
@Deprecated("Use !ShadeInteractor.isAnyExpanded instead")
override val isFullyCollapsed: Boolean = false
override val isTracking: Boolean = false
override val isViewEnabled: Boolean = false
+
override fun shouldHideStatusBarIconsWhenExpanded() = false
+
@Deprecated("Not supported by scenes") override fun blockExpansionForCurrentTouch() {}
+
override fun startExpandLatencyTracking() {}
+
override fun startBouncerPreHideAnimation() {}
+
override fun dozeTimeTick() {}
+
override fun resetViews(animate: Boolean) {}
+
override val barState: Int = 0
+
@Deprecated("Only supported by very old devices that will not adopt scenes.")
override fun closeUserSwitcherIfOpen(): Boolean {
return false
}
+
override fun onBackPressed() {}
+
@Deprecated("According to b/318376223, shade predictive back is not be supported.")
override fun onBackProgressed(progressFraction: Float) {}
+
override fun setAlpha(alpha: Int, animate: Boolean) {}
+
override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
+
@Deprecated("Not supported by scenes") override fun setPulsing(pulsing: Boolean) {}
+
override fun setQsScrimEnabled(qsScrimEnabled: Boolean) {}
+
override fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean) {}
+
override fun updateSystemUiStateFlags() {}
+
override fun updateTouchableRegion() {}
+
override fun transitionToExpandedShade(delay: Long) {}
@Deprecated("Not supported by scenes") override fun resetViewGroupFade() {}
+
@Deprecated("Not supported by scenes")
override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {}
+
@Deprecated("Not supported by scenes") override fun setOverStretchAmount(amount: Float) {}
+
@Deprecated("TODO(b/325072511) delete this")
override fun setKeyguardStatusBarAlpha(alpha: Float) {}
+
override fun showAodUi() {}
+
@Deprecated(
"depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
"{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
)
override val isFullyExpanded = false
+
override fun handleExternalTouch(event: MotionEvent): Boolean {
return false
}
+
override fun handleExternalInterceptTouch(event: MotionEvent): Boolean {
return false
}
override fun startInputFocusTransfer() {}
+
override fun cancelInputFocusTransfer() {}
+
override fun finishInputFocusTransfer(velocity: Float) {}
+
+ @Deprecated("Temporary a11y solution until dual shade launch b/371224114")
+ override fun onStatusBarLongPress(event: MotionEvent) {}
+
override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
@Deprecated("Use SceneInteractor.currentScene instead.")
@@ -98,20 +133,26 @@
class ShadeHeadsUpTrackerEmptyImpl : ShadeHeadsUpTracker {
override fun addTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {}
+
override fun removeTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {}
+
override fun setHeadsUpAppearanceController(
headsUpAppearanceController: HeadsUpAppearanceController?
) {}
+
override val trackedHeadsUpNotification: ExpandableNotificationRow? = null
}
class ShadeFoldAnimatorEmptyImpl : ShadeFoldAnimator {
override fun prepareFoldToAodAnimation() {}
+
override fun startFoldToAodAnimation(
startAction: Runnable,
endAction: Runnable,
cancelAction: Runnable,
) {}
+
override fun cancelFoldToAodAnimation() {}
+
override val view: ViewGroup? = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt
index 5db1dcb..f741b85 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt
@@ -16,7 +16,7 @@
package com.android.systemui.smartspace.config
-import com.android.systemui.Flags.smartspaceSwipeEventLogging
+import com.android.systemui.Flags.smartspaceSwipeEventLoggingFix
import com.android.systemui.Flags.smartspaceViewpager2
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.plugins.BcSmartspaceConfigPlugin
@@ -30,5 +30,5 @@
get() = smartspaceViewpager2()
override val isSwipeEventLoggingEnabled: Boolean
- get() = smartspaceSwipeEventLogging()
+ get() = smartspaceSwipeEventLoggingFix()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
index 72cd63f..ad22caa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesModule.java
@@ -17,8 +17,6 @@
package com.android.systemui.statusbar.dagger;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
-import com.android.systemui.statusbar.notification.row.NotificationRowModule;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
@@ -30,8 +28,7 @@
* Dagger Module providing {@link CentralSurfacesImpl}.
*/
@Module(includes = {CentralSurfacesDependenciesModule.class,
- StatusBarNotificationPresenterModule.class,
- NotificationsModule.class, NotificationRowModule.class})
+ StatusBarNotificationPresenterModule.class})
public interface CentralSurfacesModule {
/**
* Provides our instance of CentralSurfaces which is considered optional.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
index 8a850b0..c416bf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.data
import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule
-import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerStoreModule
import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerModule
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStoreModule
@@ -28,7 +27,6 @@
includes =
[
KeyguardStatusBarRepositoryModule::class,
- PrivacyDotViewControllerStoreModule::class,
RemoteInputRepositoryModule::class,
StatusBarConfigurationControllerModule::class,
StatusBarContentInsetsProviderStoreModule::class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotCorner.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotCorner.kt
new file mode 100644
index 0000000..8a6f355
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotCorner.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.events
+
+import android.view.Gravity
+import android.view.Surface
+
+/** Represents a corner on the display for the privacy dot. */
+enum class PrivacyDotCorner(
+ val index: Int,
+ val gravity: Int,
+ val innerGravity: Int,
+ val title: String,
+) {
+ TopLeft(
+ index = 0,
+ gravity = Gravity.TOP or Gravity.LEFT,
+ innerGravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT,
+ title = "TopLeft",
+ ),
+ TopRight(
+ index = 1,
+ gravity = Gravity.TOP or Gravity.RIGHT,
+ innerGravity = Gravity.CENTER_VERTICAL or Gravity.LEFT,
+ title = "TopRight",
+ ),
+ BottomRight(
+ index = 2,
+ gravity = Gravity.BOTTOM or Gravity.RIGHT,
+ innerGravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT,
+ title = "BottomRight",
+ ),
+ BottomLeft(
+ index = 3,
+ gravity = Gravity.BOTTOM or Gravity.LEFT,
+ innerGravity = Gravity.CENTER_VERTICAL or Gravity.LEFT,
+ title = "BottomLeft",
+ ),
+}
+
+fun PrivacyDotCorner.rotatedCorner(@Surface.Rotation rotation: Int): PrivacyDotCorner {
+ var modded = index - rotation
+ if (modded < 0) {
+ modded += 4
+ }
+ return PrivacyDotCorner.entries[modded]
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 914cc50..f7bc23c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -20,12 +20,13 @@
import android.graphics.Point
import android.graphics.Rect
import android.util.Log
-import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import androidx.core.animation.Animator
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.annotations.GuardedBy
+import com.android.systemui.ScreenDecorationsThread
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -36,6 +37,10 @@
import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomLeft
+import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomRight
+import com.android.systemui.statusbar.events.PrivacyDotCorner.TopLeft
+import com.android.systemui.statusbar.events.PrivacyDotCorner.TopRight
import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -53,7 +58,6 @@
import dagger.assisted.AssistedInject
import java.util.concurrent.Executor
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Understands how to keep the persistent privacy dot in the corner of the screen in
@@ -81,10 +85,6 @@
var showingListener: ShowingListener?
- fun setUiExecutor(e: DelayableExecutor)
-
- fun getUiExecutor(): DelayableExecutor?
-
@UiThread fun setNewRotation(rot: Int)
@UiThread fun hideDotView(dot: View, animate: Boolean)
@@ -117,6 +117,7 @@
@Assisted private val contentInsetsProvider: StatusBarContentInsetsProvider,
private val animationScheduler: SystemStatusAnimationScheduler,
shadeInteractor: ShadeInteractor?,
+ @ScreenDecorationsThread val uiExecutor: DelayableExecutor,
) : PrivacyDotViewController {
private lateinit var tl: View
private lateinit var tr: View
@@ -136,9 +137,6 @@
private val lock = Object()
private var cancelRunnable: Runnable? = null
- // Privacy dots are created in ScreenDecoration's UiThread, which is not the main thread
- private var uiExecutor: DelayableExecutor? = null
-
private val views: Sequence<View>
get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
@@ -155,7 +153,7 @@
private val configurationListener =
object : ConfigurationController.ConfigurationListener {
override fun onLayoutDirectionChanged(isRtl: Boolean) {
- uiExecutor?.execute {
+ uiExecutor.execute {
// If rtl changed, hide all dots until the next state resolves
setCornerVisibilities(View.INVISIBLE)
@@ -198,14 +196,6 @@
stateController.removeCallback(statusBarStateListener)
}
- override fun setUiExecutor(e: DelayableExecutor) {
- uiExecutor = e
- }
-
- override fun getUiExecutor(): DelayableExecutor? {
- return uiExecutor
- }
-
@UiThread
override fun setNewRotation(rot: Int) {
dlog("updateRotation: $rot")
@@ -222,8 +212,8 @@
// If we rotated, hide all dotes until the next state resolves
setCornerVisibilities(View.INVISIBLE)
- val newCorner = selectDesignatedCorner(rot, isRtl)
- val index = newCorner.cornerIndex()
+ val newCornerView = selectDesignatedCorner(rot, isRtl)
+ val corner = newCornerView.corner()
val paddingTop = contentInsetsProvider.getStatusBarPaddingTop(rot)
synchronized(lock) {
@@ -231,8 +221,8 @@
nextViewState.copy(
rotation = rot,
paddingTop = paddingTop,
- designatedCorner = newCorner,
- cornerIndex = index,
+ designatedCorner = newCornerView,
+ corner = corner,
)
}
}
@@ -284,24 +274,15 @@
views.forEach { corner ->
corner.setPadding(0, paddingTop, 0, 0)
- val rotatedCorner = rotatedCorner(cornerForView(corner), rotation)
+ val rotatedCorner = cornerForView(corner).rotatedCorner(rotation)
(corner.layoutParams as FrameLayout.LayoutParams).apply {
- gravity = rotatedCorner.toGravity()
+ gravity = rotatedCorner.gravity
}
// Set the dot's view gravity to hug the status bar
(corner.requireViewById<View>(R.id.privacy_dot).layoutParams
as FrameLayout.LayoutParams)
- .gravity = rotatedCorner.innerGravity()
- }
- }
-
- @UiThread
- private fun updateCornerSizes(l: Int, r: Int, rotation: Int) {
- views.forEach { corner ->
- val rotatedCorner = rotatedCorner(cornerForView(corner), rotation)
- val w = widthForCorner(rotatedCorner, l, r)
- (corner.layoutParams as FrameLayout.LayoutParams).width = w
+ .gravity = rotatedCorner.innerGravity
}
}
@@ -419,25 +400,16 @@
}
}
- private fun cornerForView(v: View): Int {
+ private fun cornerForView(v: View): PrivacyDotCorner {
return when (v) {
- tl -> TOP_LEFT
- tr -> TOP_RIGHT
- bl -> BOTTOM_LEFT
- br -> BOTTOM_RIGHT
+ tl -> TopLeft
+ tr -> TopRight
+ bl -> BottomLeft
+ br -> BottomRight
else -> throw IllegalArgumentException("not a corner view")
}
}
- private fun rotatedCorner(corner: Int, rotation: Int): Int {
- var modded = corner - rotation
- if (modded < 0) {
- modded += 4
- }
-
- return modded
- }
-
@Rotation
private fun activeRotationForCorner(corner: View, rtl: Boolean): Int {
// Each corner will only be visible in a single rotation, based on rtl
@@ -449,16 +421,6 @@
}
}
- private fun widthForCorner(corner: Int, left: Int, right: Int): Int {
- return when (corner) {
- TOP_LEFT,
- BOTTOM_LEFT -> left
- TOP_RIGHT,
- BOTTOM_RIGHT -> right
- else -> throw IllegalArgumentException("Unknown corner")
- }
- }
-
override fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) {
if (
this::tl.isInitialized &&
@@ -478,9 +440,9 @@
val rtl = configurationController.isLayoutRtl
val currentRotation = RotationUtils.getExactRotation(tl.context)
- val dc = selectDesignatedCorner(currentRotation, rtl)
+ val designatedCornerView = selectDesignatedCorner(currentRotation, rtl)
- val index = dc.cornerIndex()
+ val corner = designatedCornerView.corner()
mainExecutor.execute { animationScheduler.addCallback(systemStatusAnimationCallback) }
@@ -494,8 +456,8 @@
nextViewState =
nextViewState.copy(
viewInitialized = true,
- designatedCorner = dc,
- cornerIndex = index,
+ designatedCorner = designatedCornerView,
+ corner = corner,
seascapeRect = left,
portraitRect = top,
landscapeRect = right,
@@ -524,7 +486,7 @@
dlog("scheduleUpdate: ")
cancelRunnable?.run()
- cancelRunnable = uiExecutor?.executeDelayed({ processNextViewState() }, 100)
+ cancelRunnable = uiExecutor.executeDelayed({ processNextViewState() }, 100)
}
@UiThread
@@ -613,11 +575,11 @@
}
}
- private fun View?.cornerIndex(): Int {
+ private fun View?.corner(): PrivacyDotCorner? {
if (this != null) {
return cornerForView(this)
}
- return -1
+ return null
}
// Returns [left, top, right, bottom] aka [seascape, none, landscape, upside-down]
@@ -666,35 +628,11 @@
}
}
-const val TOP_LEFT = 0
-const val TOP_RIGHT = 1
-const val BOTTOM_RIGHT = 2
-const val BOTTOM_LEFT = 3
private const val DURATION = 160L
private const val TAG = "PrivacyDotViewController"
private const val DEBUG = false
private const val DEBUG_VERBOSE = false
-private fun Int.toGravity(): Int {
- return when (this) {
- TOP_LEFT -> Gravity.TOP or Gravity.LEFT
- TOP_RIGHT -> Gravity.TOP or Gravity.RIGHT
- BOTTOM_LEFT -> Gravity.BOTTOM or Gravity.LEFT
- BOTTOM_RIGHT -> Gravity.BOTTOM or Gravity.RIGHT
- else -> throw IllegalArgumentException("Not a corner")
- }
-}
-
-private fun Int.innerGravity(): Int {
- return when (this) {
- TOP_LEFT -> Gravity.CENTER_VERTICAL or Gravity.RIGHT
- TOP_RIGHT -> Gravity.CENTER_VERTICAL or Gravity.LEFT
- BOTTOM_LEFT -> Gravity.CENTER_VERTICAL or Gravity.RIGHT
- BOTTOM_RIGHT -> Gravity.CENTER_VERTICAL or Gravity.LEFT
- else -> throw IllegalArgumentException("Not a corner")
- }
-}
-
data class ViewState(
val viewInitialized: Boolean = false,
val systemPrivacyEventIsActive: Boolean = false,
@@ -707,7 +645,7 @@
val layoutRtl: Boolean = false,
val rotation: Int = 0,
val paddingTop: Int = 0,
- val cornerIndex: Int = -1,
+ val corner: PrivacyDotCorner? = null,
val designatedCorner: View? = null,
val contentDescription: String? = null,
) {
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 ea515e0..08ffbf2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -22,11 +22,13 @@
import androidx.core.animation.ObjectAnimator
import com.android.app.animation.Interpolators
import com.android.app.animation.InterpolatorsAndroidX
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Dumpable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionListener
@@ -49,7 +51,6 @@
import kotlin.math.max
import kotlin.math.min
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
@SysUISingleton
class NotificationWakeUpCoordinator
@@ -65,6 +66,7 @@
private val logger: NotificationWakeUpCoordinatorLogger,
private val notifsKeyguardInteractor: NotificationsKeyguardInteractor,
private val communalInteractor: CommunalInteractor,
+ private val pulseExpansionInteractor: PulseExpansionInteractor,
) :
OnHeadsUpChangedListener,
StatusBarStateController.StateListener,
@@ -115,7 +117,7 @@
// they were blocked by the proximity sensor
updateNotificationVisibility(
animate = shouldAnimateVisibility(),
- increaseSpeed = false
+ increaseSpeed = false,
)
}
}
@@ -139,7 +141,7 @@
// the waking up callback
updateNotificationVisibility(
animate = shouldAnimateVisibility(),
- increaseSpeed = false
+ increaseSpeed = false,
)
}
}
@@ -200,7 +202,7 @@
setNotificationsVisibleForExpansion(
visible = false,
animate = false,
- increaseSpeed = false
+ increaseSpeed = false,
)
}
}
@@ -226,7 +228,7 @@
for (listener in wakeUpListeners) {
listener.onPulseExpandingChanged(pulseExpanding)
}
- notifsKeyguardInteractor.setPulseExpanding(pulseExpanding)
+ pulseExpansionInteractor.setPulseExpanding(pulseExpanding)
}
}
}
@@ -241,7 +243,7 @@
fun setNotificationsVisibleForExpansion(
visible: Boolean,
animate: Boolean,
- increaseSpeed: Boolean
+ increaseSpeed: Boolean,
) {
notificationsVisibleForExpansion = visible
updateNotificationVisibility(animate, increaseSpeed)
@@ -282,7 +284,7 @@
private fun setNotificationsVisible(
visible: Boolean,
animate: Boolean,
- increaseSpeed: Boolean
+ increaseSpeed: Boolean,
) {
if (notificationsVisible == visible) {
return
@@ -363,7 +365,7 @@
hardOverride = hardDozeAmountOverride,
outputLinear = outputLinearDozeAmount,
state = statusBarStateController.state,
- changed = changed
+ changed = changed,
)
stackScrollerController.setDozeAmount(outputEasedDozeAmount)
updateHideAmount()
@@ -372,7 +374,7 @@
setNotificationsVisibleForExpansion(
visible = false,
animate = false,
- increaseSpeed = false
+ increaseSpeed = false,
)
}
}
@@ -389,10 +391,7 @@
* call with `false` at some point in the near future. A call with `true` before that will
* happen if the animation is not already running.
*/
- fun setWakingUp(
- wakingUp: Boolean,
- requestDelayedAnimation: Boolean,
- ) {
+ fun setWakingUp(wakingUp: Boolean, requestDelayedAnimation: Boolean) {
logger.logSetWakingUp(wakingUp, requestDelayedAnimation)
this.wakingUp = wakingUp
if (wakingUp && requestDelayedAnimation) {
@@ -432,7 +431,7 @@
// See: UnlockedScreenOffAnimationController.onFinishedWakingUp()
setHardDozeAmountOverride(
dozing = false,
- source = "Override: Shade->Shade (lock cancelled by unlock)"
+ source = "Override: Shade->Shade (lock cancelled by unlock)",
)
this.state = newState
return
@@ -478,7 +477,7 @@
wasCollapsedEnoughToHide,
isCollapsedEnoughToHide,
couldShowPulsingHuns,
- canShowPulsingHuns
+ canShowPulsingHuns,
)
if (couldShowPulsingHuns && !canShowPulsingHuns) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt
new file mode 100644
index 0000000..925d4a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dagger
+
+import com.android.systemui.statusbar.notification.row.NotificationRowModule
+import dagger.Module
+
+/**
+ * A module that includes the standard notifications classes that most SysUI variants need. Variants
+ * are free to not include this module and instead write a custom notifications module.
+ */
+@Module(includes = [NotificationsModule::class, NotificationRowModule::class])
+object ReferenceNotificationsModule
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 bd6ea30..f9fd5ca 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
@@ -24,7 +24,4 @@
class NotificationsKeyguardViewStateRepository @Inject constructor() {
/** Are notifications fully hidden from view? */
val areNotificationsFullyHidden = MutableStateFlow(false)
-
- /** Is a pulse expansion occurring? */
- val isPulseExpanding = MutableStateFlow(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
index a6361cb..1cb4144 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
@@ -22,12 +22,7 @@
/** Domain logic pertaining to notifications on the keyguard. */
class NotificationsKeyguardInteractor
@Inject
-constructor(
- private val repository: NotificationsKeyguardViewStateRepository,
-) {
- /** Is a pulse expansion occurring? */
- val isPulseExpanding: Flow<Boolean> = repository.isPulseExpanding
-
+constructor(private val repository: NotificationsKeyguardViewStateRepository) {
/** Are notifications fully hidden from view? */
val areNotificationsFullyHidden: Flow<Boolean> = repository.areNotificationsFullyHidden
@@ -35,9 +30,4 @@
fun setNotificationsFullyHidden(fullyHidden: Boolean) {
repository.areNotificationsFullyHidden.value = fullyHidden
}
-
- /** Updates whether a pulse expansion is occurring. */
- fun setPulseExpanding(pulseExpanding: Boolean) {
- repository.isPulseExpanding.value = pulseExpanding
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
index ffab9ea..42acd7bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
@@ -29,19 +29,15 @@
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.constraintlayout.widget.ConstraintSet.VERTICAL
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel.HorizontalPosition
/**
* Container for the stack scroller, so that the bounds can be externally specified, such as from
* the keyguard or shade scenes.
*/
-class SharedNotificationContainer(
- context: Context,
- private val attrs: AttributeSet?,
-) :
- ConstraintLayout(
- context,
- attrs,
- ) {
+class SharedNotificationContainer(context: Context, attrs: AttributeSet?) :
+ ConstraintLayout(context, attrs) {
private val baseConstraintSet = ConstraintSet()
@@ -59,24 +55,40 @@
}
fun updateConstraints(
- useSplitShade: Boolean,
+ horizontalPosition: HorizontalPosition,
marginStart: Int,
marginTop: Int,
marginEnd: Int,
- marginBottom: Int
+ marginBottom: Int,
) {
val constraintSet = ConstraintSet()
constraintSet.clone(baseConstraintSet)
val startConstraintId =
- if (useSplitShade) {
+ if (horizontalPosition is HorizontalPosition.MiddleToEdge) {
R.id.nssl_guideline
} else {
PARENT_ID
}
+
val nsslId = R.id.notification_stack_scroller
constraintSet.apply {
- connect(nsslId, START, startConstraintId, START, marginStart)
+ if (SceneContainerFlag.isEnabled) {
+ when (horizontalPosition) {
+ is HorizontalPosition.FloatAtEnd ->
+ constrainWidth(nsslId, horizontalPosition.width)
+ is HorizontalPosition.MiddleToEdge ->
+ setGuidelinePercent(R.id.nssl_guideline, horizontalPosition.ratio)
+ else -> Unit
+ }
+ }
+
+ if (
+ !SceneContainerFlag.isEnabled ||
+ horizontalPosition !is HorizontalPosition.FloatAtEnd
+ ) {
+ connect(nsslId, START, startConstraintId, START, marginStart)
+ }
connect(nsslId, END, PARENT_ID, END, marginEnd)
connect(nsslId, BOTTOM, PARENT_ID, BOTTOM, marginBottom)
connect(nsslId, TOP, PARENT_ID, TOP, marginTop)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index ce89d78..4a55dfa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -18,6 +18,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Flags
import com.android.systemui.common.ui.view.onLayoutChanged
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -36,11 +37,8 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Binds the shared notification container to its view-model. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class SharedNotificationContainerBinder
@Inject
@@ -74,7 +72,7 @@
launch {
viewModel.configurationBasedDimensions.collect {
view.updateConstraints(
- useSplitShade = it.useSplitShade,
+ horizontalPosition = it.horizontalPosition,
marginStart = it.marginStart,
marginTop = it.marginTop,
marginEnd = it.marginEnd,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index e6663d5..96d0d76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -224,33 +224,54 @@
if (SceneContainerFlag.isEnabled) {
combine(
shadeInteractor.isShadeLayoutWide,
+ shadeInteractor.shadeMode,
configurationInteractor.onAnyConfigurationChange,
- ) { isShadeLayoutWide, _ ->
+ ) { isShadeLayoutWide, shadeMode, _ ->
with(context.resources) {
// TODO(b/338033836): Define separate horizontal margins for dual shade.
val marginHorizontal =
getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+
+ val horizontalPosition =
+ when (shadeMode) {
+ Single -> HorizontalPosition.EdgeToEdge
+ Split -> HorizontalPosition.MiddleToEdge(ratio = 0.5f)
+ Dual ->
+ if (isShadeLayoutWide) {
+ HorizontalPosition.FloatAtEnd(
+ width = getDimensionPixelSize(R.dimen.shade_panel_width)
+ )
+ } else {
+ HorizontalPosition.EdgeToEdge
+ }
+ }
+
ConfigurationBasedDimensions(
- marginStart = if (isShadeLayoutWide) 0 else marginHorizontal,
+ horizontalPosition = horizontalPosition,
+ marginStart =
+ if (horizontalPosition is HorizontalPosition.EdgeToEdge)
+ marginHorizontal
+ else 0,
marginEnd = marginHorizontal,
marginBottom =
getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
// y position of the NSSL in the window needs to be 0 under scene
// container
marginTop = 0,
- useSplitShade = isShadeLayoutWide,
)
}
}
} else {
interactor.configurationBasedDimensions.map {
ConfigurationBasedDimensions(
+ horizontalPosition =
+ if (it.useSplitShade) HorizontalPosition.MiddleToEdge()
+ else HorizontalPosition.EdgeToEdge,
marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
marginEnd = it.marginHorizontal,
marginBottom = it.marginBottom,
marginTop =
if (it.useLargeScreenHeader) it.marginTopLargeScreen else it.marginTop,
- useSplitShade = it.useSplitShade,
)
}
}
@@ -446,59 +467,63 @@
*/
private val alphaForShadeAndQsExpansion: Flow<Float> =
if (SceneContainerFlag.isEnabled) {
- shadeInteractor.shadeMode.flatMapLatest { shadeMode ->
- when (shadeMode) {
- Single ->
- combineTransform(
- shadeInteractor.shadeExpansion,
- shadeInteractor.qsExpansion,
- ) { shadeExpansion, qsExpansion ->
- if (qsExpansion == 1f) {
- // Ensure HUNs will be visible in QS shade (at least while unlocked)
+ shadeInteractor.shadeMode.flatMapLatest { shadeMode ->
+ when (shadeMode) {
+ Single ->
+ combineTransform(
+ shadeInteractor.shadeExpansion,
+ shadeInteractor.qsExpansion,
+ ) { shadeExpansion, qsExpansion ->
+ if (qsExpansion == 1f) {
+ // Ensure HUNs will be visible in QS shade (at least while
+ // unlocked)
+ emit(1f)
+ } else if (shadeExpansion > 0f || qsExpansion > 0f) {
+ // Fade as QS shade expands
+ emit(1f - qsExpansion)
+ }
+ }
+ Split -> isAnyExpanded.filter { it }.map { 1f }
+ Dual ->
+ combineTransform(
+ headsUpNotificationInteractor.get().isHeadsUpOrAnimatingAway,
+ shadeInteractor.shadeExpansion,
+ shadeInteractor.qsExpansion,
+ ) { isHeadsUpOrAnimatingAway, shadeExpansion, qsExpansion ->
+ if (isHeadsUpOrAnimatingAway) {
+ // Ensure HUNs will be visible in QS shade (at least while
+ // unlocked)
+ emit(1f)
+ } else if (shadeExpansion > 0f || qsExpansion > 0f) {
+ // Fade out as QS shade expands
+ emit(1f - qsExpansion)
+ }
+ }
+ }
+ }
+ } else {
+ interactor.configurationBasedDimensions.flatMapLatest { configurationBasedDimensions
+ ->
+ combineTransform(shadeInteractor.shadeExpansion, shadeInteractor.qsExpansion) {
+ shadeExpansion,
+ qsExpansion ->
+ if (shadeExpansion > 0f || qsExpansion > 0f) {
+ if (configurationBasedDimensions.useSplitShade) {
emit(1f)
- } else if (shadeExpansion > 0f || qsExpansion > 0f) {
+ } else if (qsExpansion == 1f) {
+ // Ensure HUNs will be visible in QS shade (at least while
+ // unlocked)
+ emit(1f)
+ } else {
// Fade as QS shade expands
emit(1f - qsExpansion)
}
}
- Split -> isAnyExpanded.filter { it }.map { 1f }
- Dual ->
- combineTransform(
- headsUpNotificationInteractor.get().isHeadsUpOrAnimatingAway,
- shadeInteractor.shadeExpansion,
- shadeInteractor.qsExpansion,
- ) { isHeadsUpOrAnimatingAway, shadeExpansion, qsExpansion ->
- if (isHeadsUpOrAnimatingAway) {
- // Ensure HUNs will be visible in QS shade (at least while unlocked)
- emit(1f)
- } else if (shadeExpansion > 0f || qsExpansion > 0f) {
- // Fade out as QS shade expands
- emit(1f - qsExpansion)
- }
- }
- }
- }
- } else {
- interactor.configurationBasedDimensions.flatMapLatest { configurationBasedDimensions ->
- combineTransform(shadeInteractor.shadeExpansion, shadeInteractor.qsExpansion) {
- shadeExpansion,
- qsExpansion ->
- if (shadeExpansion > 0f || qsExpansion > 0f) {
- if (configurationBasedDimensions.useSplitShade) {
- emit(1f)
- } else if (qsExpansion == 1f) {
- // Ensure HUNs will be visible in QS shade (at least while unlocked)
- emit(1f)
- } else {
- // Fade as QS shade expands
- emit(1f - qsExpansion)
- }
}
}
}
- }
- .onStart { emit(1f) }
- .dumpWhileCollecting("alphaForShadeAndQsExpansion")
+ .onStart { emit(1f) }
+ .dumpWhileCollecting("alphaForShadeAndQsExpansion")
val panelAlpha = keyguardInteractor.panelAlpha
@@ -766,10 +791,25 @@
}
data class ConfigurationBasedDimensions(
+ val horizontalPosition: HorizontalPosition,
val marginStart: Int,
val marginTop: Int,
val marginEnd: Int,
val marginBottom: Int,
- val useSplitShade: Boolean,
)
+
+ /** Specifies the horizontal layout constraints for the notification container. */
+ sealed interface HorizontalPosition {
+ /** The container is using the full width of the screen (minus any margins). */
+ data object EdgeToEdge : HorizontalPosition
+
+ /** The container is laid out from the given [ratio] of the screen width to the end edge. */
+ data class MiddleToEdge(val ratio: Float = 0.5f) : HorizontalPosition
+
+ /**
+ * The container has a fixed [width] and is aligned to the end of the screen. In this
+ * layout, the start edge of the container is floating, i.e. unconstrained.
+ */
+ data class FloatAtEnd(val width: Int) : HorizontalPosition
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 65663fd..99efba4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1105,7 +1105,7 @@
mJavaAdapter.alwaysCollectFlow(
mCommunalInteractor.isIdleOnCommunal(),
mIdleOnCommunalConsumer);
- if (SceneContainerFlag.isEnabled()) {
+ if (SceneContainerFlag.isEnabled() || QSComposeFragment.isEnabled()) {
mJavaAdapter.alwaysCollectFlow(
mBrightnessMirrorShowingInteractor.isShowing(),
this::setBrightnessMirrorShowing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index e7d9717..91c43dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -39,6 +39,8 @@
import com.android.systemui.Flags;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.res.R;
+import com.android.systemui.shade.LongPressGestureDetector;
+import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
@@ -67,6 +69,7 @@
private InsetsFetcher mInsetsFetcher;
private int mDensity;
private float mFontScale;
+ private LongPressGestureDetector mLongPressGestureDetector;
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -78,6 +81,12 @@
mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class);
}
+ void setLongPressGestureDetector(LongPressGestureDetector longPressGestureDetector) {
+ if (ShadeExpandsOnStatusBarLongPress.isEnabled()) {
+ mLongPressGestureDetector = longPressGestureDetector;
+ }
+ }
+
void setTouchEventHandler(Gefingerpoken handler) {
mTouchEventHandler = handler;
}
@@ -198,6 +207,9 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
+ if (ShadeExpandsOnStatusBarLongPress.isEnabled() && mLongPressGestureDetector != null) {
+ mLongPressGestureDetector.handleTouch(event);
+ }
if (mTouchEventHandler == null) {
Log.w(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 746d6a7..c24f432 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -33,7 +33,9 @@
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.shade.LongPressGestureDetector
import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
@@ -66,6 +68,7 @@
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
private val panelExpansionInteractor: PanelExpansionInteractor,
+ private val longPressGestureDetector: Provider<LongPressGestureDetector>,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -114,6 +117,10 @@
addDarkReceivers()
addCursorSupportToIconContainers()
+ if (ShadeExpandsOnStatusBarLongPress.isEnabled) {
+ mView.setLongPressGestureDetector(longPressGestureDetector.get())
+ }
+
progressProvider?.setReadyToHandleTransition(true)
configurationController.addCallback(configurationListener)
@@ -328,6 +335,7 @@
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
private val panelExpansionInteractor: PanelExpansionInteractor,
+ private val longPressGestureDetector: Provider<LongPressGestureDetector>,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
@@ -352,6 +360,7 @@
shadeController,
shadeViewController,
panelExpansionInteractor,
+ longPressGestureDetector,
windowRootView,
shadeLogger,
statusBarMoveFromCenterAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 99f25bd..e4a75be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -31,6 +31,7 @@
import com.android.systemui.statusbar.core.StatusBarInitializerStore
import com.android.systemui.statusbar.core.StatusBarOrchestrator
import com.android.systemui.statusbar.core.StatusBarSimpleFragment
+import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerStoreModule
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.events.PrivacyDotViewControllerModule
import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
@@ -46,7 +47,9 @@
import kotlinx.coroutines.CoroutineScope
/** Similar in purpose to [StatusBarModule], but scoped only to phones */
-@Module(includes = [PrivacyDotViewControllerModule::class])
+@Module(
+ includes = [PrivacyDotViewControllerModule::class, PrivacyDotViewControllerStoreModule::class]
+)
interface StatusBarPhoneModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
index 5392e38..903c7e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
@@ -34,7 +34,7 @@
LazyVerticalGrid(
columns = GridCells.Fixed(1),
- modifier = Modifier.fillMaxWidth().heightIn(max = 320.dp),
+ modifier = Modifier.fillMaxWidth().heightIn(max = 280.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
index f2132248..70fd5ab 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
@@ -25,9 +25,7 @@
/** [Sequence] that yields all of the direct children of this [ViewGroup] */
val ViewGroup.children
- get() = sequence {
- for (i in 0 until childCount) yield(getChildAt(i))
- }
+ get() = sequence { for (i in 0 until childCount) yield(getChildAt(i)) }
/** Inclusive version of [Iterable.takeWhile] */
fun <T> Sequence<T>.takeUntil(pred: (T) -> Boolean): Sequence<T> = sequence {
@@ -62,3 +60,25 @@
fun <T> Lazy<T>.toKotlinLazy(): kotlin.Lazy<T> {
return lazy { this.get() }
}
+
+/**
+ * Returns whether this [Collection] contains exactly all [elements].
+ *
+ * Order of elements is not taken into account, but multiplicity is. For example, an element
+ * duplicated exactly 3 times in the parameter asserts that the element must likewise be duplicated
+ * exactly 3 times in this [Collection].
+ */
+fun <T> Collection<T>.containsExactly(vararg elements: T): Boolean {
+ return eachCountMap() == elements.asList().eachCountMap()
+}
+
+/**
+ * Returns a map where keys are the distinct elements of the collection and values are their
+ * corresponding counts.
+ *
+ * This is a convenient extension function for any [Collection] that allows you to easily count the
+ * occurrences of each element.
+ */
+fun <T> Collection<T>.eachCountMap(): Map<T, Int> {
+ return groupingBy { it }.eachCount()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
index d2ed71c..55cdfb2 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
@@ -19,11 +19,8 @@
import android.content.Context;
import android.os.Handler;
-import com.android.systemui.Flags;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import dagger.Lazy;
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
@@ -40,13 +37,11 @@
private final WakeLock mInner;
@AssistedInject
- public DelayedWakeLock(@Background Lazy<Handler> bgHandler,
- @Main Lazy<Handler> mainHandler,
+ public DelayedWakeLock(@Background Handler bgHandler,
Context context, WakeLockLogger logger,
@Assisted String tag) {
mInner = WakeLock.createPartial(context, logger, tag);
- mHandler = Flags.delayedWakelockReleaseOnBackgroundThread() ? bgHandler.get()
- : mainHandler.get();
+ mHandler = bgHandler;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
index f763ee4..f00e3d1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
@@ -18,14 +18,9 @@
import android.content.Context;
import android.os.PowerManager;
-import android.util.Log;
import androidx.annotation.VisibleForTesting;
-import com.android.systemui.Flags;
-
-import java.util.HashMap;
-
import javax.inject.Inject;
/** WakeLock wrapper for testability */
@@ -114,59 +109,7 @@
@VisibleForTesting
static WakeLock wrap(
final PowerManager.WakeLock inner, WakeLockLogger logger, long maxTimeout) {
- if (Flags.delayedWakelockReleaseOnBackgroundThread()) {
- return new ClientTrackingWakeLock(inner, logger, maxTimeout);
- }
-
- // Non-thread safe implementation, remove when flag above is removed.
- return new WakeLock() {
- private final HashMap<String, Integer> mActiveClients = new HashMap<>();
-
- /** @see PowerManager.WakeLock#acquire() */
- public void acquire(String why) {
- mActiveClients.putIfAbsent(why, 0);
- int count = mActiveClients.get(why) + 1;
- mActiveClients.put(why, count);
- if (logger != null) {
- logger.logAcquire(inner, why, count);
- }
- if (maxTimeout == Builder.NO_TIMEOUT) {
- inner.acquire();
- } else {
- inner.acquire(maxTimeout);
- }
- }
-
- /** @see PowerManager.WakeLock#release() */
- public void release(String why) {
- Integer count = mActiveClients.get(why);
- if (count == null) {
- Log.wtf(TAG, "Releasing WakeLock with invalid reason: " + why,
- new Throwable());
- return;
- }
- count--;
- if (count == 0) {
- mActiveClients.remove(why);
- } else {
- mActiveClients.put(why, count);
- }
- if (logger != null) {
- logger.logRelease(inner, why, count);
- }
- inner.release();
- }
-
- /** @see PowerManager.WakeLock#wrap(Runnable) */
- public Runnable wrap(Runnable runnable) {
- return wrapImpl(this, runnable);
- }
-
- @Override
- public String toString() {
- return "active clients= " + mActiveClients;
- }
- };
+ return new ClientTrackingWakeLock(inner, logger, maxTimeout);
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 344d065..0769ada 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -104,6 +104,7 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.kotlin.JavaAdapter;
@@ -186,16 +187,17 @@
private List<DecorProvider> mMockCutoutList;
private final CameraProtectionLoader mCameraProtectionLoader =
new CameraProtectionLoaderImpl(mContext);
+ private Handler mMainHandler;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- Handler mainHandler = new Handler(TestableLooper.get(this).getLooper());
+ mMainHandler = new Handler(TestableLooper.get(this).getLooper());
mSecureSettings = new FakeSettings();
mExecutor = new FakeExecutor(new FakeSystemClock());
mThreadFactory = new FakeThreadFactory(mExecutor);
- mThreadFactory.setHandler(mainHandler);
+ mThreadFactory.setHandler(mMainHandler);
mWindowManager = mock(WindowManager.class);
WindowMetrics metrics = mContext.getSystemService(WindowManager.class)
@@ -214,26 +216,26 @@
when(mMockTypedArray.length()).thenReturn(0);
mPrivacyDotTopLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
R.id.privacy_dot_top_left_container,
- DisplayCutout.BOUNDS_POSITION_TOP,
- DisplayCutout.BOUNDS_POSITION_LEFT,
+ BOUNDS_POSITION_TOP,
+ BOUNDS_POSITION_LEFT,
R.layout.privacy_dot_top_left));
mPrivacyDotTopRightDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
R.id.privacy_dot_top_right_container,
- DisplayCutout.BOUNDS_POSITION_TOP,
- DisplayCutout.BOUNDS_POSITION_RIGHT,
+ BOUNDS_POSITION_TOP,
+ BOUNDS_POSITION_RIGHT,
R.layout.privacy_dot_top_right));
mPrivacyDotBottomLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
R.id.privacy_dot_bottom_left_container,
- DisplayCutout.BOUNDS_POSITION_BOTTOM,
- DisplayCutout.BOUNDS_POSITION_LEFT,
+ BOUNDS_POSITION_BOTTOM,
+ BOUNDS_POSITION_LEFT,
R.layout.privacy_dot_bottom_left));
mPrivacyDotBottomRightDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl(
R.id.privacy_dot_bottom_right_container,
- DisplayCutout.BOUNDS_POSITION_BOTTOM,
- DisplayCutout.BOUNDS_POSITION_RIGHT,
+ BOUNDS_POSITION_BOTTOM,
+ BOUNDS_POSITION_RIGHT,
R.layout.privacy_dot_bottom_right));
// Default no cutout
@@ -256,11 +258,10 @@
mLazyViewCapture, false);
mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings,
mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController,
- mThreadFactory,
mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader,
- mViewCaptureAwareWindowManager) {
+ mViewCaptureAwareWindowManager, mMainHandler, mExecutor) {
@Override
public void start() {
super.start();
@@ -1272,10 +1273,10 @@
ScreenDecorations screenDecorations = new ScreenDecorations(mContext,
mSecureSettings, mCommandRegistry, mUserTracker, mDisplayTracker,
mDotViewController,
- mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
+ mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader,
- mViewCaptureAwareWindowManager);
+ mViewCaptureAwareWindowManager, mMainHandler, mExecutor);
screenDecorations.start();
when(mContext.getDisplay()).thenReturn(mDisplay);
when(mDisplay.getDisplayInfo(any())).thenAnswer(new Answer<Boolean>() {
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 61eeab3..e1b8a1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -22,7 +22,6 @@
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricManager
-import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.PromptVerticalListContentView
@@ -138,6 +137,7 @@
PromptSelectorInteractorImpl(
fingerprintRepository,
displayStateInteractor,
+ credentialInteractor,
biometricPromptRepository,
lockPatternUtils,
)
@@ -412,7 +412,6 @@
@Test
fun testShowBiometricUI_ContentViewWithMoreOptionsButton() {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
var isButtonClicked = false
val contentView =
PromptContentViewWithMoreOptionsButton.Builder()
@@ -449,7 +448,6 @@
@Test
fun testShowCredentialUI_withVerticalListContentView() {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val container =
initializeFingerprintContainer(
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
@@ -470,7 +468,6 @@
@Test
fun testShowCredentialUI_withContentViewWithMoreOptionsButton() {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val contentView =
PromptContentViewWithMoreOptionsButton.Builder()
.setMoreOptionsButtonListener(fakeExecutor) { _, _ -> }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
similarity index 81%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index a06784e..f7059e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -18,25 +18,30 @@
import android.content.Intent
import android.graphics.Rect
+import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper
import android.view.View
import android.view.ViewGroup
import android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
import com.android.systemui.SysuiTestCase
import com.android.systemui.activity.SingleActivityFactory
+import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
+import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModelFactory
+import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableStateFlow
@@ -53,11 +58,25 @@
import org.mockito.Mockito.eq
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@TestableLooper.RunWithLooper
-class BrightnessDialogTest : SysuiTestCase() {
+class BrightnessDialogTest(val flags: FlagsParameterization) : SysuiTestCase() {
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ val viewId by lazy {
+ if (QSComposeFragment.isEnabled) {
+ R.id.brightness_dialog_slider
+ } else {
+ R.id.brightness_mirror_container
+ }
+ }
@Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory
@Mock private lateinit var brightnessSliderController: BrightnessSliderController
@@ -66,10 +85,14 @@
@Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper
@Mock private lateinit var shadeInteractor: ShadeInteractor
+ private val kosmos = testKosmos()
+
private val clock = FakeSystemClock()
private val mainExecutor = FakeExecutor(clock)
- @Rule
+ private val onDestroyLatch = CountDownLatch(1)
+
+ @Rule(order = 200)
@JvmField
var activityRule =
ActivityTestRule(
@@ -79,7 +102,9 @@
brightnessControllerFactory,
mainExecutor,
accessibilityMgr,
- shadeInteractor
+ shadeInteractor,
+ kosmos.brightnessSliderViewModelFactory,
+ onDestroyLatch,
)
},
/* initialTouchMode= */ false,
@@ -99,12 +124,13 @@
@After
fun tearDown() {
activityRule.finishActivity()
+ onDestroyLatch.await()
}
@Test
fun testGestureExclusion() {
activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
- val frame = activityRule.activity.requireViewById<View>(R.id.brightness_mirror_container)
+ val frame = activityRule.activity.requireViewById<View>(viewId)
val lp = frame.layoutParams as ViewGroup.MarginLayoutParams
val horizontalMargin =
@@ -125,7 +151,7 @@
`when`(
accessibilityMgr.getRecommendedTimeoutMillis(
eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
- anyInt()
+ anyInt(),
)
)
.thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
@@ -144,7 +170,7 @@
`when`(
accessibilityMgr.getRecommendedTimeoutMillis(
eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
- anyInt()
+ anyInt(),
)
)
.thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
@@ -171,7 +197,7 @@
`when`(
accessibilityMgr.getRecommendedTimeoutMillis(
eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
- anyInt()
+ anyInt(),
)
)
.thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
@@ -205,14 +231,17 @@
brightnessControllerFactory: BrightnessController.Factory,
mainExecutor: DelayableExecutor,
accessibilityMgr: AccessibilityManagerWrapper,
- shadeInteractor: ShadeInteractor
+ shadeInteractor: ShadeInteractor,
+ brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory,
+ private val countdownLatch: CountDownLatch,
) :
BrightnessDialog(
brightnessSliderControllerFactory,
brightnessControllerFactory,
mainExecutor,
accessibilityMgr,
- shadeInteractor
+ shadeInteractor,
+ brightnessSliderViewModelFactory,
) {
var finishing = MutableStateFlow(false)
@@ -223,5 +252,18 @@
override fun requestFinish() {
finishing.value = true
}
+
+ override fun onDestroy() {
+ super.onDestroy()
+ countdownLatch.countDown()
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(QSComposeFragment.FLAG_NAME)
+ }
}
}
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 fdfc253..06e8b1c 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
@@ -91,6 +91,7 @@
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
+import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -559,7 +560,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void manageNotifications_visible() {
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
@@ -572,7 +573,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void clearAll_visible() {
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
@@ -585,7 +586,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void testInflateFooterView() {
mStackScroller.inflateFooterView();
ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class);
@@ -596,7 +597,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void testUpdateFooter_noNotifications() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(true);
@@ -608,7 +609,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void testUpdateFooter_remoteInput() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(true);
@@ -625,7 +626,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void testUpdateFooter_withoutNotifications() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(true);
@@ -641,7 +642,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void testUpdateFooter_oneClearableNotification() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(true);
@@ -657,7 +658,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void testUpdateFooter_withoutHistory() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(true);
@@ -674,7 +675,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void testUpdateFooter_oneClearableNotification_beforeUserSetup() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(false);
@@ -690,7 +691,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void testUpdateFooter_oneNonClearableNotification() {
setBarStateForTest(StatusBarState.SHADE);
mStackScroller.setCurrentUserSetup(true);
@@ -1181,7 +1182,7 @@
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
+ @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
public void hasFilteredOutSeenNotifs_updateFooter() {
mStackScroller.setCurrentUserSetup(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 008e8ce..638f195 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -40,6 +40,7 @@
import com.android.systemui.plugins.fakeDarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.shade.LongPressGestureDetector
import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
@@ -97,6 +98,7 @@
@Mock private lateinit var windowRootView: Provider<WindowRootView>
@Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var viewUtil: ViewUtil
+ @Mock private lateinit var longPressGestureDetector: LongPressGestureDetector
private lateinit var statusBarWindowStateController: StatusBarWindowStateController
private lateinit var view: PhoneStatusBarView
@@ -393,6 +395,7 @@
shadeControllerImpl,
shadeViewController,
panelExpansionInteractor,
+ { longPressGestureDetector },
windowRootView,
shadeLogger,
viewUtil,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt
new file mode 100644
index 0000000..0c0b5ba
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.phone.data.repository
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.KeyguardBypassRepository
+import com.android.systemui.keyguard.data.repository.configureKeyguardBypass
+import com.android.systemui.keyguard.data.repository.keyguardBypassRepository
+import com.android.systemui.keyguard.data.repository.verifyCallback
+import com.android.systemui.keyguard.data.repository.verifyNoCallback
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
+import com.android.systemui.statusbar.policy.devicePostureController
+import com.android.systemui.testKosmos
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.tuner.tunerService
+import com.android.systemui.util.mockito.withArgCaptor
+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.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
+class KeyguardBypassRepositoryTest : SysuiTestCase() {
+ @JvmField @Rule val mockito: MockitoRule = MockitoJUnit.rule()
+
+ private lateinit var tunableCallback: TunerService.Tunable
+ private lateinit var postureControllerCallback: DevicePostureController.Callback
+
+ private val kosmos = testKosmos()
+ private lateinit var underTest: KeyguardBypassRepository
+ private val testScope = kosmos.testScope
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ // overrideFaceBypassSetting overridden to true
+ // isFaceEnrolledAndEnabled true
+ // isPostureAllowedForFaceAuth true/false on posture changes
+ @Test
+ fun updatesBypassAvailableOnPostureChanges_bypassOverrideAlways() =
+ testScope.runTest {
+ // KeyguardBypassRepository#overrideFaceBypassSetting = true due to ALWAYS override
+ // Initialize face auth posture to DEVICE_POSTURE_OPENED config
+ initUnderTest(
+ faceUnlockBypassOverrideConfig = FACE_UNLOCK_BYPASS_ALWAYS,
+ faceAuthPostureConfig = DEVICE_POSTURE_CLOSED,
+ )
+ val isBypassAvailable by collectLastValue(underTest.isBypassAvailable)
+ runCurrent()
+
+ postureControllerCallback = kosmos.devicePostureController.verifyCallback()
+
+ // Update face auth posture to match config
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED)
+
+ // Assert bypass available
+ assertThat(isBypassAvailable).isTrue()
+
+ // Set face auth posture to not match config
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ // Assert bypass not available
+ assertThat(isBypassAvailable).isFalse()
+ }
+
+ // overrideFaceBypassSetting overridden to false
+ // isFaceEnrolledAndEnabled true
+ // isPostureAllowedForFaceAuth true/false on posture changes
+ @Test
+ fun updatesBypassEnabledOnPostureChanges_bypassOverrideNever() =
+ testScope.runTest {
+ // KeyguardBypassRepository#overrideFaceBypassSetting = false due to NEVER override
+ // Initialize face auth posture to DEVICE_POSTURE_OPENED config
+ initUnderTest(
+ faceUnlockBypassOverrideConfig = FACE_UNLOCK_BYPASS_NEVER,
+ faceAuthPostureConfig = DEVICE_POSTURE_CLOSED,
+ )
+ val bypassEnabled by collectLastValue(underTest.isBypassAvailable)
+ runCurrent()
+ postureControllerCallback = kosmos.devicePostureController.verifyCallback()
+
+ // Update face auth posture to match config
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED)
+
+ // Assert bypass not enabled
+ assertThat(bypassEnabled).isFalse()
+
+ // Set face auth posture to not match config
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ // Assert bypass not enabled
+ assertThat(bypassEnabled).isFalse()
+ }
+
+ // overrideFaceBypassSetting set true/false depending on Setting
+ // isFaceEnrolledAndEnabled true
+ // isPostureAllowedForFaceAuth true
+ @Test
+ fun updatesBypassEnabledOnSettingsChanges_bypassNoOverride_devicePostureMatchesConfig() =
+ testScope.runTest {
+ // No bypass override
+ // Initialize face auth posture to DEVICE_POSTURE_OPENED config
+ initUnderTest(
+ faceUnlockBypassOverrideConfig = FACE_UNLOCK_BYPASS_NO_OVERRIDE,
+ faceAuthPostureConfig = DEVICE_POSTURE_CLOSED,
+ )
+
+ val bypassEnabled by collectLastValue(underTest.isBypassAvailable)
+ runCurrent()
+ postureControllerCallback = kosmos.devicePostureController.verifyCallback()
+ tunableCallback = kosmos.tunerService.captureCallback()
+
+ // Update face auth posture to match config
+ postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED)
+
+ // FACE_UNLOCK_DISMISSES_KEYGUARD setting true
+ whenever(kosmos.tunerService.getValue(eq(faceUnlockDismissesKeyguard), anyInt()))
+ .thenReturn(1)
+ tunableCallback.onTuningChanged(faceUnlockDismissesKeyguard, "")
+
+ runCurrent()
+ // Assert bypass enabled
+ assertThat(bypassEnabled).isTrue()
+
+ // FACE_UNLOCK_DISMISSES_KEYGUARD setting false
+ whenever(kosmos.tunerService.getValue(eq(faceUnlockDismissesKeyguard), anyInt()))
+ .thenReturn(0)
+ tunableCallback.onTuningChanged(faceUnlockDismissesKeyguard, "")
+
+ runCurrent()
+ // Assert bypass not enabled
+ assertThat(bypassEnabled).isFalse()
+ }
+
+ // overrideFaceBypassSetting overridden to true
+ // isFaceEnrolledAndEnabled true
+ // isPostureAllowedForFaceAuth always true given DEVICE_POSTURE_UNKNOWN config
+ @Test
+ fun bypassEnabledTrue_bypassAlways_unknownDevicePostureConfig() =
+ testScope.runTest {
+ // KeyguardBypassRepository#overrideFaceBypassSetting = true due to ALWAYS override
+ // Set face auth posture config to unknown
+ initUnderTest(
+ faceUnlockBypassOverrideConfig = FACE_UNLOCK_BYPASS_ALWAYS,
+ faceAuthPostureConfig = DEVICE_POSTURE_UNKNOWN,
+ )
+ val bypassEnabled by collectLastValue(underTest.isBypassAvailable)
+ kosmos.devicePostureController.verifyNoCallback()
+
+ // Assert bypass enabled
+ assertThat(bypassEnabled).isTrue()
+ }
+
+ // overrideFaceBypassSetting overridden to false
+ // isFaceEnrolledAndEnabled true
+ // isPostureAllowedForFaceAuth always true given DEVICE_POSTURE_UNKNOWN config
+ @Test
+ fun bypassEnabledFalse_bypassNever_unknownDevicePostureConfig() =
+ testScope.runTest {
+ // KeyguardBypassRepository#overrideFaceBypassSetting = false due to NEVER override
+ // Set face auth posture config to unknown
+ initUnderTest(
+ faceUnlockBypassOverrideConfig = FACE_UNLOCK_BYPASS_NEVER,
+ faceAuthPostureConfig = DEVICE_POSTURE_UNKNOWN,
+ )
+ val bypassEnabled by collectLastValue(underTest.isBypassAvailable)
+ kosmos.devicePostureController.verifyNoCallback()
+
+ // Assert bypass enabled
+ assertThat(bypassEnabled).isFalse()
+ }
+
+ private fun TestScope.initUnderTest(
+ faceUnlockBypassOverrideConfig: Int,
+ faceAuthPostureConfig: Int,
+ ) {
+ kosmos.configureKeyguardBypass(
+ faceAuthEnrolledAndEnabled = true,
+ faceUnlockBypassOverrideConfig = faceUnlockBypassOverrideConfig,
+ faceAuthPostureConfig = faceAuthPostureConfig,
+ )
+ underTest = kosmos.keyguardBypassRepository
+ runCurrent()
+ }
+
+ companion object {
+ private const val FACE_UNLOCK_BYPASS_NO_OVERRIDE = 0
+ private const val FACE_UNLOCK_BYPASS_ALWAYS = 1
+ private const val FACE_UNLOCK_BYPASS_NEVER = 2
+ }
+}
+
+private const val faceUnlockDismissesKeyguard = Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD
+
+private fun TunerService.captureCallback() =
+ withArgCaptor<TunerService.Tunable> {
+ verify(this@captureCallback).addTunable(capture(), eq(faceUnlockDismissesKeyguard))
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index 020f7fa..c945812 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -30,6 +30,8 @@
import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.data.repository.PulseExpansionRepository
import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule
import com.android.systemui.scene.SceneContainerFrameworkModule
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -70,11 +72,17 @@
interface SysUITestModule {
@Binds fun bindTestableContext(sysuiTestableContext: SysuiTestableContext): TestableContext
+
@Binds fun bindContext(testableContext: TestableContext): Context
+
@Binds @Application fun bindAppContext(context: Context): Context
+
@Binds @Application fun bindAppResources(resources: Resources): Resources
+
@Binds @Main fun bindMainResources(resources: Resources): Resources
+
@Binds fun bindBroadcastDispatcher(fake: FakeBroadcastDispatcher): BroadcastDispatcher
+
@Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor
@Binds
@@ -108,7 +116,7 @@
@Provides
fun provideBaseShadeInteractor(
sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>,
- sceneContainerOff: Provider<ShadeInteractorLegacyImpl>
+ sceneContainerOff: Provider<ShadeInteractorLegacyImpl>,
): BaseShadeInteractor {
return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
@@ -125,6 +133,12 @@
): SceneDataSourceDelegator {
return SceneDataSourceDelegator(applicationScope, config)
}
+
+ @Provides
+ @SysUISingleton
+ fun providesPulseExpansionRepository(dumpManager: DumpManager): PulseExpansionRepository {
+ return PulseExpansionRepository(dumpManager)
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt
index 56297f0..787a471 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt
@@ -27,6 +27,7 @@
fingerprintPropertyRepository = fingerprintPropertyRepository,
displayStateInteractor = displayStateInteractor,
promptRepository = promptRepository,
- lockPatternUtils = lockPatternUtils
+ credentialInteractor = FakeCredentialInteractor(),
+ lockPatternUtils = lockPatternUtils,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt
index 6889b8a..52cdbed 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt
@@ -20,14 +20,19 @@
import com.android.systemui.brightness.domain.interactor.screenBrightnessInteractor
import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
-val Kosmos.brightnessSliderViewModel: BrightnessSliderViewModel by
+val Kosmos.brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory by
Kosmos.Fixture {
- BrightnessSliderViewModel(
- screenBrightnessInteractor = screenBrightnessInteractor,
- brightnessPolicyEnforcementInteractor = brightnessPolicyEnforcementInteractor,
- applicationScope = applicationCoroutineScope,
- hapticsViewModelFactory = sliderHapticsViewModelFactory,
- )
+ object : BrightnessSliderViewModel.Factory {
+ override fun create(allowsMirroring: Boolean): BrightnessSliderViewModel {
+ return BrightnessSliderViewModel(
+ screenBrightnessInteractor = screenBrightnessInteractor,
+ brightnessPolicyEnforcementInteractor = brightnessPolicyEnforcementInteractor,
+ hapticsViewModelFactory = sliderHapticsViewModelFactory,
+ brightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor,
+ supportsMirroring = allowsMirroring,
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
index 878e385..2a7e3e9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
@@ -20,6 +20,7 @@
import com.android.keyguard.logging.biometricUnlockLogger
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.dump.dumpManager
import com.android.systemui.keyevent.domain.interactor.keyEventInteractor
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.kosmos.Kosmos
@@ -27,17 +28,19 @@
import com.android.systemui.util.time.systemClock
import kotlinx.coroutines.ExperimentalCoroutinesApi
+@OptIn(ExperimentalCoroutinesApi::class)
val Kosmos.deviceEntryHapticsInteractor by
Kosmos.Fixture {
DeviceEntryHapticsInteractor(
- deviceEntrySourceInteractor = deviceEntrySourceInteractor,
- deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
- deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor,
- fingerprintPropertyRepository = fingerprintPropertyRepository,
biometricSettingsRepository = biometricSettingsRepository,
+ deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor,
+ deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+ deviceEntrySourceInteractor = deviceEntrySourceInteractor,
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
keyEventInteractor = keyEventInteractor,
+ logger = biometricUnlockLogger,
powerInteractor = powerInteractor,
systemClock = systemClock,
- logger = biometricUnlockLogger,
+ dumpManager = dumpManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt
index 0b9ec92..f91a044 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt
@@ -16,14 +16,34 @@
package com.android.systemui.deviceentry.domain.interactor
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.biometrics.authController
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.keyguard.domain.interactor.keyguardBypassInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.statusbar.phone.dozeScrimController
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ExperimentalCoroutinesApi
val Kosmos.deviceEntrySourceInteractor by
Kosmos.Fixture {
DeviceEntrySourceInteractor(
+ authenticationInteractor = authenticationInteractor,
+ authController = authController,
+ alternateBouncerInteractor = alternateBouncerInteractor,
+ deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
+ deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+ dozeScrimController = dozeScrimController,
+ keyguardBypassInteractor = keyguardBypassInteractor,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
keyguardInteractor = keyguardInteractor,
+ sceneContainerOcclusionInteractor = sceneContainerOcclusionInteractor,
+ sceneInteractor = sceneInteractor,
+ dumpManager = dumpManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
index 9282f27..92dc897 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
@@ -31,6 +31,7 @@
windowType = windowType,
context = mock(),
windowManager = mock(),
+ layoutInflater = mock(),
)
.also { properties.put(displayId, windowType, it) }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/FakeIHomeControlsRemoteProxyBinder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/FakeIHomeControlsRemoteProxyBinder.kt
new file mode 100644
index 0000000..f469d74
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/FakeIHomeControlsRemoteProxyBinder.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dreams.homecontrols.service
+
+import android.content.ComponentName
+import com.android.systemui.dreams.homecontrols.shared.IHomeControlsRemoteProxy
+import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener
+
+/** Fake binder implementation of [IHomeControlsRemoteProxy] */
+class FakeIHomeControlsRemoteProxyBinder : IHomeControlsRemoteProxy.Stub() {
+ val callbacks = mutableSetOf<IOnControlsSettingsChangeListener>()
+
+ override fun registerListenerForCurrentUser(callback: IOnControlsSettingsChangeListener) {
+ callbacks.add(callback)
+ }
+
+ override fun unregisterListenerForCurrentUser(callback: IOnControlsSettingsChangeListener) {
+ callbacks.remove(callback)
+ }
+
+ fun notifyCallbacks(component: ComponentName, allowTrivialControlsOnLockscreen: Boolean) {
+ for (callback in callbacks.toSet()) {
+ callback.onControlsSettingsChanged(component, allowTrivialControlsOnLockscreen)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxyKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxyKosmos.kt
new file mode 100644
index 0000000..58ebbf4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxyKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dreams.homecontrols.service
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.homeControlsRemoteProxy by
+ Kosmos.Fixture {
+ HomeControlsRemoteProxy(
+ bgScope = applicationCoroutineScope,
+ proxy = fakeHomeControlsRemoteBinder,
+ dumpManager = dumpManager,
+ )
+ }
+
+val Kosmos.fakeHomeControlsRemoteBinder by
+ Kosmos.Fixture<FakeIHomeControlsRemoteProxyBinder> { FakeIHomeControlsRemoteProxyBinder() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegatorKosmos.kt
new file mode 100644
index 0000000..c85c888
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegatorKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dreams.homecontrols.service
+
+import com.android.systemui.dreams.homecontrols.dagger.HomeControlsRemoteServiceComponent
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
+import org.mockito.kotlin.mock
+
+val Kosmos.remoteHomeControlsDataSourceDelegator by
+ Kosmos.Fixture {
+ RemoteHomeControlsDataSourceDelegator(
+ bgScope = applicationCoroutineScope,
+ serviceFactory = homeControlsRemoteServiceFactory,
+ logBuffer = logcatLogBuffer("HomeControlsDreamInteractor"),
+ dumpManager = dumpManager,
+ )
+ }
+
+var Kosmos.homeControlsRemoteServiceFactory by
+ Kosmos.Fixture<HomeControlsRemoteServiceComponent.Factory> { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/shared/model/FakeHomeControlsDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/shared/model/FakeHomeControlsDataSource.kt
new file mode 100644
index 0000000..f8ea022
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/shared/model/FakeHomeControlsDataSource.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dreams.homecontrols.shared.model
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+class FakeHomeControlsDataSource : HomeControlsDataSource {
+
+ private val _componentInfo = MutableStateFlow<HomeControlsComponentInfo?>(null)
+
+ override val componentInfo: Flow<HomeControlsComponentInfo>
+ get() = _componentInfo.filterNotNull()
+
+ fun setComponentInfo(info: HomeControlsComponentInfo) {
+ _componentInfo.value = info
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsDataSourceKosmos.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsDataSourceKosmos.kt
index 06592b1..942216b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsDataSourceKosmos.kt
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.qs.ui.viewmodel
+package com.android.systemui.dreams.homecontrols.shared.model
import com.android.systemui.kosmos.Kosmos
-val Kosmos.quickSettingsShadeUserActionsViewModel: QuickSettingsShadeUserActionsViewModel by
- Kosmos.Fixture {
- QuickSettingsShadeUserActionsViewModel(
- quickSettingsContainerViewModel = quickSettingsContainerViewModel,
- )
- }
+val Kosmos.homeControlsDataSource by
+ Kosmos.Fixture<HomeControlsDataSource> { fakeHomeControlsDataSource }
+
+val Kosmos.fakeHomeControlsDataSource by
+ Kosmos.Fixture<FakeHomeControlsDataSource> { FakeHomeControlsDataSource() }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorKosmos.kt
similarity index 75%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorKosmos.kt
index 7d7841f..a1182a1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorKosmos.kt
@@ -13,21 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.dreams.homecontrols
+package com.android.systemui.dreams.homecontrols.system.domain.interactor
-import android.os.powerManager
-import android.service.dream.dreamManager
-import com.android.systemui.common.domain.interactor.packageChangeInteractor
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
-import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.time.fakeSystemClock
val Kosmos.homeControlsComponentInteractor by
Kosmos.Fixture {
@@ -37,10 +32,6 @@
authorizedPanelsRepository = authorizedPanelsRepository,
userRepository = fakeUserRepository,
bgScope = applicationCoroutineScope,
- systemClock = fakeSystemClock,
- powerManager = powerManager,
- dreamManager = dreamManager,
- packageChangeInteractor = packageChangeInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactoryKosmos.kt
similarity index 68%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactoryKosmos.kt
index 06592b1..fd882a8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactoryKosmos.kt
@@ -14,13 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.qs.ui.viewmodel
+package com.android.systemui.keyguard.data.quickaffordance
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.userTracker
-val Kosmos.quickSettingsShadeUserActionsViewModel: QuickSettingsShadeUserActionsViewModel by
- Kosmos.Fixture {
- QuickSettingsShadeUserActionsViewModel(
- quickSettingsContainerViewModel = quickSettingsContainerViewModel,
- )
- }
+val Kosmos.keyguardQuickAffordanceProviderClientFactory by
+ Kosmos.Fixture { FakeKeyguardQuickAffordanceProviderClientFactory(userTracker) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/LocalUserSelectionManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/LocalUserSelectionManagerKosmos.kt
new file mode 100644
index 0000000..21d1a76
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/LocalUserSelectionManagerKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.data.quickaffordance
+
+import android.content.applicationContext
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.userFileManager
+import com.android.systemui.settings.userTracker
+
+val Kosmos.localUserSelectionManager by
+ Kosmos.Fixture {
+ KeyguardQuickAffordanceLocalUserSelectionManager(
+ context = applicationContext,
+ userFileManager = userFileManager,
+ userTracker = userTracker,
+ broadcastDispatcher = broadcastDispatcher,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/RemoteUserSelectionManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/RemoteUserSelectionManagerKosmos.kt
new file mode 100644
index 0000000..ec63071
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/RemoteUserSelectionManagerKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.data.quickaffordance
+
+import android.os.UserHandle
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.userTracker
+
+val Kosmos.remoteUserSelectionManager by
+ Kosmos.Fixture {
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = testScope.backgroundScope,
+ userTracker = userTracker,
+ clientFactory = keyguardQuickAffordanceProviderClientFactory,
+ userHandle = UserHandle.SYSTEM,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryKosmos.kt
index 9bbb34c..84eea62 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryKosmos.kt
@@ -17,6 +17,8 @@
package com.android.systemui.keyguard.data.repository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.statusbar.policy.devicePostureController
val Kosmos.devicePostureRepository: DevicePostureRepository by
- Kosmos.Fixture { FakeDevicePostureRepository() }
+ Kosmos.Fixture { DevicePostureRepositoryImpl(devicePostureController, testDispatcher) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepositoryKosmos.kt
new file mode 100644
index 0000000..c91823c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepositoryKosmos.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.data.repository
+
+import android.content.testableContext
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
+import com.android.systemui.tuner.tunerService
+import com.android.systemui.util.mockito.withArgCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+val Kosmos.keyguardBypassRepository: KeyguardBypassRepository by Fixture {
+ KeyguardBypassRepository(
+ testableContext.resources,
+ biometricSettingsRepository,
+ devicePostureRepository,
+ dumpManager,
+ tunerService,
+ testDispatcher,
+ )
+}
+
+fun Kosmos.configureKeyguardBypass(
+ isBypassAvailable: Boolean? = null,
+ faceAuthEnrolledAndEnabled: Boolean = true,
+ faceUnlockBypassOverrideConfig: Int = 0, /* FACE_UNLOCK_BYPASS_NO_OVERRIDE */
+ faceAuthPostureConfig: Int = DEVICE_POSTURE_UNKNOWN,
+) {
+ when (isBypassAvailable) {
+ null -> {
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(faceAuthEnrolledAndEnabled)
+ testableContext.orCreateTestableResources.addOverride(
+ R.integer.config_face_unlock_bypass_override,
+ faceUnlockBypassOverrideConfig,
+ )
+ testableContext.orCreateTestableResources.addOverride(
+ R.integer.config_face_auth_supported_posture,
+ faceAuthPostureConfig,
+ )
+ }
+ true -> {
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ testableContext.orCreateTestableResources.addOverride(
+ R.integer.config_face_unlock_bypass_override,
+ 1, /* FACE_UNLOCK_BYPASS_ALWAYS */
+ )
+ testableContext.orCreateTestableResources.addOverride(
+ R.integer.config_face_auth_supported_posture,
+ DEVICE_POSTURE_UNKNOWN,
+ )
+ }
+ false -> {
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ testableContext.orCreateTestableResources.addOverride(
+ R.integer.config_face_unlock_bypass_override,
+ 2, /* FACE_UNLOCK_BYPASS_NEVER */
+ )
+ testableContext.orCreateTestableResources.addOverride(
+ R.integer.config_face_auth_supported_posture,
+ DEVICE_POSTURE_CLOSED,
+ )
+ }
+ }
+}
+
+fun DevicePostureController.verifyCallback() =
+ withArgCaptor<DevicePostureController.Callback> {
+ verify(this@verifyCallback).addCallback(capture())
+ }
+
+fun DevicePostureController.verifyNoCallback() =
+ verify(this@verifyNoCallback, never()).addCallback(any())
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryKosmos.kt
new file mode 100644
index 0000000..a6d4ec5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.data.repository
+
+import android.content.applicationContext
+import android.os.UserHandle
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.keyguard.data.quickaffordance.localUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.remoteUserSelectionManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.userTracker
+import org.mockito.kotlin.mock
+
+val Kosmos.keyguardQuickAffordanceRepository by Fixture {
+ KeyguardQuickAffordanceRepository(
+ appContext = applicationContext,
+ scope = testScope.backgroundScope,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
+ legacySettingSyncer = mock(),
+ configs = setOf(),
+ dumpManager = dumpManager,
+ userHandle = UserHandle.SYSTEM,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/PulseExpansionRepositoryKosmos.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/PulseExpansionRepositoryKosmos.kt
index 06592b1..1851774 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/PulseExpansionRepositoryKosmos.kt
@@ -14,13 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.qs.ui.viewmodel
+package com.android.systemui.keyguard.data.repository
+import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
-val Kosmos.quickSettingsShadeUserActionsViewModel: QuickSettingsShadeUserActionsViewModel by
- Kosmos.Fixture {
- QuickSettingsShadeUserActionsViewModel(
- quickSettingsContainerViewModel = quickSettingsContainerViewModel,
- )
- }
+val Kosmos.pulseExpansionRepository: PulseExpansionRepository by
+ Kosmos.Fixture { PulseExpansionRepository(dumpManager) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBypassInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBypassInteractorKosmos.kt
new file mode 100644
index 0000000..59ef9df
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBypassInteractorKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.domain.interactor
+
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.keyguard.data.repository.keyguardBypassRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+
+val Kosmos.keyguardBypassInteractor by Fixture {
+ KeyguardBypassInteractor(
+ keyguardBypassRepository,
+ alternateBouncerInteractor,
+ keyguardQuickAffordanceInteractor,
+ pulseExpansionInteractor,
+ sceneInteractor,
+ shadeInteractor,
+ dumpManager,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
new file mode 100644
index 0000000..009d17e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.domain.interactor
+
+import android.app.admin.devicePolicyManager
+import android.content.applicationContext
+import com.android.internal.widget.lockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
+import com.android.systemui.animation.dialogTransitionAnimator
+import com.android.systemui.dock.dockManager
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.keyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.settings.userTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.policy.keyguardStateController
+import org.mockito.kotlin.mock
+
+var Kosmos.keyguardQuickAffordanceInteractor by Fixture {
+ KeyguardQuickAffordanceInteractor(
+ keyguardInteractor = keyguardInteractor,
+ shadeInteractor = shadeInteractor,
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
+ featureFlags = featureFlagsClassic,
+ repository = { keyguardQuickAffordanceRepository },
+ launchAnimator = dialogTransitionAnimator,
+ logger = mock<KeyguardQuickAffordancesLogger>(),
+ metricsLogger = mock<KeyguardQuickAffordancesMetricsLogger>(),
+ devicePolicyManager = devicePolicyManager,
+ dockManager = dockManager,
+ biometricSettingsRepository = biometricSettingsRepository,
+ backgroundDispatcher = testDispatcher,
+ appContext = applicationContext,
+ sceneInteractor = { sceneInteractor },
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/PulseExpansionInteractorKosmos.kt
similarity index 66%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/PulseExpansionInteractorKosmos.kt
index 06592b1..f02e0a4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/PulseExpansionInteractorKosmos.kt
@@ -14,13 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.qs.ui.viewmodel
+package com.android.systemui.keyguard.domain.interactor
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.keyguard.data.repository.pulseExpansionRepository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.quickSettingsShadeUserActionsViewModel: QuickSettingsShadeUserActionsViewModel by
- Kosmos.Fixture {
- QuickSettingsShadeUserActionsViewModel(
- quickSettingsContainerViewModel = quickSettingsContainerViewModel,
- )
- }
+val Kosmos.pulseExpansionInteractor: PulseExpansionInteractor by
+ Kosmos.Fixture { PulseExpansionInteractor(pulseExpansionRepository, dumpManager) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 3c87106..3ab686d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -41,6 +42,7 @@
communalInteractor = communalInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
notificationsKeyguardInteractor = notificationsKeyguardInteractor,
+ pulseExpansionInteractor = pulseExpansionInteractor,
aodNotificationIconViewModel = notificationIconContainerAlwaysOnDisplayViewModel,
notificationShadeWindowModel = notificationShadeWindowModel,
alternateBouncerToAodTransitionViewModel = alternateBouncerToAodTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index a9a80b5..ddae581 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -6,6 +6,8 @@
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
var Kosmos.testDispatcher by Fixture { StandardTestDispatcher() }
@@ -34,3 +36,12 @@
testScope.backgroundScope.coroutineContext
}
var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext }
+
+/**
+ * Run this test body with a [Kosmos] as receiver, and using the [testScope] currently installed in
+ * that kosmos instance
+ */
+fun Kosmos.runTest(testBody: suspend Kosmos.() -> Unit) =
+ testScope.runTest { this@runTest.testBody() }
+
+fun Kosmos.runCurrent() = testScope.runCurrent()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 522c387..5eaa198 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -50,6 +50,7 @@
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor
import com.android.systemui.model.sceneContainerPlugin
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.power.data.repository.fakePowerRepository
@@ -124,6 +125,7 @@
val sceneBackInteractor by lazy { kosmos.sceneBackInteractor }
val falsingCollector by lazy { kosmos.falsingCollector }
val powerInteractor by lazy { kosmos.powerInteractor }
+ val pulseExpansionInteractor by lazy { kosmos.pulseExpansionInteractor }
val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
val deviceEntryUdfpsInteractor by lazy { kosmos.deviceEntryUdfpsInteractor }
val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index dff5625..462b408 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -26,7 +26,7 @@
import com.android.systemui.qs.footerActionsViewModelFactory
import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
import com.android.systemui.qs.panels.ui.viewmodel.paginatedGridViewModel
-import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModel
+import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModelFactory
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.shade.transition.largeScreenShadeInterpolator
import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
@@ -41,7 +41,7 @@
lifecycleScope: LifecycleCoroutineScope
): QSFragmentComposeViewModel {
return QSFragmentComposeViewModel(
- quickSettingsContainerViewModel,
+ quickSettingsContainerViewModelFactory,
mainResources,
footerActionsViewModelFactory,
footerActionsController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
index 779634d..6ded751 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
@@ -16,18 +16,25 @@
package com.android.systemui.qs.ui.viewmodel
-import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModel
+import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModelFactory
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
-val Kosmos.quickSettingsContainerViewModel by
+val Kosmos.quickSettingsContainerViewModelFactory by
Kosmos.Fixture {
- QuickSettingsContainerViewModel(
- brightnessSliderViewModel,
- tileGridViewModel,
- editModeViewModel,
- quickQuickSettingsViewModel,
- )
+ object : QuickSettingsContainerViewModel.Factory {
+ override fun create(
+ supportsBrightnessMirroring: Boolean
+ ): QuickSettingsContainerViewModel {
+ return QuickSettingsContainerViewModel(
+ brightnessSliderViewModelFactory,
+ supportsBrightnessMirroring,
+ tileGridViewModel,
+ editModeViewModel,
+ quickQuickSettingsViewModel,
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt
index 6ced8c3..a1dbeaa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt
@@ -18,6 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.util.mockito.mock
@@ -25,5 +26,5 @@
val Kosmos.quickSettingsShadeOverlayActionsViewModel:
QuickSettingsShadeOverlayActionsViewModel by Fixture {
- QuickSettingsShadeOverlayActionsViewModel(quickSettingsContainerViewModel)
+ QuickSettingsShadeOverlayActionsViewModel(editModeViewModel)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
index 6540ed6..0462848 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
@@ -27,6 +27,6 @@
shadeInteractor = shadeInteractor,
sceneInteractor = sceneInteractor,
shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
- quickSettingsContainerViewModel = quickSettingsContainerViewModel,
+ quickSettingsContainerViewModelFactory = quickSettingsContainerViewModelFactory,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.kt
deleted file mode 100644
index cd1704c..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.ui.viewmodel
-
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.quickSettingsShadeSceneContentViewModel: QuickSettingsShadeSceneContentViewModel by
- Kosmos.Fixture {
- QuickSettingsShadeSceneContentViewModel(
- quickSettingsContainerViewModel = quickSettingsContainerViewModel,
- )
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/CameraLauncherKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shade/CameraLauncherKosmos.kt
index 06592b1..d6dd867 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/CameraLauncherKosmos.kt
@@ -14,13 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.qs.ui.viewmodel
+package com.android.systemui.shade
+import com.android.systemui.camera.cameraGestureHelper
+import com.android.systemui.keyguard.domain.interactor.keyguardQuickAffordanceInteractor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.keyguardBypassController
-val Kosmos.quickSettingsShadeUserActionsViewModel: QuickSettingsShadeUserActionsViewModel by
+val Kosmos.cameraLauncher by
Kosmos.Fixture {
- QuickSettingsShadeUserActionsViewModel(
- quickSettingsContainerViewModel = quickSettingsContainerViewModel,
- )
+ CameraLauncher(cameraGestureHelper, keyguardBypassController) {
+ keyguardQuickAffordanceInteractor
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt
index 61a38b8..9c2a2be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt
@@ -22,7 +22,5 @@
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
val Kosmos.notificationsKeyguardInteractor by Fixture {
- NotificationsKeyguardInteractor(
- repository = notificationsKeyguardViewStateRepository,
- )
+ NotificationsKeyguardInteractor(repository = notificationsKeyguardViewStateRepository)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/tuner/TunerServiceKosmos.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/tuner/TunerServiceKosmos.kt
index 06592b1..9bc3ae9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/tuner/TunerServiceKosmos.kt
@@ -13,14 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package com.android.systemui.qs.ui.viewmodel
+package com.android.systemui.tuner
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import org.mockito.kotlin.mock
-val Kosmos.quickSettingsShadeUserActionsViewModel: QuickSettingsShadeUserActionsViewModel by
- Kosmos.Fixture {
- QuickSettingsShadeUserActionsViewModel(
- quickSettingsContainerViewModel = quickSettingsContainerViewModel,
- )
- }
+val Kosmos.tunerService by Fixture { mock<TunerService>() }
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index 7fa0ef1..a1243e3 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -35,18 +35,18 @@
},
{
"name": "CarLibHostUnitTest",
- "host": true,
- "keywords": ["automotive_code_coverage"]
+ "keywords": ["automotive_code_coverage"],
+ "host": true
},
{
"name": "CarServiceHostUnitTest",
- "host": true,
- "keywords": ["automotive_code_coverage"]
+ "keywords": ["automotive_code_coverage"],
+ "host": true
},
{
"name": "CarSystemUIRavenTests",
- "host": true,
- "keywords": ["automotive_code_coverage"]
+ "keywords": ["automotive_code_coverage"],
+ "host": true
},
{
"name": "CtsAccountManagerTestCasesRavenwood",
@@ -65,6 +65,10 @@
"host": true
},
{
+ "name": "CtsDeviceConfigTestCasesRavenwood",
+ "host": true
+ },
+ {
"name": "CtsGraphicsTestCasesRavenwood",
"host": true
},
diff --git a/ravenwood/scripts/update-test-mapping.sh b/ravenwood/scripts/update-test-mapping.sh
index e478b50..ab37baf 100755
--- a/ravenwood/scripts/update-test-mapping.sh
+++ b/ravenwood/scripts/update-test-mapping.sh
@@ -23,6 +23,14 @@
# Tests that shouldn't be in presubmit.
EXEMPT='^(SystemUiRavenTests)$'
+is_car() {
+ local module="$1"
+
+ # If the module name starts with "Car", then it's a test for "Car".
+ [[ "$module" =~ ^Car ]]
+ return $?
+}
+
main() {
local script_name="${0##*/}"
local script_dir="${0%/*}"
@@ -62,6 +70,10 @@
fi
echo " {"
echo " \"name\": \"${tests[$i]}\","
+ if is_car "${tests[$i]}"; then
+ echo ' "keywords": ["automotive_code_coverage"],'
+ fi
+
echo " \"host\": true"
echo " }$comma"
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index c6fe497..974cba2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -52,6 +52,7 @@
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.ALL;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
@@ -3897,6 +3898,7 @@
userState.getShortcutTargetsLocked(HARDWARE);
final Set<String> qsShortcutTargets =
userState.getShortcutTargetsLocked(QUICK_SETTINGS);
+ final Set<String> shortcutTargets = userState.getShortcutTargetsLocked(ALL);
userState.mEnabledServices.forEach(componentName -> {
if (packageName != null && componentName != null
&& !packageName.equals(componentName.getPackageName())) {
@@ -3917,7 +3919,11 @@
if (TextUtils.isEmpty(serviceName)) {
return;
}
- if (doesShortcutTargetsStringContain(buttonTargets, serviceName)
+ if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
+ if (doesShortcutTargetsStringContain(shortcutTargets, serviceName)) {
+ return;
+ }
+ } else if (doesShortcutTargetsStringContain(buttonTargets, serviceName)
|| doesShortcutTargetsStringContain(shortcutKeyTargets, serviceName)
|| doesShortcutTargetsStringContain(qsShortcutTargets, serviceName)) {
return;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0bf7ec00..67b4063 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -106,21 +106,17 @@
final Set<ComponentName> mTouchExplorationGrantedServices = new HashSet<>();
- private final ArraySet<String> mAccessibilityShortcutKeyTargets = new ArraySet<>();
-
- private final ArraySet<String> mAccessibilityButtonTargets = new ArraySet<>();
- private final ArraySet<String> mAccessibilityGestureTargets = new ArraySet<>();
- private final ArraySet<String> mAccessibilityQsTargets = new ArraySet<>();
+ private final HashMap<Integer, ArraySet<String>> mShortcutTargets = new HashMap<>();
/**
- * The QuickSettings tiles in the QS Panel. This can be different from
- * {@link #mAccessibilityQsTargets} in that {@link #mA11yTilesInQsPanel} stores the
+ * The QuickSettings tiles in the QS Panel. This can be different from the QS targets in
+ * {@link #mShortcutTargets} in that {@link #mA11yTilesInQsPanel} stores the
* TileService's or the a11y framework tile component names (e.g.
* {@link AccessibilityShortcutController#COLOR_INVERSION_TILE_COMPONENT_NAME}) instead of the
* A11y Feature's component names.
* <p/>
* In addition, {@link #mA11yTilesInQsPanel} stores what's on the QS Panel, whereas
- * {@link #mAccessibilityQsTargets} stores the targets that configured qs as their shortcut and
+ * {@link #mShortcutTargets} stores the targets that configured qs as their shortcut and
* also grant full device control permission.
*/
private final ArraySet<ComponentName> mA11yTilesInQsPanel = new ArraySet<>();
@@ -208,6 +204,11 @@
mSupportWindowMagnification = mContext.getResources().getBoolean(
R.bool.config_magnification_area) && mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WINDOW_MAGNIFICATION);
+
+ mShortcutTargets.put(HARDWARE, new ArraySet<>());
+ mShortcutTargets.put(SOFTWARE, new ArraySet<>());
+ mShortcutTargets.put(GESTURE, new ArraySet<>());
+ mShortcutTargets.put(QUICK_SETTINGS, new ArraySet<>());
}
boolean isHandlingAccessibilityEventsLocked() {
@@ -233,10 +234,7 @@
// Clear state persisted in settings.
mEnabledServices.clear();
mTouchExplorationGrantedServices.clear();
- mAccessibilityShortcutKeyTargets.clear();
- mAccessibilityButtonTargets.clear();
- mAccessibilityGestureTargets.clear();
- mAccessibilityQsTargets.clear();
+ mShortcutTargets.forEach((type, targets) -> targets.clear());
mA11yTilesInQsPanel.clear();
mTargetAssignedToAccessibilityButton = null;
mIsTouchExplorationEnabled = false;
@@ -541,7 +539,7 @@
private void dumpShortcutTargets(
PrintWriter pw, @UserShortcutType int shortcutType, String name) {
pw.append(" ").append(name).append(":{");
- ArraySet<String> targets = getShortcutTargetsInternalLocked(shortcutType);
+ ArraySet<String> targets = getShortcutTargetsLocked(shortcutType);
int size = targets.size();
for (int i = 0; i < size; i++) {
if (i > 0) {
@@ -712,7 +710,7 @@
*/
public boolean isShortcutMagnificationEnabledLocked() {
for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
- if (getShortcutTargetsInternalLocked(shortcutType)
+ if (getShortcutTargetsLocked(shortcutType)
.contains(MAGNIFICATION_CONTROLLER_NAME)) {
return true;
}
@@ -788,43 +786,29 @@
}
/**
- * Disable both shortcuts' magnification function.
- */
- public void disableShortcutMagnificationLocked() {
- mAccessibilityShortcutKeyTargets.remove(MAGNIFICATION_CONTROLLER_NAME);
- mAccessibilityButtonTargets.remove(MAGNIFICATION_CONTROLLER_NAME);
- }
-
- /**
* Returns a set which contains the flattened component names and the system class names
- * assigned to the given shortcut. The set is a defensive copy. To apply any changes to the set,
- * use {@link #updateShortcutTargetsLocked(Set, int)}
+ * assigned to the given shortcut. <strong>The set is a defensive copy.</strong>
+ * To apply any changes to the set, use {@link #updateShortcutTargetsLocked(Set, int)}
*
- * @param shortcutType The shortcut type.
+ * @param shortcutTypes The shortcut type or types (in bitmask format).
* @return The array set of the strings
*/
- public ArraySet<String> getShortcutTargetsLocked(@UserShortcutType int shortcutType) {
- return new ArraySet<>(getShortcutTargetsInternalLocked(shortcutType));
- }
-
- private ArraySet<String> getShortcutTargetsInternalLocked(@UserShortcutType int shortcutType) {
- if (shortcutType == HARDWARE) {
- return mAccessibilityShortcutKeyTargets;
- } else if (shortcutType == SOFTWARE) {
- return mAccessibilityButtonTargets;
- } else if (shortcutType == GESTURE) {
- return mAccessibilityGestureTargets;
- } else if (shortcutType == QUICK_SETTINGS) {
- return mAccessibilityQsTargets;
- } else if ((shortcutType == TRIPLETAP
- && isMagnificationSingleFingerTripleTapEnabledLocked()) || (
- shortcutType == TWOFINGER_DOUBLETAP
- && isMagnificationTwoFingerTripleTapEnabledLocked())) {
- ArraySet<String> targets = new ArraySet<>();
- targets.add(MAGNIFICATION_CONTROLLER_NAME);
- return targets;
+ public ArraySet<String> getShortcutTargetsLocked(int shortcutTypes) {
+ ArraySet<String> targets = new ArraySet<>();
+ for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
+ if ((shortcutTypes & shortcutType) != shortcutType) {
+ continue;
+ }
+ if ((shortcutType == TRIPLETAP
+ && isMagnificationSingleFingerTripleTapEnabledLocked()) || (
+ shortcutType == TWOFINGER_DOUBLETAP
+ && isMagnificationTwoFingerTripleTapEnabledLocked())) {
+ targets.add(MAGNIFICATION_CONTROLLER_NAME);
+ } else if (mShortcutTargets.containsKey(shortcutType)) {
+ targets.addAll(mShortcutTargets.get(shortcutType));
+ }
}
- return new ArraySet<>();
+ return targets;
}
/**
@@ -843,8 +827,10 @@
if ((shortcutType & mask) != 0) {
throw new IllegalArgumentException("Tap shortcuts cannot be updated with target sets.");
}
-
- final Set<String> currentTargets = getShortcutTargetsInternalLocked(shortcutType);
+ if (!mShortcutTargets.containsKey(shortcutType)) {
+ mShortcutTargets.put(shortcutType, new ArraySet<>());
+ }
+ ArraySet<String> currentTargets = mShortcutTargets.get(shortcutType);
if (newTargets.equals(currentTargets)) {
return false;
}
@@ -904,7 +890,7 @@
}
// getting internal set lets us directly modify targets, as it's not a copy.
- Set<String> targets = getShortcutTargetsInternalLocked(shortcutType);
+ Set<String> targets = mShortcutTargets.get(shortcutType);
return targets.removeIf(name -> {
ComponentName componentName;
if (name == null
@@ -1169,13 +1155,6 @@
);
}
- /**
- * Returns a copy of the targets which has qs shortcut turned on
- */
- public ArraySet<String> getA11yQsTargets() {
- return new ArraySet<>(mAccessibilityQsTargets);
- }
-
public void updateA11yTilesInQsPanelLocked(Set<ComponentName> componentNames) {
mA11yTilesInQsPanel.clear();
mA11yTilesInQsPanel.addAll(componentNames);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index c9f8929..b52c6505 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -92,6 +92,7 @@
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.infra.AbstractPerUserSystemService;
import com.android.server.inputmethod.InputMethodManagerInternal;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.PrintWriter;
@@ -192,6 +193,8 @@
private final ContentCaptureManagerInternal mContentCaptureManagerInternal;
+ private final UserManagerInternal mUserManagerInternal;
+
private final DisabledInfoCache mDisabledInfoCache;
AutofillManagerServiceImpl(AutofillManagerService master, Object lock,
@@ -208,6 +211,7 @@
mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
mContentCaptureManagerInternal = LocalServices.getService(
ContentCaptureManagerInternal.class);
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mDisabledInfoCache = disableCache;
updateLocked(disabled);
}
@@ -379,6 +383,13 @@
return 0;
}
+ // TODO(b/376482880): remove this check once autofill service supports visible
+ // background users.
+ if (mUserManagerInternal.isVisibleBackgroundFullUser(mUserId)) {
+ Slog.d(TAG, "Currently, autofill service does not support visible background users.");
+ return 0;
+ }
+
if (!forAugmentedAutofillOnly && isAutofillDisabledLocked(clientActivity)) {
// Standard autofill is enabled, but service disabled autofill for this activity; that
// means no session, unless the activity is allowlisted for augmented autofill
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 51c768b..06e6c8b 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -48,6 +48,7 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.net.vcn.Flags;
import android.net.vcn.IVcnManagementService;
import android.net.vcn.IVcnStatusCallback;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
@@ -524,6 +525,9 @@
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
switch (action) {
case Intent.ACTION_PACKAGE_ADDED: // Fallthrough
@@ -878,6 +882,7 @@
private void garbageCollectAndWriteVcnConfigsLocked() {
final SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class);
+ final Set<ParcelUuid> subGroups = mLastSnapshot.getAllSubscriptionGroups();
boolean shouldWrite = false;
@@ -885,11 +890,20 @@
while (configsIterator.hasNext()) {
final ParcelUuid subGrp = configsIterator.next();
- final List<SubscriptionInfo> subscriptions = subMgr.getSubscriptionsInGroup(subGrp);
- if (subscriptions == null || subscriptions.isEmpty()) {
- // Trim subGrps with no more subscriptions; must have moved to another subGrp
- configsIterator.remove();
- shouldWrite = true;
+ if (Flags.fixConfigGarbageCollection()) {
+ if (!subGroups.contains(subGrp)) {
+ // Trim subGrps with no more subscriptions; must have moved to another subGrp
+ logDbg("Garbage collect VcnConfig for group=" + subGrp);
+ configsIterator.remove();
+ shouldWrite = true;
+ }
+ } else {
+ final List<SubscriptionInfo> subscriptions = subMgr.getSubscriptionsInGroup(subGrp);
+ if (subscriptions == null || subscriptions.isEmpty()) {
+ // Trim subGrps with no more subscriptions; must have moved to another subGrp
+ configsIterator.remove();
+ shouldWrite = true;
+ }
}
}
@@ -1094,13 +1108,7 @@
synchronized (mLock) {
final Vcn vcn = mVcns.get(subGrp);
final VcnConfig vcnConfig = mConfigs.get(subGrp);
- if (vcn != null) {
- if (vcnConfig == null) {
- // TODO: b/284381334 Investigate for the root cause of this issue
- // and handle it properly
- logWtf("Vcn instance exists but VcnConfig does not for " + subGrp);
- }
-
+ if (vcn != null && vcnConfig != null) {
if (vcn.getStatus() == VCN_STATUS_CODE_ACTIVE) {
isVcnManagedNetwork = true;
}
@@ -1120,6 +1128,8 @@
}
}
}
+ } else if (vcn != null && vcnConfig == null) {
+ logWtf("Vcn instance exists but VcnConfig does not for " + subGrp);
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index f016590..3c7fb52 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -19,7 +19,6 @@
import android.annotation.Nullable;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
-import android.compat.annotation.Overridable;
import android.content.IntentFilter;
import android.os.UserHandle;
import android.util.PrintWriterPrinter;
@@ -40,7 +39,6 @@
*/
@ChangeId
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE)
- @Overridable
@VisibleForTesting
static final long CHANGE_RESTRICT_PRIORITY_VALUES = 371309185L;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 31ae966..c31b9ef 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2383,7 +2383,7 @@
// If running in background is disabled or mStopUserOnSwitch mode, stop the user.
if (hasRestriction || isStopUserOnSwitchEnabled()) {
Slogf.i(TAG, "Stopping user %d and its profiles on user switch", oldUserId);
- stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null);
+ stopUsersLU(oldUserId, /* allowDelayedLocking= */ !hasRestriction, null, null);
return;
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index b4cce7d..702ad95 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -70,8 +70,10 @@
import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
-import static android.permission.flags.Flags.deviceAwareAppOpNewSchemaEnabled;
+import static android.os.Flags.binderFrozenStateChangeCallback;
import static android.permission.flags.Flags.checkOpValidatePackage;
+import static android.permission.flags.Flags.deviceAwareAppOpNewSchemaEnabled;
+import static android.permission.flags.Flags.useFrozenAwareRemoteCallbackList;
import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED;
import static com.android.internal.util.FrameworkStatsLog.APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION;
@@ -182,6 +184,7 @@
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.policy.AppOpsPolicy;
+import com.android.server.selinux.RateLimiter;
import dalvik.annotation.optimization.NeverCompile;
@@ -201,6 +204,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
+import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
@@ -351,6 +355,10 @@
@GuardedBy("this")
private boolean mUidStatesInitialized;
+ // A rate limiter to prevent excessive Atom pushing. Used by noteOperation.
+ private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10);
+ private final RateLimiter mRateLimiter = new RateLimiter(RATE_LIMITER_WINDOW);
+
volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
/*
@@ -3135,10 +3143,12 @@
boolean shouldCollectMessage) {
if (Binder.getCallingPid() != Process.myPid()
&& Flags.appopAccessTrackingLoggingEnabled()) {
- FrameworkStatsLog.write(
- APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
- APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_OPERATION,
- attributionTag != null);
+ if (mRateLimiter.tryAcquire()) {
+ FrameworkStatsLog.write(
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__NOTE_OPERATION,
+ attributionTag != null);
+ }
}
return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
attributionTag, Context.DEVICE_ID_DEFAULT, shouldCollectAsyncNotedOp, message,
@@ -3543,20 +3553,23 @@
synchronized (this) {
RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+ if (callbacks == null && binderFrozenStateChangeCallback()
+ && useFrozenAwareRemoteCallbackList()) {
+ callbacks = new RemoteCallbackList.Builder<IAppOpsAsyncNotedCallback>(
+ RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP)
+ .setInterfaceDiedCallback((rcl, cb, cookie) ->
+ stopWatchingAsyncNoted(packageName, callback)
+ ).build();
+ }
if (callbacks == null) {
callbacks = new RemoteCallbackList<IAppOpsAsyncNotedCallback>() {
- @Override
- public void onCallbackDied(IAppOpsAsyncNotedCallback callback) {
- synchronized (AppOpsService.this) {
- if (getRegisteredCallbackCount() == 0) {
- mAsyncOpWatchers.remove(key);
- }
+ @Override
+ public void onCallbackDied(IAppOpsAsyncNotedCallback cb) {
+ stopWatchingAsyncNoted(packageName, callback);
}
- }
- };
- mAsyncOpWatchers.put(key, callbacks);
+ };
}
-
+ mAsyncOpWatchers.put(key, callbacks);
callbacks.register(callback);
}
}
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index f462539..5ebe6a1 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -16,6 +16,9 @@
package com.android.server.audio;
+import static com.android.server.utils.EventLogger.Event.ALOGE;
+import static com.android.server.utils.EventLogger.Event.ALOGW;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.UserProperties;
@@ -24,6 +27,7 @@
import android.media.AudioManager;
import android.media.IAudioFocusDispatcher;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
@@ -31,6 +35,7 @@
import com.android.server.LocalServices;
import com.android.server.audio.MediaFocusControl.AudioFocusDeathHandler;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.util.List;
@@ -84,6 +89,8 @@
*/
private final @NonNull AudioAttributes mAttributes;
+ private final EventLogger mEventLogger;
+
/**
* Class constructor
* @param aa
@@ -100,7 +107,7 @@
FocusRequester(@NonNull AudioAttributes aa, int focusRequest, int grantFlags,
IAudioFocusDispatcher afl, IBinder source, @NonNull String id,
AudioFocusDeathHandler hdlr, @NonNull String pn, int uid,
- @NonNull MediaFocusControl ctlr, int sdk) {
+ @NonNull MediaFocusControl ctlr, int sdk, EventLogger eventLogger) {
mAttributes = aa;
mFocusDispatcher = afl;
mSourceRef = source;
@@ -115,10 +122,12 @@
mFocusLossFadeLimbo = false;
mFocusController = ctlr;
mSdkTarget = sdk;
+ mEventLogger = eventLogger;
}
FocusRequester(AudioFocusInfo afi, IAudioFocusDispatcher afl,
- IBinder source, AudioFocusDeathHandler hdlr, @NonNull MediaFocusControl ctlr) {
+ IBinder source, AudioFocusDeathHandler hdlr, @NonNull MediaFocusControl ctlr,
+ EventLogger eventLogger) {
mAttributes = afi.getAttributes();
mClientId = afi.getClientId();
mPackageName = afi.getPackageName();
@@ -134,6 +143,7 @@
mSourceRef = source;
mDeathHandler = hdlr;
mFocusController = ctlr;
+ mEventLogger = eventLogger;
}
boolean hasSameClient(String otherClient) {
@@ -357,18 +367,22 @@
mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(),
AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
final IAudioFocusDispatcher fd = mFocusDispatcher;
- if (fd != null) {
+ if (fd != null && mFocusLossWasNotified) {
if (DEBUG) {
Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
+ mClientId);
}
- if (mFocusLossWasNotified) {
- fd.dispatchAudioFocusChange(focusGain, mClientId);
- }
+ fd.dispatchAudioFocusChange(focusGain, mClientId);
+ mEventLogger.enqueue(new FocusRequestEvent(
+ this, focusGain, "handleGain"));
+ } else if (mFocusLossWasNotified) {
+ mEventLogger.enqueue(new FocusRequestEvent(
+ this, focusGain, "handleGain no listener").printSlog(ALOGW, TAG));
}
mFocusController.restoreVShapedPlayers(this);
- } catch (android.os.RemoteException e) {
- Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
+ } catch (RemoteException e) {
+ mEventLogger.enqueue(new FocusRequestEvent(
+ this, focusGain, "handleGain exc: " + e).printSlog(ALOGE, TAG));
}
}
@@ -385,62 +399,67 @@
if (DEBUG) {
Log.i(TAG, "handleFocusLoss for " + mClientId + " loss:" + focusLoss);
}
- try {
- if (focusLoss != mFocusLossReceived) {
- mFocusLossReceived = focusLoss;
- mFocusLossWasNotified = false;
- // before dispatching a focus loss, check if the following conditions are met:
- // 1/ the framework is not supposed to notify the focus loser on a DUCK loss
- // (i.e. it has a focus controller that implements a ducking policy)
- // 2/ it is a DUCK loss
- // 3/ the focus loser isn't flagged as pausing in a DUCK loss
- // if they are, do not notify the focus loser
- if (!mFocusController.mustNotifyFocusOwnerOnDuck()
- && mFocusLossReceived == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
- && (mGrantFlags
- & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) == 0) {
- if (DEBUG) {
- Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
- + " to " + mClientId + ", to be handled externally");
- }
- mFocusController.notifyExtPolicyFocusLoss_syncAf(
- toAudioFocusInfo(), false /* wasDispatched */);
- return;
+ if (focusLoss != mFocusLossReceived) {
+ mFocusLossReceived = focusLoss;
+ mFocusLossWasNotified = false;
+ // before dispatching a focus loss, check if the following conditions are met:
+ // 1/ the framework is not supposed to notify the focus loser on a DUCK loss
+ // (i.e. it has a focus controller that implements a ducking policy)
+ // 2/ it is a DUCK loss
+ // 3/ the focus loser isn't flagged as pausing in a DUCK loss
+ // if they are, do not notify the focus loser
+ if (!mFocusController.mustNotifyFocusOwnerOnDuck()
+ && mFocusLossReceived == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
+ && (mGrantFlags
+ & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) == 0) {
+ if (DEBUG) {
+ Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
+ + " to " + mClientId + ", to be handled externally");
}
-
- // check enforcement by the framework
- boolean handled = false;
- if (frWinner != null) {
- handled = frameworkHandleFocusLoss(focusLoss, frWinner, forceDuck);
- }
-
- if (handled) {
- if (DEBUG) {
- Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
- + " to " + mClientId + ", response handled by framework");
- }
- mFocusController.notifyExtPolicyFocusLoss_syncAf(
- toAudioFocusInfo(), false /* wasDispatched */);
- return; // with mFocusLossWasNotified = false
- }
-
- final IAudioFocusDispatcher fd = mFocusDispatcher;
- if (fd != null) {
- if (DEBUG) {
- Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "
- + mClientId);
- }
- mFocusController.notifyExtPolicyFocusLoss_syncAf(
- toAudioFocusInfo(), true /* wasDispatched */);
- mFocusLossWasNotified = true;
- fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
- } else if (DEBUG) {
- Log.i(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
- + " to " + mClientId + " no IAudioFocusDispatcher");
- }
+ mFocusController.notifyExtPolicyFocusLoss_syncAf(
+ toAudioFocusInfo(), false /* wasDispatched */);
+ return;
}
- } catch (android.os.RemoteException e) {
- Log.e(TAG, "Failure to signal loss of audio focus due to:", e);
+
+ // check enforcement by the framework
+ boolean handled = false;
+ if (frWinner != null) {
+ handled = frameworkHandleFocusLoss(focusLoss, frWinner, forceDuck);
+ }
+
+ if (handled) {
+ if (DEBUG) {
+ Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
+ + " to " + mClientId + ", response handled by framework");
+ }
+ mFocusController.notifyExtPolicyFocusLoss_syncAf(
+ toAudioFocusInfo(), false /* wasDispatched */);
+ return; // with mFocusLossWasNotified = false
+ }
+
+ final IAudioFocusDispatcher fd = mFocusDispatcher;
+ if (fd != null) {
+ if (DEBUG) {
+ Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "
+ + mClientId);
+ }
+ mFocusController.notifyExtPolicyFocusLoss_syncAf(
+ toAudioFocusInfo(), true /* wasDispatched */);
+ mFocusLossWasNotified = true;
+ try {
+ fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
+ mEventLogger.enqueue(new FocusRequestEvent(
+ this, mFocusLossReceived, "handleLoss"));
+ } catch (RemoteException e) {
+ mEventLogger.enqueue(new FocusRequestEvent(
+ this, mFocusLossReceived, "handleLoss failed exc: " + e)
+ .printSlog(ALOGE,TAG));
+ }
+ } else {
+ mEventLogger.enqueue(new FocusRequestEvent(
+ this, mFocusLossReceived, "handleLoss failed no listener")
+ .printSlog(ALOGE, TAG));
+ }
}
}
@@ -505,7 +524,7 @@
return false;
}
- int dispatchFocusChange(int focusChange) {
+ int dispatchFocusChange(int focusChange, String reason) {
final IAudioFocusDispatcher fd = mFocusDispatcher;
if (fd == null) {
if (MediaFocusControl.DEBUG) { Log.e(TAG, "dispatchFocusChange: no focus dispatcher"); }
@@ -528,8 +547,11 @@
}
try {
fd.dispatchAudioFocusChange(focusChange, mClientId);
- } catch (android.os.RemoteException e) {
- Log.e(TAG, "dispatchFocusChange: error talking to focus listener " + mClientId, e);
+ mEventLogger.enqueue(new FocusRequestEvent(this,
+ focusChange, "dispatch: " + reason));
+ } catch (RemoteException e) {
+ mEventLogger.enqueue(new FocusRequestEvent(
+ this, focusChange, "dispatch failed: " + e).printSlog(ALOGE, TAG));
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
@@ -559,7 +581,7 @@
}
}
}
- return dispatchFocusChange(focusChange);
+ return dispatchFocusChange(focusChange, "focus with fade");
}
void dispatchFocusResultFromExtPolicy(int requestResult) {
@@ -575,7 +597,7 @@
}
try {
fd.dispatchFocusResultFromExtPolicy(requestResult, mClientId);
- } catch (android.os.RemoteException e) {
+ } catch (RemoteException e) {
Log.e(TAG, "dispatchFocusResultFromExtPolicy: error talking to focus listener"
+ mClientId, e);
}
@@ -585,4 +607,32 @@
return new AudioFocusInfo(mAttributes, mCallingUid, mClientId, mPackageName,
mFocusGainRequest, mFocusLossReceived, mGrantFlags, mSdkTarget);
}
+
+ static class FocusRequestEvent extends EventLogger.Event {
+ private final String mClientId;
+ private final int mUid;
+ private final String mPackageName;
+ private final int mCode;
+ private final String mDescription;
+
+ public FocusRequestEvent(FocusRequester fr, String description) {
+ this(fr, -1, description);
+ }
+
+ public FocusRequestEvent(FocusRequester fr, int code, String description) {
+ mClientId = fr.getClientId();
+ mUid = fr.getClientUid();
+ mPackageName = fr.getPackageName();
+ mCode = code;
+ mDescription = description != null ? description : "";
+ }
+ @Override
+ public String eventToString() {
+ return "focus owner: " + mClientId + " in uid: " + mUid
+ + " pack: " + mPackageName
+ + ((mCode != -1) ? " code: " + mCode : "")
+ + " event: " + mDescription;
+ }
+ }
+
}
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index b4af46e..1604e94 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -231,13 +231,8 @@
final FocusRequester focusOwner = stackIterator.next();
if (focusOwner.hasSameUid(uid) && focusOwner.hasSamePackage(packageName)) {
clientsToRemove.add(focusOwner.getClientId());
- mEventLogger.enqueue((new EventLogger.StringEvent(
- "focus owner:" + focusOwner.getClientId()
- + " in uid:" + uid + " pack: " + packageName
- + " getting AUDIOFOCUS_LOSS due to app suspension"))
- .printLog(TAG));
// make the suspended app lose focus through its focus listener (if any)
- focusOwner.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS);
+ focusOwner.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS, "app suspension");
}
}
for (String clientToRemove : clientsToRemove) {
@@ -548,11 +543,7 @@
FocusRequester fr = stackIterator.next();
if(fr.hasSameBinder(cb)) {
Log.i(TAG, "AudioFocus removeFocusStackEntryOnDeath(): removing entry for " + cb);
- mEventLogger.enqueue(new EventLogger.StringEvent(
- "focus requester:" + fr.getClientId()
- + " in uid:" + fr.getClientUid()
- + " pack:" + fr.getPackageName()
- + " died"));
+ mEventLogger.enqueue(new FocusRequester.FocusRequestEvent(fr, " died"));
notifyExtPolicyFocusLoss_syncAf(fr.toAudioFocusInfo(), false);
stackIterator.remove();
@@ -585,11 +576,7 @@
final FocusRequester fr = owner.getValue();
if (fr.hasSameBinder(cb)) {
ownerIterator.remove();
- mEventLogger.enqueue(new EventLogger.StringEvent(
- "focus requester:" + fr.getClientId()
- + " in uid:" + fr.getClientUid()
- + " pack:" + fr.getPackageName()
- + " died"));
+ mEventLogger.enqueue(new FocusRequester.FocusRequestEvent(fr, "died"));
fr.release();
notifyExtFocusPolicyFocusAbandon_syncAf(fr.toAudioFocusInfo());
break;
@@ -900,7 +887,7 @@
}
// new focus (future) focus owner to keep track of
mFocusOwnersForFocusPolicy.put(afi.getClientId(),
- new FocusRequester(afi, fd, cb, hdlr, this));
+ new FocusRequester(afi, fd, cb, hdlr, this, mEventLogger));
}
try {
@@ -972,7 +959,7 @@
}
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
- return fr.dispatchFocusChange(focusChange);
+ return fr.dispatchFocusChange(focusChange, "audiomanager");
}
}
@@ -1006,6 +993,7 @@
otherActiveFrs.add(otherFr);
}
+ // TODO log
int status = fr.dispatchFocusChangeWithFadeLocked(focusChange, otherActiveFrs);
if (status != AudioManager.AUDIOFOCUS_REQUEST_DELAYED
&& focusChange == AudioManager.AUDIOFOCUS_LOSS) {
@@ -1080,6 +1068,7 @@
switch (attr.getUsage()) {
case AudioAttributes.USAGE_MEDIA:
case AudioAttributes.USAGE_GAME:
+ case AudioAttributes.USAGE_SPEAKER_CLEANUP:
return 1000;
case AudioAttributes.USAGE_ALARM:
case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
@@ -1260,7 +1249,7 @@
removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
- clientId, afdh, callingPackageName, uid, this, sdk);
+ clientId, afdh, callingPackageName, uid, this, sdk, mEventLogger);
if (mMultiAudioFocusEnabled
&& (focusChangeHint == AudioManager.AUDIOFOCUS_GAIN)) {
@@ -1594,7 +1583,7 @@
synchronized (mAudioFocusLock) {
final FocusRequester loser = (FocusRequester) msg.obj;
if (loser.isInFocusLossLimbo()) {
- loser.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS);
+ loser.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS, "loss after fade");
loser.release();
postForgetUidLater(loser);
}
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 3afecf1..290aab2 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -305,7 +305,7 @@
mSensors /* sensorIds */,
true /* credentialAllowed */,
false /* requireConfirmation */,
- mUserId,
+ mPreAuthInfo.callingUserId,
mOperationId,
mOpPackageName,
mRequestId);
@@ -357,7 +357,7 @@
mSensors,
mPreAuthInfo.shouldShowCredential(),
requireConfirmation,
- mUserId,
+ mPreAuthInfo.callingUserId,
mOperationId,
mOpPackageName,
mRequestId);
@@ -491,7 +491,7 @@
mSensors /* sensorIds */,
true /* credentialAllowed */,
false /* requireConfirmation */,
- mUserId,
+ mPreAuthInfo.callingUserId,
mOperationId,
mOpPackageName,
mRequestId);
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 4c91789..00280c8f 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1133,7 +1133,7 @@
return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
- getContext(), mBiometricCameraManager);
+ getContext(), mBiometricCameraManager, mUserManager);
}
/**
@@ -1520,9 +1520,9 @@
mHandler.post(() -> {
try {
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
- mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
- opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
- getContext(), mBiometricCameraManager);
+ mDevicePolicyManager, mSettingObserver, mSensors, userId,
+ promptInfo, opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
+ getContext(), mBiometricCameraManager, mUserManager);
// Set the default title if necessary.
if (promptInfo.isUseDefaultTitle()) {
@@ -1572,8 +1572,8 @@
promptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL);
}
- authenticateInternal(token, requestId, operationId, userId, receiver,
- opPackageName, promptInfo, preAuthInfo);
+ authenticateInternal(token, requestId, operationId, preAuthInfo.userId,
+ receiver, opPackageName, promptInfo, preAuthInfo);
} else {
receiver.onError(preAuthStatus.first /* modality */,
preAuthStatus.second /* errorCode */,
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index 96c178a..a9ada29 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -32,6 +32,7 @@
import android.hardware.biometrics.Flags;
import android.hardware.biometrics.PromptInfo;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Pair;
import android.util.Slog;
@@ -72,6 +73,7 @@
final boolean confirmationRequested;
final boolean ignoreEnrollmentState;
final int userId;
+ final int callingUserId;
final Context context;
private final boolean mBiometricRequested;
private final int mBiometricStrengthRequested;
@@ -82,7 +84,7 @@
private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
boolean credentialRequested, List<BiometricSensor> eligibleSensors,
List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
- PromptInfo promptInfo, int userId, Context context,
+ PromptInfo promptInfo, int userId, int callingUserId, Context context,
BiometricCameraManager biometricCameraManager,
boolean isOnlyMandatoryBiometricsRequested,
boolean isMandatoryBiometricsAuthentication) {
@@ -97,6 +99,7 @@
this.confirmationRequested = promptInfo.isConfirmationRequested();
this.ignoreEnrollmentState = promptInfo.isIgnoreEnrollmentState();
this.userId = userId;
+ this.callingUserId = callingUserId;
this.context = context;
this.mOnlyMandatoryBiometricsRequested = isOnlyMandatoryBiometricsRequested;
this.mIsMandatoryBiometricsAuthentication = isMandatoryBiometricsAuthentication;
@@ -108,7 +111,8 @@
List<BiometricSensor> sensors,
int userId, PromptInfo promptInfo, String opPackageName,
boolean checkDevicePolicyManager, Context context,
- BiometricCameraManager biometricCameraManager)
+ BiometricCameraManager biometricCameraManager,
+ UserManager userManager)
throws RemoteException {
final boolean isOnlyMandatoryBiometricsRequested = promptInfo.getAuthenticators()
@@ -141,14 +145,20 @@
final List<BiometricSensor> eligibleSensors = new ArrayList<>();
final List<Pair<BiometricSensor, Integer>> ineligibleSensors = new ArrayList<>();
+ final int effectiveUserId;
+ if (Flags.privateSpaceBp()) {
+ effectiveUserId = userManager.getCredentialOwnerProfile(userId);
+ } else {
+ effectiveUserId = userId;
+ }
+
if (biometricRequested) {
for (BiometricSensor sensor : sensors) {
@AuthenticatorStatus int status = getStatusForBiometricAuthenticator(
- devicePolicyManager, settingObserver, sensor, userId, opPackageName,
- checkDevicePolicyManager, requestedStrength,
- promptInfo.getAllowedSensorIds(),
- promptInfo.isIgnoreEnrollmentState(),
+ devicePolicyManager, settingObserver, sensor, effectiveUserId,
+ opPackageName, checkDevicePolicyManager, requestedStrength,
+ promptInfo.getAllowedSensorIds(), promptInfo.isIgnoreEnrollmentState(),
biometricCameraManager);
Slog.d(TAG, "Package: " + opPackageName
@@ -172,9 +182,9 @@
}
return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
- eligibleSensors, ineligibleSensors, credentialAvailable, promptInfo, userId,
- context, biometricCameraManager, isOnlyMandatoryBiometricsRequested,
- isMandatoryBiometricsAuthentication);
+ eligibleSensors, ineligibleSensors, credentialAvailable, promptInfo,
+ effectiveUserId, userId, context, biometricCameraManager,
+ isOnlyMandatoryBiometricsRequested, isMandatoryBiometricsAuthentication);
}
private static boolean dropCredentialFallback(int authenticators,
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 a118415..21c5140 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -162,7 +162,7 @@
STATE_WAITING_IN_QUEUE,
STATE_WAITING_FOR_COOKIE,
STATE_WAITING_IN_QUEUE_CANCELING)) {
- return false;
+ return hasOperationAlreadyStarted();
}
if (mClientMonitor.getCookie() != 0) {
@@ -191,7 +191,7 @@
STATE_WAITING_IN_QUEUE,
STATE_WAITING_FOR_COOKIE,
STATE_WAITING_IN_QUEUE_CANCELING)) {
- return false;
+ return hasOperationAlreadyStarted();
}
return doStart(callback);
@@ -230,6 +230,10 @@
return true;
}
+ private boolean hasOperationAlreadyStarted() {
+ return mState == STATE_STARTED;
+ }
+
/**
* Abort a pending operation.
*
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
index a3c68f9..afffa66 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -21,6 +21,7 @@
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
+import android.hardware.broadcastradio.Alert;
import android.hardware.broadcastradio.AmFmRegionConfig;
import android.hardware.broadcastradio.Announcement;
import android.hardware.broadcastradio.ConfigFlag;
@@ -36,6 +37,7 @@
import android.hardware.radio.Flags;
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioAlert;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import android.hardware.radio.RadioTuner;
@@ -573,6 +575,86 @@
return builder.build();
}
+ @Nullable private static RadioAlert.Polygon polygonFromHalPolygon(
+ android.hardware.broadcastradio.Polygon halPolygon) {
+ if (halPolygon.coordinates.length < 4) {
+ Slogf.e(TAG, "Number of coordinates in alert polygon cannot be less than 4");
+ return null;
+ } else if (halPolygon.coordinates[0].latitude
+ != halPolygon.coordinates[halPolygon.coordinates.length - 1].latitude
+ || halPolygon.coordinates[0].longitude
+ != halPolygon.coordinates[halPolygon.coordinates.length - 1].longitude) {
+ Slogf.e(TAG, "The first and the last coordinate in alert polygon cannot be different");
+ return null;
+ }
+ List<RadioAlert.Coordinate> coordinates = new ArrayList<>(halPolygon.coordinates.length);
+ for (int idx = 0; idx < halPolygon.coordinates.length; idx++) {
+ coordinates.add(new RadioAlert.Coordinate(halPolygon.coordinates[idx].latitude,
+ halPolygon.coordinates[idx].longitude));
+ }
+ return new RadioAlert.Polygon(coordinates);
+ }
+
+ private static RadioAlert.Geocode geocodeFromHalGeocode(
+ android.hardware.broadcastradio.Geocode geocode) {
+ return new RadioAlert.Geocode(geocode.valueName, geocode.value);
+ }
+
+ private static RadioAlert.AlertArea alertAreaFromHalAlertArea(
+ android.hardware.broadcastradio.AlertArea halAlertArea) {
+ List<RadioAlert.Polygon> polygonList = new ArrayList<>();
+ for (int idx = 0; idx < halAlertArea.polygons.length; idx++) {
+ RadioAlert.Polygon polygon = polygonFromHalPolygon(halAlertArea.polygons[idx]);
+ if (polygon != null) {
+ polygonList.add(polygon);
+ }
+ }
+ List<RadioAlert.Geocode> geocodeList = new ArrayList<>(halAlertArea.geocodes.length);
+ for (int idx = 0; idx < halAlertArea.geocodes.length; idx++) {
+ geocodeList.add(geocodeFromHalGeocode(halAlertArea.geocodes[idx]));
+ }
+ return new RadioAlert.AlertArea(polygonList, geocodeList);
+ }
+
+ private static RadioAlert.AlertInfo alertInfoFromHalAlertInfo(
+ android.hardware.broadcastradio.AlertInfo halAlertInfo) {
+ int[] categoryArray = new int[halAlertInfo.categoryArray.length];
+ for (int idx = 0; idx < halAlertInfo.categoryArray.length; idx++) {
+ // Integer values in android.hardware.radio.RadioAlert.AlertCategory and
+ // android.hardware.broadcastradio.AlertCategory match.
+ categoryArray[idx] = halAlertInfo.categoryArray[idx];
+ }
+ List<RadioAlert.AlertArea> alertAreaList = new ArrayList<>();
+ for (int idx = 0; idx < halAlertInfo.areas.length; idx++) {
+ alertAreaList.add(alertAreaFromHalAlertArea(halAlertInfo.areas[idx]));
+ }
+ // Integer values in android.hardware.radio.RadioAlert.AlertUrgency and
+ // android.hardware.broadcastradio.AlertUrgency match.
+ // Integer values in android.hardware.radio.RadioAlert.AlertSeverity and
+ // android.hardware.broadcastradio.AlertSeverity match.
+ // Integer values in android.hardware.radio.RadioAlert.AlertCertainty and
+ // android.hardware.broadcastradio.AlertCertainty match.
+ return new RadioAlert.AlertInfo(categoryArray, halAlertInfo.urgency, halAlertInfo.severity,
+ halAlertInfo.certainty, halAlertInfo.description, alertAreaList,
+ halAlertInfo.language);
+ }
+
+ @VisibleForTesting
+ @Nullable static RadioAlert radioAlertFromHalAlert(Alert halAlert) {
+ if (halAlert == null) {
+ return null;
+ }
+ List<RadioAlert.AlertInfo> alertInfo = new ArrayList<>(halAlert.infoArray.length);
+ for (int idx = 0; idx < halAlert.infoArray.length; idx++) {
+ alertInfo.add(alertInfoFromHalAlertInfo(halAlert.infoArray[idx]));
+ }
+ // Integer values in android.hardware.radio.RadioAlert.AlertStatus and
+ // android.hardware.broadcastradio.AlertStatus match.
+ // Integer values in android.hardware.radio.RadioAlert.AlertMessageType and
+ // android.hardware.broadcastradio.AlertMessageType match.
+ return new RadioAlert(halAlert.status, halAlert.messageType, alertInfo);
+ }
+
private static boolean isValidLogicallyTunedTo(ProgramIdentifier id) {
return id.type == IdentifierType.AMFM_FREQUENCY_KHZ || id.type == IdentifierType.RDS_PI
|| id.type == IdentifierType.HD_STATION_ID_EXT
@@ -605,7 +687,18 @@
}
}
}
-
+ if (!Flags.hdRadioEmergencyAlertSystem()) {
+ return new RadioManager.ProgramInfo(
+ Objects.requireNonNull(programSelectorFromHalProgramSelector(info.selector)),
+ identifierFromHalProgramIdentifier(info.logicallyTunedTo),
+ identifierFromHalProgramIdentifier(info.physicallyTunedTo),
+ relatedContent,
+ info.infoFlags,
+ info.signalQuality,
+ radioMetadataFromHalMetadata(info.metadata),
+ vendorInfoFromHalVendorKeyValues(info.vendorInfo)
+ );
+ }
return new RadioManager.ProgramInfo(
Objects.requireNonNull(programSelectorFromHalProgramSelector(info.selector)),
identifierFromHalProgramIdentifier(info.logicallyTunedTo),
@@ -614,7 +707,8 @@
info.infoFlags,
info.signalQuality,
radioMetadataFromHalMetadata(info.metadata),
- vendorInfoFromHalVendorKeyValues(info.vendorInfo)
+ vendorInfoFromHalVendorKeyValues(info.vendorInfo),
+ radioAlertFromHalAlert(info.emergencyAlert)
);
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 0807c70..4ad7c10 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -26,6 +26,7 @@
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
import android.view.DisplayShape;
+import android.view.FrameRateCategoryRate;
import android.view.RoundedCorners;
import android.view.Surface;
@@ -300,6 +301,11 @@
public boolean hasArrSupport;
/**
+ * Represents frame rate for the FrameRateCategory Normal and High.
+ * @see android.view.Display#getSuggestedFrameRate(int) for more details.
+ */
+ public FrameRateCategoryRate frameRateCategoryRate;
+ /**
* The default mode of the display.
*/
public int defaultModeId;
@@ -548,7 +554,8 @@
|| !Objects.equals(roundedCorners, other.roundedCorners)
|| installOrientation != other.installOrientation
|| !Objects.equals(displayShape, other.displayShape)
- || hasArrSupport != other.hasArrSupport) {
+ || hasArrSupport != other.hasArrSupport
+ || !Objects.equals(frameRateCategoryRate, other.frameRateCategoryRate)) {
diff |= DIFF_OTHER;
}
return diff;
@@ -567,6 +574,7 @@
modeId = other.modeId;
renderFrameRate = other.renderFrameRate;
hasArrSupport = other.hasArrSupport;
+ frameRateCategoryRate = other.frameRateCategoryRate;
defaultModeId = other.defaultModeId;
userPreferredModeId = other.userPreferredModeId;
supportedModes = other.supportedModes;
@@ -612,6 +620,7 @@
sb.append(", modeId ").append(modeId);
sb.append(", renderFrameRate ").append(renderFrameRate);
sb.append(", hasArrSupport ").append(hasArrSupport);
+ sb.append(", frameRateCategoryRate ").append(frameRateCategoryRate);
sb.append(", defaultModeId ").append(defaultModeId);
sb.append(", userPreferredModeId ").append(userPreferredModeId);
sb.append(", supportedModes ").append(Arrays.toString(supportedModes));
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index f9c3a46..a4bb8c3 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -45,6 +45,7 @@
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
import android.view.DisplayShape;
+import android.view.FrameRateCategoryRate;
import android.view.RoundedCorners;
import android.view.SurfaceControl;
@@ -247,6 +248,7 @@
private boolean mDisplayModeSpecsInvalid;
private int mActiveColorMode;
private boolean mHasArrSupport;
+ private FrameRateCategoryRate mFrameRateCategoryRate;
private Display.HdrCapabilities mHdrCapabilities;
private boolean mAllmSupported;
private boolean mGameContentTypeSupported;
@@ -313,6 +315,7 @@
changed |= updateAllmSupport(dynamicInfo.autoLowLatencyModeSupported);
changed |= updateGameContentTypeSupport(dynamicInfo.gameContentTypeSupported);
changed |= updateHasArrSupportLocked(dynamicInfo.hasArrSupport);
+ changed |= updateFrameRateCategoryRatesLocked(dynamicInfo.frameRateCategoryRate);
if (changed) {
mHavePendingChanges = true;
@@ -604,6 +607,15 @@
return true;
}
+ private boolean updateFrameRateCategoryRatesLocked(
+ FrameRateCategoryRate newFrameRateCategoryRate) {
+ if (Objects.equals(mFrameRateCategoryRate, newFrameRateCategoryRate)) {
+ return false;
+ }
+ mFrameRateCategoryRate = newFrameRateCategoryRate;
+ return true;
+ }
+
private boolean updateHasArrSupportLocked(boolean newHasArrSupport) {
if (mHasArrSupport == newHasArrSupport) {
return false;
@@ -695,6 +707,7 @@
}
mInfo.hdrCapabilities = mHdrCapabilities;
mInfo.hasArrSupport = mHasArrSupport;
+ mInfo.frameRateCategoryRate = mFrameRateCategoryRate;
mInfo.appVsyncOffsetNanos = mActiveSfDisplayMode.appVsyncOffsetNanos;
mInfo.presentationDeadlineNanos = mActiveSfDisplayMode.presentationDeadlineNanos;
mInfo.state = mState;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 074a4d8..7cfdcaf 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -507,6 +507,7 @@
mBaseDisplayInfo.modeId = deviceInfo.modeId;
mBaseDisplayInfo.renderFrameRate = deviceInfo.renderFrameRate;
mBaseDisplayInfo.hasArrSupport = deviceInfo.hasArrSupport;
+ mBaseDisplayInfo.frameRateCategoryRate = deviceInfo.frameRateCategoryRate;
mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId;
mBaseDisplayInfo.userPreferredModeId = deviceInfo.userPreferredModeId;
mBaseDisplayInfo.supportedModes = Arrays.copyOf(
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 36cadf5..f49608b 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -430,3 +430,11 @@
bug: "350617205"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_get_suggested_frame_rate"
+ namespace: "core_graphics"
+ description: "Flag for an API to get suggested frame rates"
+ bug: "361433796"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 0766c3a..132d6fa 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -3178,6 +3178,10 @@
HdmiCecLocalDeviceSource source = playback();
if (source == null) {
source = audioSystem();
+ } else {
+ // Cancel an existing timer to send the device to sleep since OTP was triggered.
+ playback().mDelayedStandbyOnActiveSourceLostHandler
+ .removeCallbacksAndMessages(null);
}
if (source == null) {
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
new file mode 100644
index 0000000..ae31b33
--- /dev/null
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.input;
+
+import android.annotation.NonNull;
+import android.hardware.input.InputGestureData;
+import android.hardware.input.InputManager;
+import android.util.IndentingPrintWriter;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for managing pre-defined input
+ * gestures and custom gestures defined by other system components using Input APIs.
+ *
+ * TODO(b/365064144): Add implementation to persist data, identify clashes with existing shortcuts.
+ *
+ */
+final class InputGestureManager {
+ private static final String TAG = "InputGestureManager";
+
+ @GuardedBy("mCustomInputGestures")
+ private final SparseArray<Map<InputGestureData.Trigger, InputGestureData>>
+ mCustomInputGestures = new SparseArray<>();
+
+ @InputManager.CustomInputGestureResult
+ public int addCustomInputGesture(int userId, InputGestureData newGesture) {
+ synchronized (mCustomInputGestures) {
+ if (!mCustomInputGestures.contains(userId)) {
+ mCustomInputGestures.put(userId, new HashMap<>());
+ }
+ Map<InputGestureData.Trigger, InputGestureData> customGestures =
+ mCustomInputGestures.get(userId);
+ if (customGestures.containsKey(newGesture.getTrigger())) {
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS;
+ }
+ customGestures.put(newGesture.getTrigger(), newGesture);
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS;
+ }
+ }
+
+ @InputManager.CustomInputGestureResult
+ public int removeCustomInputGesture(int userId, InputGestureData data) {
+ synchronized (mCustomInputGestures) {
+ if (!mCustomInputGestures.contains(userId)) {
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST;
+ }
+ Map<InputGestureData.Trigger, InputGestureData> customGestures =
+ mCustomInputGestures.get(userId);
+ InputGestureData customGesture = customGestures.get(data.getTrigger());
+ if (!Objects.equals(data, customGesture)) {
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST;
+ }
+ customGestures.remove(data.getTrigger());
+ if (customGestures.size() == 0) {
+ mCustomInputGestures.remove(userId);
+ }
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS;
+ }
+ }
+
+ public void removeAllCustomInputGestures(int userId) {
+ synchronized (mCustomInputGestures) {
+ mCustomInputGestures.remove(userId);
+ }
+ }
+
+ @NonNull
+ public List<InputGestureData> getCustomInputGestures(int userId) {
+ synchronized (mCustomInputGestures) {
+ if (!mCustomInputGestures.contains(userId)) {
+ return List.of();
+ }
+ return new ArrayList<>(mCustomInputGestures.get(userId).values());
+ }
+ }
+
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println("InputGestureManager:");
+ ipw.increaseIndent();
+ synchronized (mCustomInputGestures) {
+ int size = mCustomInputGestures.size();
+ for (int i = 0; i < size; i++) {
+ Map<InputGestureData.Trigger, InputGestureData> customGestures =
+ mCustomInputGestures.valueAt(i);
+ ipw.println("UserId = " + mCustomInputGestures.keyAt(i));
+ ipw.increaseIndent();
+ for (InputGestureData customGesture : customGestures.values()) {
+ ipw.println(customGesture);
+ }
+ ipw.decreaseIndent();
+ }
+ }
+ ipw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index a421d04..d43ce71 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -51,6 +51,7 @@
import android.hardware.SensorPrivacyManagerInternal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayViewport;
+import android.hardware.input.AidlInputGestureData;
import android.hardware.input.HostUsiVersion;
import android.hardware.input.IInputDeviceBatteryListener;
import android.hardware.input.IInputDeviceBatteryState;
@@ -63,6 +64,7 @@
import android.hardware.input.IStickyModifierStateListener;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.InputDeviceIdentifier;
+import android.hardware.input.InputGestureData;
import android.hardware.input.InputManager;
import android.hardware.input.InputSensorInfo;
import android.hardware.input.InputSettings;
@@ -2985,6 +2987,39 @@
mKeyGestureController.unregisterKeyGestureHandler(handler, Binder.getCallingPid());
}
+ @Override
+ @PermissionManuallyEnforced
+ public int addCustomInputGesture(@NonNull AidlInputGestureData inputGestureData) {
+ enforceManageKeyGesturePermission();
+
+ Objects.requireNonNull(inputGestureData);
+ return mKeyGestureController.addCustomInputGesture(UserHandle.getCallingUserId(),
+ inputGestureData);
+ }
+
+ @Override
+ @PermissionManuallyEnforced
+ public int removeCustomInputGesture(@NonNull AidlInputGestureData inputGestureData) {
+ enforceManageKeyGesturePermission();
+
+ Objects.requireNonNull(inputGestureData);
+ return mKeyGestureController.removeCustomInputGesture(UserHandle.getCallingUserId(),
+ inputGestureData);
+ }
+
+ @Override
+ @PermissionManuallyEnforced
+ public void removeAllCustomInputGestures() {
+ enforceManageKeyGesturePermission();
+
+ mKeyGestureController.removeAllCustomInputGestures(UserHandle.getCallingUserId());
+ }
+
+ @Override
+ public AidlInputGestureData[] getCustomInputGestures() {
+ return mKeyGestureController.getCustomInputGestures(UserHandle.getCallingUserId());
+ }
+
private void handleCurrentUserChanged(@UserIdInt int userId) {
mCurrentUserId = userId;
}
diff --git a/services/core/java/com/android/server/input/InputShellCommand.java b/services/core/java/com/android/server/input/InputShellCommand.java
index 4c5a3c2..9833016 100644
--- a/services/core/java/com/android/server/input/InputShellCommand.java
+++ b/services/core/java/com/android/server/input/InputShellCommand.java
@@ -89,6 +89,9 @@
private static final int DEFAULT_FLAGS = 0;
private static final boolean INJECT_ASYNC = true;
private static final boolean INJECT_SYNC = false;
+ private static final long SECOND_IN_MILLISECONDS = 1000;
+
+ public static final int SWIPE_EVENT_HZ_DEFAULT = 120;
/** Modifier key to meta state */
private static final Map<Integer, Integer> MODIFIER;
@@ -519,11 +522,30 @@
}
long now = SystemClock.uptimeMillis();
final long endTime = down + duration;
+ final float swipeEventPeriodMillis =
+ (float) SECOND_IN_MILLISECONDS / SWIPE_EVENT_HZ_DEFAULT;
+ int injected = 1;
while (now < endTime) {
- final long elapsedTime = now - down;
+ // Ensure that we inject at most at the frequency of SWIPE_EVENT_HZ_DEFAULT
+ // by waiting an additional delta between the actual time and expected time.
+ long elapsedTime = now - down;
+ final long errorMillis =
+ (long) Math.floor(injected * swipeEventPeriodMillis - elapsedTime);
+ if (errorMillis > 0) {
+ // Make sure not to exceed the duration and inject an extra event.
+ if (errorMillis > endTime - now) {
+ sleep(endTime - now);
+ break;
+ }
+ sleep(errorMillis);
+ }
+
+ now = SystemClock.uptimeMillis();
+ elapsedTime = now - down;
final float alpha = (float) elapsedTime / duration;
injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, down, now,
lerp(x1, x2, alpha), lerp(y1, y2, alpha), 1.0f, displayId);
+ injected++;
now = SystemClock.uptimeMillis();
}
injectMotionEvent(inputSource, MotionEvent.ACTION_UP, down, now, x2, y2, 0.0f,
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index 7ee8116..68eaf72 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -28,16 +28,20 @@
import android.annotation.BinderThread;
import android.annotation.MainThread;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.hardware.input.AidlInputGestureData;
import android.hardware.input.AidlKeyGestureEvent;
import android.hardware.input.IKeyGestureEventListener;
import android.hardware.input.IKeyGestureHandler;
+import android.hardware.input.InputGestureData;
import android.hardware.input.InputManager;
import android.hardware.input.InputSettings;
import android.hardware.input.KeyGestureEvent;
@@ -67,6 +71,7 @@
import java.util.ArrayDeque;
import java.util.HashSet;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
@@ -109,6 +114,7 @@
private final int mSystemPid;
private final KeyCombinationManager mKeyCombinationManager;
private final SettingsObserver mSettingsObserver;
+ private final InputGestureManager mInputGestureManager = new InputGestureManager();
// Pending actions
private boolean mPendingMetaAction;
@@ -1134,6 +1140,37 @@
}
}
+ @BinderThread
+ @InputManager.CustomInputGestureResult
+ public int addCustomInputGesture(@UserIdInt int userId,
+ @NonNull AidlInputGestureData inputGestureData) {
+ return mInputGestureManager.addCustomInputGesture(userId,
+ new InputGestureData(inputGestureData));
+ }
+
+ @BinderThread
+ @InputManager.CustomInputGestureResult
+ public int removeCustomInputGesture(@UserIdInt int userId,
+ @NonNull AidlInputGestureData inputGestureData) {
+ return mInputGestureManager.removeCustomInputGesture(userId,
+ new InputGestureData(inputGestureData));
+ }
+
+ @BinderThread
+ public void removeAllCustomInputGestures(@UserIdInt int userId) {
+ mInputGestureManager.removeAllCustomInputGestures(userId);
+ }
+
+ @BinderThread
+ public AidlInputGestureData[] getCustomInputGestures(@UserIdInt int userId) {
+ List<InputGestureData> customGestures = mInputGestureManager.getCustomInputGestures(userId);
+ AidlInputGestureData[] result = new AidlInputGestureData[customGestures.size()];
+ for (int i = 0; i < customGestures.size(); i++) {
+ result[i] = customGestures.get(i).getAidlData();
+ }
+ return result;
+ }
+
private void onKeyGestureEventListenerDied(int pid) {
synchronized (mKeyGestureEventListenerRecords) {
mKeyGestureEventListenerRecords.remove(pid);
@@ -1335,5 +1372,6 @@
}
ipw.decreaseIndent();
mKeyCombinationManager.dump("", ipw);
+ mInputGestureManager.dump(ipw);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 15d76a2..0104373 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1082,6 +1082,19 @@
AdditionalSubtypeMapRepository.remove(userId);
InputMethodSettingsRepository.remove(userId);
mService.mUserDataRepository.remove(userId);
+ synchronized (ImfLock.class) {
+ final int nextOrCurrentUser = mService.mUserSwitchHandlerTask != null
+ ? mService.mUserSwitchHandlerTask.mToUserId : mService.mCurrentImeUserId;
+ if (!mService.mConcurrentMultiUserModeEnabled && userId == nextOrCurrentUser) {
+ // The current user was removed without an ongoing switch, or the user targeted
+ // by the ongoing switch was removed. Switch to the current non-profile user
+ // to allow starting input on it or one of its profile users later.
+ // Note: non-profile users cannot be removed while they are the current user.
+ final int currentUserId = mService.mActivityManagerInternal.getCurrentUserId();
+ mService.scheduleSwitchUserTaskLocked(currentUserId,
+ null /* clientToBeReset */);
+ }
+ }
}
@Override
@@ -1332,7 +1345,7 @@
+ " prevUserId=" + prevUserId);
}
- // Clean up stuff for mCurrentUserId, which soon becomes the previous user.
+ // Clean up stuff for mCurrentImeUserId, which soon becomes the previous user.
// TODO(b/338461930): Check if this is still necessary or not.
onUnbindCurrentMethodByReset(prevUserId);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index bbdac56..8798a64 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1183,9 +1183,7 @@
// If config_disableWeaverOnUnsecuredUsers=true, then the Weaver HAL may be buggy and
// need multiple retries before it works here to unwrap the SP, if the SP was already
- // protected by Weaver. Note that the problematic HAL can also deadlock if called with
- // the ActivityManagerService lock held, but that should not be a problem here since
- // that lock isn't held here, unlike unlockUserKeyIfUnsecured() where it is.
+ // protected by Weaver.
for (int i = 0; i < 12 && sp == null; i++) {
Slog.e(TAG, "Failed to unwrap synthetic password. Waiting 5 seconds to retry.");
SystemClock.sleep(5000);
diff --git a/services/core/java/com/android/server/notification/NotificationBackupHelper.java b/services/core/java/com/android/server/notification/NotificationBackupHelper.java
index ee9ec15..9df44a4 100644
--- a/services/core/java/com/android/server/notification/NotificationBackupHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationBackupHelper.java
@@ -16,6 +16,8 @@
package com.android.server.notification;
+import static android.app.backup.NotificationLoggingConstants.KEY_NOTIFICATIONS;
+
import android.app.INotificationManager;
import android.app.backup.BlobBackupHelper;
import android.os.ServiceManager;
@@ -31,9 +33,6 @@
// Current version of the blob schema
static final int BLOB_VERSION = 1;
- // Key under which the payload blob is stored
- static final String KEY_NOTIFICATIONS = "notifications";
-
private final int mUserId;
private final NotificationManagerInternal mNm;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index abf3da4..62df825 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -161,6 +161,8 @@
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING;
import static com.android.server.notification.Flags.expireBitmaps;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
@@ -1098,7 +1100,7 @@
}
void readPolicyXml(InputStream stream, boolean forRestore, int userId,
- BackupRestoreEventLogger logger)
+ @Nullable BackupRestoreEventLogger logger)
throws XmlPullParserException, NumberFormatException, IOException {
final TypedXmlPullParser parser;
if (forRestore) {
@@ -1114,7 +1116,27 @@
int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (ZenModeConfig.ZEN_TAG.equals(parser.getName())) {
- mZenModeHelper.readXml(parser, forRestore, userId);
+ int successfulReads = 0;
+ int unsuccessfulReads = 0;
+ try {
+ boolean loadedCorrectly =
+ mZenModeHelper.readXml(parser, forRestore, userId, logger);
+ if (loadedCorrectly)
+ successfulReads++;
+ else
+ unsuccessfulReads++;
+ } catch (Exception e) {
+ Slog.wtf(TAG, "failed to read config", e);
+ unsuccessfulReads++;
+ }
+ if (logger != null) {
+ logger.logItemsRestored(DATA_TYPE_ZEN_CONFIG, successfulReads);
+ if (unsuccessfulReads > 0) {
+ logger.logItemsRestoreFailed(
+ DATA_TYPE_ZEN_CONFIG, unsuccessfulReads, ERROR_XML_PARSING);
+ }
+ }
+
} else if (PreferencesHelper.TAG_RANKING.equals(parser.getName())){
mPreferencesHelper.readXml(parser, forRestore, userId);
}
@@ -1246,7 +1268,7 @@
}
}
- private void writePolicyXml(OutputStream stream, boolean forBackup, int userId,
+ void writePolicyXml(OutputStream stream, boolean forBackup, int userId,
BackupRestoreEventLogger logger) throws IOException {
final TypedXmlSerializer out;
if (forBackup) {
@@ -1258,7 +1280,7 @@
out.startDocument(null, true);
out.startTag(null, TAG_NOTIFICATION_POLICY);
out.attributeInt(null, ATTR_VERSION, DB_VERSION);
- mZenModeHelper.writeXml(out, forBackup, null, userId);
+ mZenModeHelper.writeXml(out, forBackup, null, userId, logger);
mPreferencesHelper.writeXml(out, forBackup, userId);
mListeners.writeXml(out, forBackup, userId);
mAssistants.writeXml(out, forBackup, userId);
@@ -5683,14 +5705,16 @@
final int zenMode = zenModeFromInterruptionFilter(interruptionFilter, -1);
if (zenMode == -1) return;
+
+ UserHandle zenUser = getCallingZenUser();
if (!canManageGlobalZenPolicy(info.component.getPackageName(), callingUid)) {
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(
- info.component.getPackageName(), callingUid, zenMode);
+ zenUser, info.component.getPackageName(), callingUid, zenMode);
} else {
int origin = computeZenOrigin(/* fromUser= */ false);
Binder.withCleanCallingIdentity(() -> {
- mZenModeHelper.setManualZenMode(zenMode, /* conditionId= */ null, origin,
- "listener:" + info.component.flattenToShortString(),
+ mZenModeHelper.setManualZenMode(zenUser, zenMode, /* conditionId= */ null,
+ origin, "listener:" + info.component.flattenToShortString(),
/* caller= */ info.component.getPackageName(),
callingUid);
});
@@ -5745,12 +5769,13 @@
public void setZenMode(int mode, Uri conditionId, String reason, boolean fromUser) {
enforceSystemOrSystemUI("INotificationManager.setZenMode");
enforceUserOriginOnlyFromSystem(fromUser, "setZenMode");
+ UserHandle zenUser = getCallingZenUser();
final int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
- mZenModeHelper.setManualZenMode(mode, conditionId, computeZenOrigin(fromUser),
- reason, /* caller= */ null, callingUid);
+ mZenModeHelper.setManualZenMode(zenUser, mode, conditionId,
+ computeZenOrigin(fromUser), reason, /* caller= */ null, callingUid);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -5760,7 +5785,7 @@
@Override
public List<ZenModeConfig.ZenRule> getZenRules() throws RemoteException {
enforcePolicyAccess(Binder.getCallingUid(), "getZenRules");
- return mZenModeHelper.getZenRules();
+ return mZenModeHelper.getZenRules(getCallingZenUser());
}
@Override
@@ -5769,14 +5794,14 @@
throw new IllegalStateException("getAutomaticZenRules called with flag off!");
}
enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRules");
- return mZenModeHelper.getAutomaticZenRules();
+ return mZenModeHelper.getAutomaticZenRules(getCallingZenUser());
}
@Override
public AutomaticZenRule getAutomaticZenRule(String id) throws RemoteException {
Objects.requireNonNull(id, "Id is null");
enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRule");
- return mZenModeHelper.getAutomaticZenRule(id);
+ return mZenModeHelper.getAutomaticZenRule(getCallingZenUser(), id);
}
@Override
@@ -5791,6 +5816,7 @@
}
enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
enforceUserOriginOnlyFromSystem(fromUser, "addAutomaticZenRule");
+ UserHandle zenUser = getCallingZenUser();
// 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.
@@ -5801,16 +5827,18 @@
}
}
- return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
+ return mZenModeHelper.addAutomaticZenRule(zenUser, rulePkg, automaticZenRule,
computeZenOrigin(fromUser), "addAutomaticZenRule", Binder.getCallingUid());
}
@Override
public void setManualZenRuleDeviceEffects(ZenDeviceEffects effects) throws RemoteException {
checkCallerIsSystem();
+ UserHandle zenUser = getCallingZenUser();
- mZenModeHelper.setManualZenRuleDeviceEffects(effects, computeZenOrigin(true),
- "Update manual mode non-policy settings", Binder.getCallingUid());
+ mZenModeHelper.setManualZenRuleDeviceEffects(zenUser, effects,
+ computeZenOrigin(true), "Update manual mode non-policy settings",
+ Binder.getCallingUid());
}
@Override
@@ -5819,8 +5847,9 @@
validateAutomaticZenRule(id, automaticZenRule);
enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule");
enforceUserOriginOnlyFromSystem(fromUser, "updateAutomaticZenRule");
+ UserHandle zenUser = getCallingZenUser();
- return mZenModeHelper.updateAutomaticZenRule(id, automaticZenRule,
+ return mZenModeHelper.updateAutomaticZenRule(zenUser, id, automaticZenRule,
computeZenOrigin(fromUser), "updateAutomaticZenRule", Binder.getCallingUid());
}
@@ -5886,8 +5915,9 @@
// Verify that they can modify zen rules.
enforcePolicyAccess(Binder.getCallingUid(), "removeAutomaticZenRule");
enforceUserOriginOnlyFromSystem(fromUser, "removeAutomaticZenRule");
+ UserHandle zenUser = getCallingZenUser();
- return mZenModeHelper.removeAutomaticZenRule(id, computeZenOrigin(fromUser),
+ return mZenModeHelper.removeAutomaticZenRule(zenUser, id, computeZenOrigin(fromUser),
"removeAutomaticZenRule", Binder.getCallingUid());
}
@@ -5897,9 +5927,11 @@
Objects.requireNonNull(packageName, "Package name is null");
enforceSystemOrSystemUI("removeAutomaticZenRules");
enforceUserOriginOnlyFromSystem(fromUser, "removeAutomaticZenRules");
+ UserHandle zenUser = getCallingZenUser();
- return mZenModeHelper.removeAutomaticZenRules(packageName, computeZenOrigin(fromUser),
- packageName + "|removeAutomaticZenRules", Binder.getCallingUid());
+ return mZenModeHelper.removeAutomaticZenRules(zenUser, packageName,
+ computeZenOrigin(fromUser), packageName + "|removeAutomaticZenRules",
+ Binder.getCallingUid());
}
@Override
@@ -5907,7 +5939,7 @@
Objects.requireNonNull(owner, "Owner is null");
enforceSystemOrSystemUI("getRuleInstanceCount");
- return mZenModeHelper.getCurrentInstanceCount(owner);
+ return mZenModeHelper.getCurrentInstanceCount(getCallingZenUser(), owner);
}
@Override
@@ -5915,7 +5947,7 @@
public int getAutomaticZenRuleState(@NonNull String id) {
Objects.requireNonNull(id, "id is null");
enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRuleState");
- return mZenModeHelper.getAutomaticZenRuleState(id);
+ return mZenModeHelper.getAutomaticZenRuleState(getCallingZenUser(), id);
}
@Override
@@ -5926,9 +5958,30 @@
enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
boolean fromUser = (condition.source == Condition.SOURCE_USER_ACTION);
+ UserHandle zenUser = getCallingZenUser();
- mZenModeHelper.setAutomaticZenRuleState(id, condition, computeZenOrigin(fromUser),
- Binder.getCallingUid());
+ mZenModeHelper.setAutomaticZenRuleState(zenUser, id, condition,
+ computeZenOrigin(fromUser), Binder.getCallingUid());
+ }
+
+ /**
+ * Returns the {@link UserHandle} corresponding to the caller that is performing a
+ * zen-related operation (such as {@link #setInterruptionFilter},
+ * {@link #addAutomaticZenRule}, {@link #setAutomaticZenRuleState}, etc). The user is
+ * {@link UserHandle#USER_CURRENT} if the caller is the system or SystemUI (assuming
+ * that all interactions in SystemUI are for the "current" user); otherwise it's the user
+ * associated to the binder call.
+ */
+ private UserHandle getCallingZenUser() {
+ if (android.app.Flags.modesMultiuser()) {
+ if (isCallerSystemOrSystemUiOrShell()) {
+ return UserHandle.CURRENT;
+ } else {
+ return Binder.getCallingUserHandle();
+ }
+ } else {
+ return UserHandle.CURRENT;
+ }
}
@ZenModeConfig.ConfigOrigin
@@ -5964,15 +6017,16 @@
if (zen == -1) throw new IllegalArgumentException("Invalid filter: " + filter);
final int callingUid = Binder.getCallingUid();
enforceUserOriginOnlyFromSystem(fromUser, "setInterruptionFilter");
+ UserHandle zenUser = getCallingZenUser();
if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) {
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, callingUid, zen);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(zenUser, pkg, callingUid, zen);
return;
}
final long identity = Binder.clearCallingIdentity();
try {
- mZenModeHelper.setManualZenMode(zen, null, computeZenOrigin(fromUser),
+ mZenModeHelper.setManualZenMode(zenUser, zen, null, computeZenOrigin(fromUser),
/* reason= */ "setInterruptionFilter", /* caller= */ pkg,
callingUid);
} finally {
@@ -6268,12 +6322,13 @@
@Override
public Policy getNotificationPolicy(String pkg) {
final int callingUid = Binder.getCallingUid();
+ UserHandle zenUser = getCallingZenUser();
if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) {
- return mZenModeHelper.getNotificationPolicyFromImplicitZenRule(pkg);
+ return mZenModeHelper.getNotificationPolicyFromImplicitZenRule(zenUser, pkg);
}
final long identity = Binder.clearCallingIdentity();
try {
- return mZenModeHelper.getNotificationPolicy();
+ return mZenModeHelper.getNotificationPolicy(zenUser);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -6302,6 +6357,7 @@
enforceUserOriginOnlyFromSystem(fromUser, "setNotificationPolicy");
int callingUid = Binder.getCallingUid();
@ZenModeConfig.ConfigOrigin int origin = computeZenOrigin(fromUser);
+ UserHandle zenUser = getCallingZenUser();
boolean isSystemCaller = isCallerSystemOrSystemUiOrShell();
boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi()
@@ -6311,7 +6367,7 @@
try {
final ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pkg,
0, UserHandle.getUserId(callingUid));
- Policy currPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy currPolicy = mZenModeHelper.getNotificationPolicy(zenUser);
if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.P) {
int priorityCategories = policy.priorityCategories;
@@ -6369,11 +6425,12 @@
}
if (shouldApplyAsImplicitRule) {
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(zenUser, pkg, callingUid,
+ policy);
} else {
ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion,
policy);
- mZenModeHelper.setNotificationPolicy(policy, origin, callingUid);
+ mZenModeHelper.setNotificationPolicy(zenUser, policy, origin, callingUid);
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to set notification policy", e);
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index d26a5aa..9f0b4b0 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -2028,8 +2028,9 @@
* </ul>
*/
void syncChannelsBypassingDnd() {
- mCurrentUserHasChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
- & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
+ mCurrentUserHasChannelsBypassingDnd =
+ (mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).state
+ & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
updateCurrentUserHasChannelsBypassingDnd(/* callingUid= */ Process.SYSTEM_UID,
/* fromSystemOrSystemUi= */ true);
@@ -2072,7 +2073,8 @@
if (mCurrentUserHasChannelsBypassingDnd != haveBypassingApps) {
mCurrentUserHasChannelsBypassingDnd = haveBypassingApps;
if (android.app.Flags.modesUi()) {
- mZenModeHelper.updateHasPriorityChannels(mCurrentUserHasChannelsBypassingDnd);
+ mZenModeHelper.updateHasPriorityChannels(UserHandle.CURRENT,
+ mCurrentUserHasChannelsBypassingDnd);
} else {
updateZenPolicy(mCurrentUserHasChannelsBypassingDnd, callingUid,
fromSystemOrSystemUi);
@@ -2099,8 +2101,10 @@
// PreferencesHelper should otherwise not need to modify actual policy
public void updateZenPolicy(boolean areChannelsBypassingDnd, int callingUid,
boolean fromSystemOrSystemUi) {
- NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
+ NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy(
+ UserHandle.CURRENT);
mZenModeHelper.setNotificationPolicy(
+ UserHandle.CURRENT,
new NotificationManager.Policy(
policy.priorityCategories, policy.priorityCallSenders,
policy.priorityMessageSenders, policy.suppressedVisualEffects,
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index b1f010c..52d0c41 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -20,6 +20,7 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Process;
+import android.os.UserHandle;
import android.service.notification.Condition;
import android.service.notification.IConditionProvider;
import android.service.notification.ZenModeConfig;
@@ -117,7 +118,10 @@
ZenModeConfig config = mHelper.getConfig();
if (config == null) return;
final int callingUid = Binder.getCallingUid();
- mHelper.setAutomaticZenRuleState(id, condition,
+
+ // This change is known to be for UserHandle.CURRENT because ConditionProviders for
+ // background users are not bound.
+ mHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, condition,
callingUid == Process.SYSTEM_UID ? ZenModeConfig.ORIGIN_SYSTEM
: ZenModeConfig.ORIGIN_APP,
callingUid);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 5547bd3..d5f13a8 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -40,9 +40,12 @@
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_ACTIVATE;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE;
import static android.service.notification.ZenModeConfig.implicitRuleId;
+import static android.service.notification.ZenModeConfig.isImplicitRuleId;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.Preconditions.checkArgument;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING;
import static java.util.Objects.requireNonNull;
@@ -56,6 +59,7 @@
import android.app.Flags;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -167,15 +171,13 @@
private final Clock mClock;
private final SettingsObserver mSettingsObserver;
private final AppOpsManager mAppOps;
- private final NotificationManager mNotificationManager;
private final ZenModeConfig mDefaultConfig;
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
private final ZenModeFiltering mFiltering;
private final RingerModeDelegate mRingerModeDelegate = new
RingerModeDelegate();
@VisibleForTesting protected final ZenModeConditions mConditions;
- private final Object mConfigsArrayLock = new Object();
- @GuardedBy("mConfigsArrayLock")
+ @GuardedBy("mConfigLock")
@VisibleForTesting final SparseArray<ZenModeConfig> mConfigs = new SparseArray<>();
private final Metrics mMetrics = new Metrics();
private final ConditionProviders.Config mServiceConfig;
@@ -215,15 +217,14 @@
mClock = clock;
addCallback(mMetrics);
mAppOps = context.getSystemService(AppOpsManager.class);
- mNotificationManager = context.getSystemService(NotificationManager.class);
mDefaultConfig = Flags.modesUi()
? ZenModeConfig.getDefaultConfig()
: readDefaultConfig(mContext.getResources());
updateDefaultConfig(mContext, mDefaultConfig);
- mConfig = mDefaultConfig.copy();
- synchronized (mConfigsArrayLock) {
+ synchronized (mConfigLock) {
+ mConfig = mDefaultConfig.copy();
mConfigs.put(UserHandle.USER_SYSTEM, mConfig);
}
mConsolidatedPolicy = mConfig.toNotificationPolicy();
@@ -237,10 +238,6 @@
mZenModeEventLogger = zenModeEventLogger;
}
- public Looper getLooper() {
- return mHandler.getLooper();
- }
-
@Override
public String toString() {
return TAG;
@@ -331,7 +328,7 @@
public void onUserRemoved(int user) {
if (user < UserHandle.USER_SYSTEM) return;
if (DEBUG) Log.d(TAG, "onUserRemoved u=" + user);
- synchronized (mConfigsArrayLock) {
+ synchronized (mConfigLock) {
mConfigs.remove(user);
}
}
@@ -350,7 +347,7 @@
mUser = user;
if (DEBUG) Log.d(TAG, reason + " u=" + user);
ZenModeConfig config = null;
- synchronized (mConfigsArrayLock) {
+ synchronized (mConfigLock) {
if (mConfigs.get(user) != null) {
config = mConfigs.get(user).copy();
}
@@ -376,7 +373,9 @@
boolean fromSystemOrSystemUi) {
final int newZen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
if (newZen != -1) {
- setManualZenMode(newZen, null,
+ // This change is known to be for UserHandle.CURRENT because NLSes for
+ // background users are unbound.
+ setManualZenMode(UserHandle.CURRENT, newZen, null,
fromSystemOrSystemUi ? ORIGIN_SYSTEM : ORIGIN_APP,
/* reason= */ "listener:" + (name != null ? name.flattenToShortString() : null),
/* caller= */ name != null ? name.getPackageName() : null,
@@ -399,11 +398,12 @@
}
// TODO: b/310620812 - Make private (or inline) when MODES_API is inlined.
- public List<ZenRule> getZenRules() {
+ public List<ZenRule> getZenRules(UserHandle user) {
List<ZenRule> rules = new ArrayList<>();
synchronized (mConfigLock) {
- if (mConfig == null) return rules;
- for (ZenRule rule : mConfig.automaticRules.values()) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return rules;
+ for (ZenRule rule : config.automaticRules.values()) {
if (canManageAutomaticZenRule(rule)) {
rules.add(rule);
}
@@ -417,8 +417,8 @@
* (which means the owned rules for a regular app, and every rule for system callers) together
* with their ids.
*/
- Map<String, AutomaticZenRule> getAutomaticZenRules() {
- List<ZenRule> ruleList = getZenRules();
+ Map<String, AutomaticZenRule> getAutomaticZenRules(UserHandle user) {
+ List<ZenRule> ruleList = getZenRules(user);
HashMap<String, AutomaticZenRule> rules = new HashMap<>(ruleList.size());
for (ZenRule rule : ruleList) {
rules.put(rule.id, zenRuleToAutomaticZenRule(rule));
@@ -426,11 +426,12 @@
return rules;
}
- public AutomaticZenRule getAutomaticZenRule(String id) {
+ public AutomaticZenRule getAutomaticZenRule(UserHandle user, String id) {
ZenRule rule;
synchronized (mConfigLock) {
- if (mConfig == null) return null;
- rule = mConfig.automaticRules.get(id);
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return null;
+ rule = config.automaticRules.get(id);
}
if (rule == null) return null;
if (canManageAutomaticZenRule(rule)) {
@@ -439,8 +440,9 @@
return null;
}
- public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
- @ConfigOrigin int origin, String reason, int callingUid) {
+ public String addAutomaticZenRule(UserHandle user, String pkg,
+ AutomaticZenRule automaticZenRule, @ConfigOrigin int origin, String reason,
+ int callingUid) {
checkManageRuleOrigin("addAutomaticZenRule", origin);
if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
@@ -455,10 +457,10 @@
ruleInstanceLimit = component.metaData.getInt(
ConditionProviderService.META_DATA_RULE_INSTANCE_LIMIT, -1);
}
- int newRuleInstanceCount = getCurrentInstanceCount(automaticZenRule.getOwner())
- + getCurrentInstanceCount(automaticZenRule.getConfigurationActivity())
+ int newRuleInstanceCount = getCurrentInstanceCount(user, automaticZenRule.getOwner())
+ + getCurrentInstanceCount(user, automaticZenRule.getConfigurationActivity())
+ 1;
- int newPackageRuleCount = getPackageRuleCount(pkg) + 1;
+ int newPackageRuleCount = getPackageRuleCount(user, pkg) + 1;
if (newPackageRuleCount > RULE_LIMIT_PER_PACKAGE
|| (ruleInstanceLimit > 0 && ruleInstanceLimit < newRuleInstanceCount)) {
throw new IllegalArgumentException("Rule instance limit exceeded");
@@ -467,15 +469,16 @@
ZenModeConfig newConfig;
synchronized (mConfigLock) {
- if (mConfig == null) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) {
throw new AndroidRuntimeException("Could not create rule");
}
if (DEBUG) {
Log.d(TAG, "addAutomaticZenRule rule= " + automaticZenRule + " reason=" + reason);
}
- newConfig = mConfig.copy();
+ newConfig = config.copy();
ZenRule rule = new ZenRule();
- populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
+ populateZenRule(pkg, automaticZenRule, newConfig, rule, origin, /* isNew= */ true);
rule = maybeRestoreRemovedRule(newConfig, pkg, rule, automaticZenRule, origin);
newConfig.automaticRules.put(rule.id, rule);
maybeReplaceDefaultRule(newConfig, null, automaticZenRule);
@@ -524,7 +527,7 @@
// "Preserve" the previous rule by considering the azrToAdd an update instead.
// Only app-modifiable fields will actually be modified.
- populateZenRule(pkg, azrToAdd, ruleToRestore, origin, /* isNew= */ false);
+ populateZenRule(pkg, azrToAdd, config, ruleToRestore, origin, /* isNew= */ false);
return ruleToRestore;
}
@@ -558,35 +561,37 @@
}
}
- public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
- @ConfigOrigin int origin, String reason, int callingUid) {
+ public boolean updateAutomaticZenRule(UserHandle user, String ruleId,
+ AutomaticZenRule automaticZenRule, @ConfigOrigin int origin, String reason,
+ int callingUid) {
checkManageRuleOrigin("updateAutomaticZenRule", origin);
if (ruleId == null) {
throw new IllegalArgumentException("ruleId cannot be null");
}
synchronized (mConfigLock) {
- if (mConfig == null) return false;
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return false;
if (DEBUG) {
Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
+ " reason=" + reason);
}
- ZenModeConfig.ZenRule oldRule = mConfig.automaticRules.get(ruleId);
+ ZenModeConfig.ZenRule oldRule = config.automaticRules.get(ruleId);
if (oldRule == null || !canManageAutomaticZenRule(oldRule)) {
throw new SecurityException(
"Cannot update rules not owned by your condition provider");
}
- ZenModeConfig newConfig = mConfig.copy();
+ ZenModeConfig newConfig = config.copy();
ZenModeConfig.ZenRule newRule = requireNonNull(newConfig.automaticRules.get(ruleId));
if (!Flags.modesApi()) {
if (newRule.enabled != automaticZenRule.isEnabled()) {
- dispatchOnAutomaticRuleStatusChanged(mConfig.user, newRule.getPkg(), ruleId,
+ dispatchOnAutomaticRuleStatusChanged(config.user, newRule.getPkg(), ruleId,
automaticZenRule.isEnabled()
? AUTOMATIC_RULE_STATUS_ENABLED
: AUTOMATIC_RULE_STATUS_DISABLED);
}
}
- boolean updated = populateZenRule(newRule.pkg, automaticZenRule, newRule,
+ boolean updated = populateZenRule(newRule.pkg, automaticZenRule, newConfig, newRule,
origin, /* isNew= */ false);
if (Flags.modesApi() && !updated) {
// Bail out so we don't have the side effects of updating a rule (i.e. dropping
@@ -619,16 +624,18 @@
*
* @param zenMode one of the {@code Global#ZEN_MODE_x} values
*/
- void applyGlobalZenModeAsImplicitZenRule(String callingPkg, int callingUid, int zenMode) {
+ void applyGlobalZenModeAsImplicitZenRule(UserHandle user, String callingPkg, int callingUid,
+ int zenMode) {
if (!android.app.Flags.modesApi()) {
Log.wtf(TAG, "applyGlobalZenModeAsImplicitZenRule called with flag off!");
return;
}
synchronized (mConfigLock) {
- if (mConfig == null) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) {
return;
}
- ZenModeConfig newConfig = mConfig.copy();
+ ZenModeConfig newConfig = config.copy();
ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg));
if (zenMode == Global.ZEN_MODE_OFF) {
// Deactivate implicit rule if it exists and is active; otherwise ignore.
@@ -650,9 +657,14 @@
// would apply if changing the global interruption filter. We only do this
// for newly created rules, as existing rules have a pre-existing policy
// (whether initialized here or set via app or user).
- rule.zenPolicy = mConfig.getZenPolicy().copy();
+ rule.zenPolicy = config.getZenPolicy().copy();
newConfig.automaticRules.put(rule.id, rule);
+ } else {
+ if (Flags.modesUi()) {
+ updateImplicitZenRuleNameAndDescription(rule);
+ }
}
+
// If the user has changed the rule's *zenMode*, then don't let app overwrite it.
// We allow the update if the user has only changed other aspects of the rule.
if ((rule.userModifiedFields & AutomaticZenRule.FIELD_INTERRUPTION_FILTER) == 0) {
@@ -680,17 +692,18 @@
* {@link AutomaticZenRule#configurationActivity}. Its zen mode will be set to
* {@link Global#ZEN_MODE_IMPORTANT_INTERRUPTIONS}.
*/
- void applyGlobalPolicyAsImplicitZenRule(String callingPkg, int callingUid,
+ void applyGlobalPolicyAsImplicitZenRule(UserHandle user, String callingPkg, int callingUid,
NotificationManager.Policy policy) {
if (!android.app.Flags.modesApi()) {
Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!");
return;
}
synchronized (mConfigLock) {
- if (mConfig == null) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) {
return;
}
- ZenModeConfig newConfig = mConfig.copy();
+ ZenModeConfig newConfig = config.copy();
boolean isNew = false;
ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg));
if (rule == null) {
@@ -698,7 +711,12 @@
rule = newImplicitZenRule(callingPkg);
rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
newConfig.automaticRules.put(rule.id, rule);
+ } else {
+ if (Flags.modesUi()) {
+ updateImplicitZenRuleNameAndDescription(rule);
+ }
}
+
// If the user has changed the rule's *ZenPolicy*, then don't let app overwrite it.
// We allow the update if the user has only changed other aspects of the rule.
if (rule.zenPolicyUserModifiedFields == 0) {
@@ -709,9 +727,10 @@
// would take effect if changing the global policy.
// Note that NotificationManager.Policy cannot have any unset priority
// categories, but *can* have unset visual effects, which is why we do this.
- newZenPolicy = mConfig.getZenPolicy().overwrittenWith(newZenPolicy);
+ newZenPolicy = config.getZenPolicy().overwrittenWith(newZenPolicy);
}
updatePolicy(
+ newConfig,
rule,
newZenPolicy,
/* updateBitmask= */ false,
@@ -734,25 +753,26 @@
* <p>Any unset values in the {@link ZenPolicy} will be mapped to their current defaults.
*/
@Nullable
- Policy getNotificationPolicyFromImplicitZenRule(String callingPkg) {
+ Policy getNotificationPolicyFromImplicitZenRule(UserHandle user, String callingPkg) {
if (!android.app.Flags.modesApi()) {
Log.wtf(TAG, "getNotificationPolicyFromImplicitZenRule called with flag off!");
- return getNotificationPolicy();
+ return getNotificationPolicy(user);
}
synchronized (mConfigLock) {
- if (mConfig == null) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) {
return null;
}
- ZenRule implicitRule = mConfig.automaticRules.get(implicitRuleId(callingPkg));
+ ZenRule implicitRule = config.automaticRules.get(implicitRuleId(callingPkg));
if (implicitRule != null && implicitRule.zenPolicy != null) {
- // toNotificationPolicy takes defaults from mConfig, and technically, those are not
+ // toNotificationPolicy takes defaults from config, and technically those are not
// the defaults that would apply if any fields were unset. However, all rules should
// have all fields set in their ZenPolicy objects upon rule creation, so in
// practice, this is only filling in the areChannelsBypassingDnd field, which is a
// state rather than a part of the policy.
- return mConfig.toNotificationPolicy(implicitRule.zenPolicy);
+ return config.toNotificationPolicy(implicitRule.zenPolicy);
} else {
- return getNotificationPolicy();
+ return getNotificationPolicy(user);
}
}
}
@@ -766,24 +786,8 @@
rule.id = implicitRuleId(pkg);
rule.pkg = pkg;
rule.creationTime = mClock.millis();
-
- Binder.withCleanCallingIdentity(() -> {
- try {
- ApplicationInfo applicationInfo = mPm.getApplicationInfo(pkg, 0);
- rule.name = applicationInfo.loadLabel(mPm).toString();
- if (!Flags.modesUi()) {
- rule.iconResName = drawableResIdToResName(pkg, applicationInfo.icon);
- }
- } catch (PackageManager.NameNotFoundException e) {
- // Should not happen, since it's the app calling us (?)
- Log.w(TAG, "Package not found for creating implicit zen rule");
- rule.name = "Unknown";
- }
- });
-
+ updateImplicitZenRuleNameAndDescription(rule);
rule.type = AutomaticZenRule.TYPE_OTHER;
- rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description,
- rule.name);
rule.condition = null;
rule.conditionId = new Uri.Builder()
.scheme(Condition.SCHEME)
@@ -798,13 +802,46 @@
return rule;
}
- boolean removeAutomaticZenRule(String id, @ConfigOrigin int origin, String reason,
- int callingUid) {
+ private void updateImplicitZenRuleNameAndDescription(ZenRule rule) {
+ checkArgument(isImplicitRuleId(rule.id));
+ requireNonNull(rule.pkg, "Implicit rule is not associated to package yet!");
+
+ String pkgAppName = Binder.withCleanCallingIdentity(() -> {
+ try {
+ ApplicationInfo applicationInfo = mPm.getApplicationInfo(rule.pkg, 0);
+ return applicationInfo.loadLabel(mPm).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ // Should not happen. When creating it's the app calling us, and when updating
+ // the rule would've been deleted if the package was removed.
+ Slog.e(TAG, "Package not found when updating implicit zen rule name", e);
+ return null;
+ }
+ });
+
+ if (pkgAppName != null) {
+ if ((rule.userModifiedFields & AutomaticZenRule.FIELD_NAME) == 0) {
+ if (Flags.modesUi()) {
+ rule.name = mContext.getString(R.string.zen_mode_implicit_name, pkgAppName);
+ } else {
+ rule.name = pkgAppName;
+ }
+ }
+ rule.triggerDescription = mContext.getString(
+ R.string.zen_mode_implicit_trigger_description, pkgAppName);
+ } else if (rule.name == null) {
+ // We must give a new rule SOME name. But this path should never be hit.
+ rule.name = "Unknown";
+ }
+ }
+
+ boolean removeAutomaticZenRule(UserHandle user, String id, @ConfigOrigin int origin,
+ String reason, int callingUid) {
checkManageRuleOrigin("removeAutomaticZenRule", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
- if (mConfig == null) return false;
- newConfig = mConfig.copy();
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return false;
+ newConfig = config.copy();
ZenRule ruleToRemove = newConfig.automaticRules.get(id);
if (ruleToRemove == null) return false;
if (canManageAutomaticZenRule(ruleToRemove)) {
@@ -826,18 +863,19 @@
"Cannot delete rules not owned by your condition provider");
}
dispatchOnAutomaticRuleStatusChanged(
- mConfig.user, ruleToRemove.getPkg(), id, AUTOMATIC_RULE_STATUS_REMOVED);
+ config.user, ruleToRemove.getPkg(), id, AUTOMATIC_RULE_STATUS_REMOVED);
return setConfigLocked(newConfig, origin, reason, null, true, callingUid);
}
}
- boolean removeAutomaticZenRules(String packageName, @ConfigOrigin int origin,
+ boolean removeAutomaticZenRules(UserHandle user, String packageName, @ConfigOrigin int origin,
String reason, int callingUid) {
checkManageRuleOrigin("removeAutomaticZenRules", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
- if (mConfig == null) return false;
- newConfig = mConfig.copy();
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return false;
+ newConfig = config.copy();
for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
if (Objects.equals(rule.getPkg(), packageName) && canManageAutomaticZenRule(rule)) {
@@ -885,12 +923,13 @@
}
@Condition.State
- int getAutomaticZenRuleState(String id) {
+ int getAutomaticZenRuleState(UserHandle user, String id) {
synchronized (mConfigLock) {
- if (mConfig == null) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) {
return Condition.STATE_UNKNOWN;
}
- ZenRule rule = mConfig.automaticRules.get(id);
+ ZenRule rule = config.automaticRules.get(id);
if (rule == null || !canManageAutomaticZenRule(rule)) {
return Condition.STATE_UNKNOWN;
}
@@ -903,14 +942,15 @@
}
}
- void setAutomaticZenRuleState(String id, Condition condition, @ConfigOrigin int origin,
- int callingUid) {
+ void setAutomaticZenRuleState(UserHandle user, String id, Condition condition,
+ @ConfigOrigin int origin, int callingUid) {
checkSetRuleStateOrigin("setAutomaticZenRuleState(String id)", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
- if (mConfig == null) return;
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return;
- newConfig = mConfig.copy();
+ newConfig = config.copy();
ZenRule rule = newConfig.automaticRules.get(id);
if (Flags.modesApi()) {
if (rule != null && canManageAutomaticZenRule(rule)) {
@@ -925,13 +965,14 @@
}
}
- void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition,
+ void setAutomaticZenRuleState(UserHandle user, Uri ruleDefinition, Condition condition,
@ConfigOrigin int origin, int callingUid) {
checkSetRuleStateOrigin("setAutomaticZenRuleState(Uri ruleDefinition)", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
- if (mConfig == null) return;
- newConfig = mConfig.copy();
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return;
+ newConfig = config.copy();
List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleDefinition, condition);
if (Flags.modesApi()) {
@@ -1025,13 +1066,16 @@
return true;
}
- public int getCurrentInstanceCount(ComponentName cn) {
+ public int getCurrentInstanceCount(UserHandle user, ComponentName cn) {
if (cn == null) {
return 0;
}
int count = 0;
synchronized (mConfigLock) {
- for (ZenRule rule : mConfig.automaticRules.values()) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return 0;
+
+ for (ZenRule rule : config.automaticRules.values()) {
if (cn.equals(rule.component) || cn.equals(rule.configurationActivity)) {
count++;
}
@@ -1042,13 +1086,16 @@
// Equivalent method to getCurrentInstanceCount, but for all rules associated with a specific
// package rather than a condition provider service or activity.
- private int getPackageRuleCount(String pkg) {
+ private int getPackageRuleCount(UserHandle user, String pkg) {
if (pkg == null) {
return 0;
}
int count = 0;
synchronized (mConfigLock) {
- for (ZenRule rule : mConfig.automaticRules.values()) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return 0;
+
+ for (ZenRule rule : config.automaticRules.values()) {
if (pkg.equals(rule.getPkg())) {
count++;
}
@@ -1081,13 +1128,15 @@
void updateZenRulesOnLocaleChange() {
updateRuleStringsForCurrentLocale(mContext, mDefaultConfig);
synchronized (mConfigLock) {
- if (mConfig == null) {
+ ZenModeConfig config = getConfigLocked(UserHandle.CURRENT);
+ if (config == null) {
return;
}
- ZenModeConfig config = mConfig.copy();
+
+ ZenModeConfig newConfig = config.copy();
boolean updated = false;
for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) {
- ZenRule currRule = config.automaticRules.get(defaultRule.id);
+ ZenRule currRule = newConfig.automaticRules.get(defaultRule.id);
// if default rule wasn't user-modified use localized name
// instead of previous system name
if (currRule != null
@@ -1103,14 +1152,16 @@
}
}
if (Flags.modesApi() && Flags.modesUi()) {
- for (ZenRule rule : config.automaticRules.values()) {
+ for (ZenRule rule : newConfig.automaticRules.values()) {
if (SystemZenRules.isSystemOwnedRule(rule)) {
updated |= SystemZenRules.updateTriggerDescription(mContext, rule);
+ } else if (isImplicitRuleId(rule.id)) {
+ updateImplicitZenRuleNameAndDescription(rule);
}
}
}
if (updated) {
- setConfigLocked(config, null, ORIGIN_SYSTEM,
+ setConfigLocked(newConfig, null, ORIGIN_SYSTEM,
"updateZenRulesOnLocaleChange", Process.SYSTEM_UID);
}
}
@@ -1170,8 +1221,8 @@
* deactivated) unless the update has origin == {@link ZenModeConfig#ORIGIN_USER_IN_SYSTEMUI}.
*/
@GuardedBy("mConfigLock")
- private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenRule rule,
- @ConfigOrigin int origin, boolean isNew) {
+ private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenModeConfig config,
+ ZenRule rule, @ConfigOrigin int origin, boolean isNew) {
if (Flags.modesApi()) {
boolean modified = false;
// These values can always be edited by the app, so we apply changes immediately.
@@ -1307,7 +1358,7 @@
}
// Updates the bitmask and values for all policy fields, based on the origin.
- modified |= updatePolicy(rule, azr.getZenPolicy(), updateBitmask, isNew);
+ modified |= updatePolicy(config, rule, azr.getZenPolicy(), updateBitmask, isNew);
// Updates the bitmask and values for all device effect fields, based on the origin.
modified |= updateZenDeviceEffects(rule, azr.getDeviceEffects(),
@@ -1360,13 +1411,13 @@
* <p>Returns {@code true} if the policy of the rule was modified.
*/
@GuardedBy("mConfigLock")
- private boolean updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
- boolean updateBitmask, boolean isNew) {
+ private boolean updatePolicy(ZenModeConfig config, ZenRule zenRule,
+ @Nullable ZenPolicy newPolicy, boolean updateBitmask, boolean isNew) {
if (newPolicy == null) {
if (isNew) {
// Newly created rule with no provided policy; fill in with the default.
zenRule.zenPolicy =
- (Flags.modesUi() ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy())
+ (Flags.modesUi() ? mDefaultConfig.getZenPolicy() : config.getZenPolicy())
.copy();
return true;
}
@@ -1378,7 +1429,7 @@
// fields in the bitmask should be marked as updated.
ZenPolicy oldPolicy = zenRule.zenPolicy != null
? zenRule.zenPolicy
- : (Flags.modesUi() ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy());
+ : (Flags.modesUi() ? mDefaultConfig.getZenPolicy() : config.getZenPolicy());
// If this is updating a rule rather than creating a new one, keep any fields from the
// old policy if they are unspecified in the new policy. For newly created rules, oldPolicy
@@ -1570,17 +1621,20 @@
// Update only the hasPriorityChannels state (aka areChannelsBypassingDnd) without modifying
// any of the rest of the existing policy. This allows components that only want to modify
// this bit (PreferencesHelper) to not have to adjust the rest of the policy.
- protected void updateHasPriorityChannels(boolean hasPriorityChannels) {
+ protected void updateHasPriorityChannels(UserHandle user, boolean hasPriorityChannels) {
if (!Flags.modesUi()) {
Log.wtf(TAG, "updateHasPriorityChannels called without modes_ui");
}
synchronized (mConfigLock) {
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return;
+
// If it already matches, do nothing
- if (mConfig.areChannelsBypassingDnd == hasPriorityChannels) {
+ if (config.areChannelsBypassingDnd == hasPriorityChannels) {
return;
}
- ZenModeConfig newConfig = mConfig.copy();
+ ZenModeConfig newConfig = config.copy();
newConfig.areChannelsBypassingDnd = hasPriorityChannels;
// The updated calculation of whether there are priority channels is always done by
// the system, even if the event causing the calculation had a different origin.
@@ -1610,22 +1664,25 @@
: AUTOMATIC_RULE_STATUS_DISABLED);
}
- void setManualZenMode(int zenMode, Uri conditionId, @ConfigOrigin int origin,
+ void setManualZenMode(UserHandle user, int zenMode, Uri conditionId, @ConfigOrigin int origin,
String reason, @Nullable String caller, int callingUid) {
- setManualZenMode(zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/,
+ setManualZenMode(user, zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/,
callingUid);
}
- private void setManualZenMode(int zenMode, Uri conditionId, @ConfigOrigin int origin,
- String reason, @Nullable String caller, boolean setRingerMode, int callingUid) {
+ private void setManualZenMode(UserHandle user, int zenMode, Uri conditionId,
+ @ConfigOrigin int origin, String reason, @Nullable String caller, boolean setRingerMode,
+ int callingUid) {
ZenModeConfig newConfig;
synchronized (mConfigLock) {
- if (mConfig == null) return;
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return;
+
if (!Global.isValidZenMode(zenMode)) return;
if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
+ " conditionId=" + conditionId + " reason=" + reason
+ " setRingerMode=" + setRingerMode);
- newConfig = mConfig.copy();
+ newConfig = config.copy();
if (Flags.modesUi()) {
newConfig.manualRule.enabler = caller;
newConfig.manualRule.conditionId = conditionId != null ? conditionId : Uri.EMPTY;
@@ -1668,18 +1725,20 @@
}
}
- public void setManualZenRuleDeviceEffects(ZenDeviceEffects deviceEffects,
+ public void setManualZenRuleDeviceEffects(UserHandle user, ZenDeviceEffects deviceEffects,
@ConfigOrigin int origin, String reason, int callingUid) {
if (!Flags.modesUi()) {
return;
}
ZenModeConfig newConfig;
synchronized (mConfigLock) {
- if (mConfig == null) return;
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return;
+
if (DEBUG) Log.d(TAG, "updateManualRule " + deviceEffects
+ " reason=" + reason
+ " callingUid=" + callingUid);
- newConfig = mConfig.copy();
+ newConfig = config.copy();
newConfig.manualRule.pkg = PACKAGE_ANDROID;
newConfig.manualRule.zenDeviceEffects = deviceEffects;
@@ -1709,7 +1768,7 @@
pw.println(Global.zenModeToString(mZenMode));
pw.print(prefix);
pw.println("mConsolidatedPolicy=" + mConsolidatedPolicy.toString());
- synchronized (mConfigsArrayLock) {
+ synchronized (mConfigLock) {
final int N = mConfigs.size();
for (int i = 0; i < N; i++) {
dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i));
@@ -1730,11 +1789,10 @@
pw.println(config);
}
- public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId)
- throws XmlPullParserException, IOException {
- ZenModeConfig config = ZenModeConfig.readXml(parser);
+ public boolean readXml(TypedXmlPullParser parser, boolean forRestore, int userId,
+ @Nullable BackupRestoreEventLogger logger) throws XmlPullParserException, IOException {
+ ZenModeConfig config = ZenModeConfig.readXml(parser, logger);
String reason = "readXml";
-
if (config != null) {
if (forRestore) {
config.user = userId;
@@ -1826,22 +1884,38 @@
if (DEBUG) Log.d(TAG, reason);
synchronized (mConfigLock) {
- setConfigLocked(config, null,
+ return setConfigLocked(config, null,
forRestore ? ORIGIN_RESTORE_BACKUP : ORIGIN_INIT, reason,
Process.SYSTEM_UID);
}
}
+ return false;
}
- public void writeXml(TypedXmlSerializer out, boolean forBackup, Integer version, int userId)
- throws IOException {
- synchronized (mConfigsArrayLock) {
+ public void writeXml(TypedXmlSerializer out, boolean forBackup, Integer version, int userId,
+ @Nullable BackupRestoreEventLogger logger) throws IOException {
+ synchronized (mConfigLock) {
+ int successfulWrites = 0;
+ int unsuccessfulWrites = 0;
final int n = mConfigs.size();
for (int i = 0; i < n; i++) {
if (forBackup && mConfigs.keyAt(i) != userId) {
continue;
}
- mConfigs.valueAt(i).writeXml(out, version, forBackup);
+ try {
+ mConfigs.valueAt(i).writeXml(out, version, forBackup, logger);
+ successfulWrites++;
+ } catch (Exception e) {
+ Slog.e(TAG, "failed to write config", e);
+ unsuccessfulWrites++;
+ }
+ }
+ if (logger != null) {
+ logger.logItemsBackedUp(DATA_TYPE_ZEN_CONFIG, successfulWrites);
+ if (unsuccessfulWrites > 0) {
+ logger.logItemsBackupFailed(DATA_TYPE_ZEN_CONFIG,
+ unsuccessfulWrites, ERROR_XML_PARSING);
+ }
}
}
}
@@ -1849,24 +1923,29 @@
/**
* @return user-specified default notification policy for priority only do not disturb
*/
- public Policy getNotificationPolicy() {
+ @Nullable
+ public Policy getNotificationPolicy(UserHandle user) {
synchronized (mConfigLock) {
- return getNotificationPolicy(mConfig);
+ return getNotificationPolicy(getConfigLocked(user));
}
}
- private static Policy getNotificationPolicy(ZenModeConfig config) {
+ @Nullable
+ private static Policy getNotificationPolicy(@Nullable ZenModeConfig config) {
return config == null ? null : config.toNotificationPolicy();
}
/**
* Sets the global notification policy used for priority only do not disturb
*/
- public void setNotificationPolicy(Policy policy, @ConfigOrigin int origin,
+ public void setNotificationPolicy(UserHandle user, Policy policy, @ConfigOrigin int origin,
int callingUid) {
synchronized (mConfigLock) {
- if (policy == null || mConfig == null) return;
- final ZenModeConfig newConfig = mConfig.copy();
+ if (policy == null) return;
+ ZenModeConfig config = getConfigLocked(user);
+ if (config == null) return;
+
+ final ZenModeConfig newConfig = config.copy();
if (Flags.modesApi() && !Flags.modesUi()) {
// Fix for b/337193321 -- propagate changes to notificationPolicy to rules where
// the user cannot edit zen policy to emulate the previous "inheritance".
@@ -1894,7 +1973,7 @@
}
/**
- * Cleans up obsolete rules:
+ * Cleans up obsolete rules in the current {@link ZenModeConfig}.
* <ul>
* <li>Rule instances whose owner is not installed.
* <li>Deleted rules that were deleted more than 30 days ago.
@@ -1966,6 +2045,27 @@
return mDefaultConfig.getZenPolicy();
}
+ /**
+ * Returns the {@link ZenModeConfig} corresponding to the supplied {@link UserHandle}.
+ * The result will be {@link #mConfig} if the user is {@link UserHandle#CURRENT}, or the same
+ * as {@link #mUser}, otherwise will be the corresponding entry in {@link #mConfigs}.
+ *
+ * <p>Remember to continue holding {@link #mConfigLock} while operating on the returned value.
+ */
+ @Nullable
+ @GuardedBy("mConfigLock")
+ private ZenModeConfig getConfigLocked(@NonNull UserHandle user) {
+ if (Flags.modesMultiuser()) {
+ if (user.getIdentifier() == UserHandle.USER_CURRENT || user.getIdentifier() == mUser) {
+ return mConfig;
+ } else {
+ return mConfigs.get(user.getIdentifier());
+ }
+ } else {
+ return mConfig;
+ }
+ }
+
@GuardedBy("mConfigLock")
private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
@ConfigOrigin int origin, String reason, int callingUid) {
@@ -1992,7 +2092,7 @@
}
if (config.user != mUser) {
// simply store away for background users
- synchronized (mConfigsArrayLock) {
+ synchronized (mConfigLock) {
mConfigs.put(config.user, config);
}
if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user);
@@ -2001,7 +2101,7 @@
// handle CPS backed conditions - danger! may modify config
mConditions.evaluateConfig(config, null, false /*processSubscriptions*/);
- synchronized (mConfigsArrayLock) {
+ synchronized (mConfigLock) {
mConfigs.put(config.user, config);
}
if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable());
@@ -2142,7 +2242,8 @@
}
@GuardedBy("mConfigLock")
- private void applyCustomPolicy(ZenPolicy policy, ZenRule rule, boolean useManualConfig) {
+ private void applyCustomPolicy(ZenModeConfig config, ZenPolicy policy, ZenRule rule,
+ boolean useManualConfig) {
if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
if (Flags.modesApi() && Flags.modesUi()) {
policy.apply(ZenPolicy.getBasePolicyInterruptionFilterNone());
@@ -2168,8 +2269,8 @@
} else {
if (Flags.modesApi()) {
if (useManualConfig) {
- // manual rule is configured using the settings stored directly in mConfig
- policy.apply(mConfig.getZenPolicy());
+ // manual rule is configured using the settings stored directly in ZenModeConfig
+ policy.apply(config.getZenPolicy());
} else {
// under modes_api flag, an active automatic rule with no specified policy
// inherits the device default settings as stored in mDefaultConfig. While the
@@ -2177,11 +2278,11 @@
// catch any that may have fallen through the cracks.
Log.wtf(TAG, "active automatic rule found with no specified policy: " + rule);
policy.apply(Flags.modesUi()
- ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy());
+ ? mDefaultConfig.getZenPolicy() : config.getZenPolicy());
}
} else {
// active rule with no specified policy inherits the manual rule config settings
- policy.apply(mConfig.getZenPolicy());
+ policy.apply(config.getZenPolicy());
}
}
}
@@ -2194,7 +2295,7 @@
ZenPolicy policy = new ZenPolicy();
ZenDeviceEffects.Builder deviceEffectsBuilder = new ZenDeviceEffects.Builder();
if (mConfig.isManualActive()) {
- applyCustomPolicy(policy, mConfig.manualRule, true);
+ applyCustomPolicy(mConfig, policy, mConfig.manualRule, true);
if (Flags.modesApi()) {
deviceEffectsBuilder.add(mConfig.manualRule.zenDeviceEffects);
}
@@ -2206,7 +2307,7 @@
// policy. This is relevant in case some other active rule has a more
// restrictive INTERRUPTION_FILTER but a more lenient ZenPolicy!
if (!Flags.modesApi() || automaticRule.zenMode != Global.ZEN_MODE_OFF) {
- applyCustomPolicy(policy, automaticRule, false);
+ applyCustomPolicy(mConfig, policy, automaticRule, false);
}
if (Flags.modesApi()) {
deviceEffectsBuilder.add(automaticRule.zenDeviceEffects);
@@ -2445,7 +2546,8 @@
try {
parser = resources.getXml(R.xml.default_zen_mode_config);
while (parser.next() != XmlPullParser.END_DOCUMENT) {
- final ZenModeConfig config = ZenModeConfig.readXml(XmlUtils.makeTyped(parser));
+ final ZenModeConfig config =
+ ZenModeConfig.readXml(XmlUtils.makeTyped(parser), null);
if (config != null) return config;
}
} catch (Exception e) {
@@ -2469,7 +2571,7 @@
* Generate pulled atoms about do not disturb configurations.
*/
public void pullRules(List<StatsEvent> events) {
- synchronized (mConfigsArrayLock) {
+ synchronized (mConfigLock) {
final int numConfigs = mConfigs.size();
for (int i = 0; i < numConfigs; i++) {
final int user = mConfigs.keyAt(i);
@@ -2496,7 +2598,7 @@
}
}
- @GuardedBy("mConfigsArrayLock")
+ @GuardedBy("mConfigLock")
private void ruleToProtoLocked(int user, ZenRule rule, boolean isManualRule,
List<StatsEvent> events) {
// Make the ID safe.
@@ -2601,7 +2703,7 @@
}
if (newZen != -1) {
- setManualZenMode(newZen, null, ORIGIN_SYSTEM,
+ setManualZenMode(UserHandle.CURRENT, newZen, null, ORIGIN_SYSTEM,
"ringerModeInternal", /* caller= */ null, /* setRingerMode= */ false,
Process.SYSTEM_UID);
}
@@ -2646,7 +2748,7 @@
break;
}
if (newZen != -1) {
- setManualZenMode(newZen, null, ORIGIN_SYSTEM,
+ setManualZenMode(UserHandle.CURRENT, newZen, null, ORIGIN_SYSTEM,
"ringerModeExternal", caller, false /*setRingerMode*/, Process.SYSTEM_UID);
}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
index 1553618..27c4e9d 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
@@ -35,6 +35,7 @@
@VisibleForTesting static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
@VisibleForTesting static final String FLAGGED_USER_ID_KEY = "userId";
+ @VisibleForTesting static final String INSTALL_EVENT_TYPE_KEY = "installEventType";
private static final String TAG = "BackgroundInstallControlCallbackHelper";
private final Handler mHandler;
@@ -74,10 +75,14 @@
* Invokes all registered callbacks Callbacks are processed through user provided-threads and
* parameters are passed in via {@link BackgroundInstallControlManager} InstallEvent
*/
- public void notifyAllCallbacks(int userId, String packageName) {
+ public void notifyAllCallbacks(
+ int userId,
+ String packageName,
+ @BackgroundInstallControlService.InstallEventType int installEventType) {
Bundle extras = new Bundle();
extras.putCharSequence(FLAGGED_PACKAGE_NAME_KEY, packageName);
extras.putInt(FLAGGED_USER_ID_KEY, userId);
+ extras.putInt(INSTALL_EVENT_TYPE_KEY, installEventType);
synchronized (mCallbacks) {
mHandler.post(
() ->
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index b6daed1..20cca969 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.app.Flags;
@@ -64,6 +65,8 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@@ -76,11 +79,23 @@
* @hide
*/
public class BackgroundInstallControlService extends SystemService {
+ public static final int INSTALL_EVENT_TYPE_UNKNOWN = 0;
+ public static final int INSTALL_EVENT_TYPE_INSTALL = 1;
+ public static final int INSTALL_EVENT_TYPE_UNINSTALL = 2;
+
+ @IntDef(
+ value = {
+ INSTALL_EVENT_TYPE_UNKNOWN,
+ INSTALL_EVENT_TYPE_INSTALL,
+ INSTALL_EVENT_TYPE_UNINSTALL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InstallEventType {}
+
private static final String TAG = "BackgroundInstallControlService";
private static final String DISK_FILE_NAME = "states";
private static final String DISK_DIR_NAME = "bic";
-
private static final String ENFORCE_PERMISSION_ERROR_MSG =
"User is not permitted to call service: ";
@@ -313,7 +328,7 @@
initBackgroundInstalledPackages();
mBackgroundInstalledPackages.add(userId, packageName);
- mCallbackHelper.notifyAllCallbacks(userId, packageName);
+ mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_INSTALL);
writeBackgroundInstalledPackagesToDisk();
}
@@ -391,6 +406,7 @@
initBackgroundInstalledPackages();
mBackgroundInstalledPackages.remove(userId, packageName);
writeBackgroundInstalledPackagesToDisk();
+ mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL);
}
void handleUsageEvent(UsageEvents.Event event, int userId) {
diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
index d1d6ed0..77572e0 100644
--- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
+++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
@@ -179,13 +179,17 @@
final String action = intent.getAction().substring(actionIndex);
Log.d(LOG_TAG, "Action requested: " + action + ", by userId "
+ ActivityManager.getCurrentUser() + " for alarm on user "
- + UserHandle.getUserHandleForUid(clientUid));
+ + UserHandle.getUserHandleForUid(clientUid).getIdentifier());
}
if (ACTION_MUTE_SOUND.equals(intent.getAction())) {
muteAlarmSounds(clientUid);
} else if (ACTION_SWITCH_USER.equals(intent.getAction())) {
- activityManager.switchUser(UserHandle.getUserId(clientUid));
+ int userId = UserHandle.getUserId(clientUid);
+ if (mUserManager.isProfile(userId)) {
+ userId = mUserManager.getProfileParent(userId).id;
+ }
+ activityManager.switchUser(userId);
}
if (Flags.multipleAlarmNotificationsSupport()) {
mNotificationClientUids.remove(clientUid);
@@ -237,11 +241,12 @@
UserHandle.of(ActivityManager.getCurrentUser()), 0);
final int userId = UserHandle.getUserId(afi.getClientUid());
final int usage = afi.getAttributes().getUsage();
- UserInfo userInfo = mUserManager.getUserInfo(userId);
-
+ UserInfo userInfo = mUserManager.isProfile(userId) ? mUserManager.getProfileParent(userId) :
+ mUserManager.getUserInfo(userId);
+ ActivityManager activityManager = foregroundContext.getSystemService(ActivityManager.class);
// Only show notification if the sound is coming from background user and the notification
// for this UID is not already shown.
- if (userInfo != null && userId != foregroundContext.getUserId()
+ if (userInfo != null && !activityManager.isProfileForeground(userInfo.getUserHandle())
&& !isNotificationShown(afi.getClientUid())) {
//TODO: b/349138482 - Add handling of cases when usage == USAGE_NOTIFICATION_RINGTONE
if (usage == USAGE_ALARM) {
diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
index 2db454a..db65bf0 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
@@ -33,6 +33,7 @@
import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.pm.parsing.pkg.PackageImpl;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.component.AconfigFlags;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.pm.ApexManager;
@@ -41,6 +42,8 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
public class PackageCacher implements IPackageCacher {
@@ -57,6 +60,8 @@
@Nullable
private final PackageParser2.Callback mCallback;
+ private static final AconfigFlags sAconfigFlags = ParsingPackageUtils.getAconfigFlags();
+
public PackageCacher(File cacheDir) {
this(cacheDir, null);
}
@@ -136,7 +141,7 @@
* Given a {@code packageFile} and a {@code cacheFile} returns whether the
* cache file is up to date based on the mod-time of both files.
*/
- private static boolean isCacheUpToDate(File packageFile, File cacheFile) {
+ private static boolean isCacheFileUpToDate(File packageFile, File cacheFile) {
try {
// In case packageFile is located on one of /apex mount points it's mtime will always be
// 0. Instead, we can use mtime of the APEX file backing the corresponding mount point.
@@ -185,16 +190,36 @@
try {
// If the cache is not up to date, return null.
- if (!isCacheUpToDate(packageFile, cacheFile)) {
+ if (!isCacheFileUpToDate(packageFile, cacheFile)) {
return null;
}
final byte[] bytes = IoUtils.readFileAsByteArray(cacheFile.getAbsolutePath());
- ParsedPackage parsed = fromCacheEntry(bytes);
+ final ParsedPackage parsed = fromCacheEntry(bytes);
if (!packageFile.getAbsolutePath().equals(parsed.getPath())) {
// Don't use this cache if the path doesn't match
return null;
}
+
+ if (!android.content.pm.Flags.includeFeatureFlagsInPackageCacher()) {
+ return parsed;
+ }
+
+ final Map<String, Boolean> featureFlagState =
+ ((PackageImpl) parsed).getFeatureFlagState();
+ if (!featureFlagState.isEmpty()) {
+ Slog.d(TAG, "Feature flags for package " + packageFile + ": " + featureFlagState);
+ for (var entry : featureFlagState.entrySet()) {
+ final String flagPackageAndName = entry.getKey();
+ if (!Objects.equals(sAconfigFlags.getFlagValue(flagPackageAndName),
+ entry.getValue())) {
+ Slog.i(TAG, "Feature flag " + flagPackageAndName + " changed for package "
+ + packageFile + "; cached result is invalid");
+ return null;
+ }
+ }
+ }
+
return parsed;
} catch (Throwable e) {
Slog.w(TAG, "Error reading package cache: ", e);
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index bc6a40a..07fd1cb 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1271,6 +1271,7 @@
*/
private boolean isFixedOrUserSet(int flags) {
return (flags & (PackageManager.FLAG_PERMISSION_USER_SET
+ | PackageManager.FLAG_PERMISSION_ONE_TIME
| PackageManager.FLAG_PERMISSION_USER_FIXED
| PackageManager.FLAG_PERMISSION_POLICY_FIXED
| PackageManager.FLAG_PERMISSION_SYSTEM_FIXED)) != 0;
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index 0962319..38bc026 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -115,11 +115,18 @@
*/
private int mPriority;
+ /**
+ * If resource holder retains ownership of the resource in a challenge scenario then value is
+ * true.
+ */
+ private boolean mResourceHolderRetain;
+
private ClientProfile(Builder builder) {
this.mId = builder.mId;
this.mTvInputSessionId = builder.mTvInputSessionId;
this.mUseCase = builder.mUseCase;
this.mProcessId = builder.mProcessId;
+ this.mResourceHolderRetain = builder.mResourceHolderRetain;
}
public int getId() {
@@ -139,6 +146,14 @@
}
/**
+ * Returns true when the resource holder retains ownership of the resource in a challenge
+ * scenario.
+ */
+ public boolean shouldResourceHolderRetain() {
+ return mResourceHolderRetain;
+ }
+
+ /**
* If the client priority is overwrttien.
*/
public boolean isPriorityOverwritten() {
@@ -180,6 +195,19 @@
}
/**
+ * Determines whether the resource holder retains ownership of the resource during a challenge
+ * scenario, when both resource holder and resource challenger have same processId and same
+ * priority.
+ *
+ * @param resourceHolderRetain Set to true to allow the resource holder to retain ownership, or
+ * false (or resourceHolderRetain not set at all) to allow the resource challenger to
+ * acquire the resource. If not explicitly set, resourceHolderRetain is set to false.
+ */
+ public void setResourceHolderRetain(boolean resourceHolderRetain) {
+ mResourceHolderRetain = resourceHolderRetain;
+ }
+
+ /**
* Set when the client starts to use a frontend.
*
* @param frontendHandle being used.
@@ -361,6 +389,7 @@
private String mTvInputSessionId;
private int mUseCase;
private int mProcessId;
+ private boolean mResourceHolderRetain = false;
Builder(int id) {
this.mId = id;
@@ -397,6 +426,18 @@
}
/**
+ * Builder for {@link ClientProfile}.
+ *
+ * @param resourceHolderRetain the determining factor for resource ownership during
+ * challenger scenario. The default behavior favors the resource challenger and grants
+ * them ownership of the resource if resourceHolderRetain is not explicitly set to true.
+ */
+ public Builder resourceHolderRetain(boolean resourceHolderRetain) {
+ this.mResourceHolderRetain = resourceHolderRetain;
+ return this;
+ }
+
+ /**
* Build a {@link ClientProfile}.
*
* @return {@link ClientProfile}.
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index c5b6bbf..5ae8c11 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.tv.tunerresourcemanager;
+import static android.media.tv.flags.Flags.setResourceHolderRetain;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -229,6 +231,14 @@
}
@Override
+ public void setResourceHolderRetain(int clientId, boolean resourceHolderRetain) {
+ enforceTrmAccessPermission("setResourceHolderRetain");
+ synchronized (mLock) {
+ getClientProfile(clientId).setResourceHolderRetain(resourceHolderRetain);
+ }
+ }
+
+ @Override
public boolean isLowestPriority(int clientId, int frontendType)
throws RemoteException {
enforceTrmAccessPermission("isLowestPriority");
@@ -1066,8 +1076,10 @@
// request client has higher priority.
if (inUseLowestPriorityFrontend != null
&& ((requestClient.getPriority() > currentLowestPriority)
- || ((requestClient.getPriority() == currentLowestPriority)
- && isRequestFromSameProcess))) {
+ || ((requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess
+ && !(setResourceHolderRetain()
+ && requestClient.shouldResourceHolderRetain())))) {
frontendHandle[0] = inUseLowestPriorityFrontend.getHandle();
reclaimOwnerId[0] = inUseLowestPriorityFrontend.getOwnerClientId();
return true;
@@ -1249,9 +1261,11 @@
// When all the resources are occupied, grant the lowest priority resource if the
// request client has higher priority.
if (inUseLowestPriorityLnb != null
- && ((requestClient.getPriority() > currentLowestPriority) || (
- (requestClient.getPriority() == currentLowestPriority)
- && isRequestFromSameProcess))) {
+ && ((requestClient.getPriority() > currentLowestPriority)
+ || ((requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess
+ && !(setResourceHolderRetain()
+ && requestClient.shouldResourceHolderRetain())))) {
lnbHandle[0] = inUseLowestPriorityLnb.getHandle();
reclaimOwnerId[0] = inUseLowestPriorityLnb.getOwnerClientId();
return true;
@@ -1335,8 +1349,10 @@
// request client has higher priority.
if (lowestPriorityOwnerId != INVALID_CLIENT_ID
&& ((requestClient.getPriority() > currentLowestPriority)
- || ((requestClient.getPriority() == currentLowestPriority)
- && isRequestFromSameProcess))) {
+ || ((requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess
+ && !(setResourceHolderRetain()
+ && requestClient.shouldResourceHolderRetain())))) {
casSessionHandle[0] = cas.getHandle();
reclaimOwnerId[0] = lowestPriorityOwnerId;
return true;
@@ -1420,8 +1436,10 @@
// request client has higher priority.
if (lowestPriorityOwnerId != INVALID_CLIENT_ID
&& ((requestClient.getPriority() > currentLowestPriority)
- || ((requestClient.getPriority() == currentLowestPriority)
- && isRequestFromSameProcess))) {
+ || ((requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess
+ && !(setResourceHolderRetain()
+ && requestClient.shouldResourceHolderRetain())))) {
ciCamHandle[0] = ciCam.getHandle();
reclaimOwnerId[0] = lowestPriorityOwnerId;
return true;
@@ -1655,9 +1673,11 @@
// When all the resources are occupied, grant the lowest priority resource if the
// request client has higher priority.
if (inUseLowestPriorityDemux != null
- && ((requestClient.getPriority() > currentLowestPriority) || (
- (requestClient.getPriority() == currentLowestPriority)
- && isRequestFromSameProcess))) {
+ && ((requestClient.getPriority() > currentLowestPriority)
+ || ((requestClient.getPriority() == currentLowestPriority)
+ && isRequestFromSameProcess
+ && !(setResourceHolderRetain()
+ && requestClient.shouldResourceHolderRetain())))) {
demuxHandle[0] = inUseLowestPriorityDemux.getHandle();
reclaimOwnerId[0] = inUseLowestPriorityDemux.getOwnerClientId();
return true;
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index baf84cf..3392d03 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -436,6 +436,17 @@
return mPrivilegedPackages.keySet();
}
+ /** Returns all subscription groups */
+ @NonNull
+ public Set<ParcelUuid> getAllSubscriptionGroups() {
+ final Set<ParcelUuid> subGroups = new ArraySet<>();
+ for (SubscriptionInfo subInfo : mSubIdToInfoMap.values()) {
+ subGroups.add(subInfo.getGroupUuid());
+ }
+
+ return subGroups;
+ }
+
/** Checks if the provided package is carrier privileged for the specified sub group. */
public boolean packageHasPermissionsForSubscriptionGroup(
@NonNull ParcelUuid subGrp, @NonNull String packageName) {
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index ab5316f..92ce251 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -16,8 +16,6 @@
package com.android.server.webkit;
-import static android.webkit.Flags.updateServiceV2;
-
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -253,46 +251,11 @@
}
@Override
- public int getMultiProcessSetting() {
- if (updateServiceV2()) {
- throw new IllegalStateException(
- "getMultiProcessSetting shouldn't be called if update_service_v2 flag is set.");
- }
- return Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, 0);
- }
-
- @Override
- public void setMultiProcessSetting(int value) {
- if (updateServiceV2()) {
- throw new IllegalStateException(
- "setMultiProcessSetting shouldn't be called if update_service_v2 flag is set.");
- }
- Settings.Global.putInt(
- mContext.getContentResolver(), Settings.Global.WEBVIEW_MULTIPROCESS, value);
- }
-
- @Override
- public void notifyZygote(boolean enableMultiProcess) {
- if (updateServiceV2()) {
- throw new IllegalStateException(
- "notifyZygote shouldn't be called if update_service_v2 flag is set.");
- }
- WebViewZygote.setMultiprocessEnabled(enableMultiProcess);
- }
-
- @Override
public void ensureZygoteStarted() {
WebViewZygote.getProcess();
}
@Override
- public boolean isMultiProcessDefaultEnabled() {
- // Multiprocess is enabled by default for all devices.
- return true;
- }
-
- @Override
public void pinWebviewIfRequired(ApplicationInfo appInfo) {
PinnerService pinnerService = LocalServices.getService(PinnerService.class);
int webviewPinQuota = pinnerService.getWebviewPinQuota();
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index 3b77d07..6710554 100644
--- a/services/core/java/com/android/server/webkit/SystemInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -55,12 +55,8 @@
*/
List<UserPackage> getPackageInfoForProviderAllUsers(WebViewProviderInfo configInfo);
- int getMultiProcessSetting();
- void setMultiProcessSetting(int value);
- void notifyZygote(boolean enableMultiProcess);
/** Start the zygote if it's not already running. */
void ensureZygoteStarted();
- boolean isMultiProcessDefaultEnabled();
void pinWebviewIfRequired(ApplicationInfo appInfo);
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 7acb864..744c3da6 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -16,8 +16,6 @@
package com.android.server.webkit;
-import static android.webkit.Flags.updateServiceV2;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -63,7 +61,7 @@
new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f));
private BroadcastReceiver mWebViewUpdatedReceiver;
- private WebViewUpdateServiceInterface mImpl;
+ private WebViewUpdateServiceImpl2 mImpl;
static final int PACKAGE_CHANGED = 0;
static final int PACKAGE_ADDED = 1;
@@ -72,11 +70,7 @@
public WebViewUpdateService(Context context) {
super(context);
- if (updateServiceV2()) {
- mImpl = new WebViewUpdateServiceImpl2(new SystemImpl(context));
- } else {
- mImpl = new WebViewUpdateServiceImpl(new SystemImpl(context));
- }
+ mImpl = new WebViewUpdateServiceImpl2(new SystemImpl(context));
}
@Override
@@ -170,13 +164,8 @@
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- if (updateServiceV2()) {
- (new WebViewUpdateServiceShellCommand2(this))
- .exec(this, in, out, err, args, callback, resultReceiver);
- } else {
- (new WebViewUpdateServiceShellCommand(this))
- .exec(this, in, out, err, args, callback, resultReceiver);
- }
+ (new WebViewUpdateServiceShellCommand2(this))
+ .exec(this, in, out, err, args, callback, resultReceiver);
}
@@ -300,45 +289,6 @@
return currentWebViewPackage;
}
- @Override // Binder call
- public boolean isMultiProcessEnabled() {
- if (updateServiceV2()) {
- throw new IllegalStateException(
- "isMultiProcessEnabled shouldn't be called if update_service_v2 flag is"
- + " set.");
- }
- return WebViewUpdateService.this.mImpl.isMultiProcessEnabled();
- }
-
- @Override // Binder call
- public void enableMultiProcess(boolean enable) {
- if (updateServiceV2()) {
- throw new IllegalStateException(
- "enableMultiProcess shouldn't be called if update_service_v2 flag is set.");
- }
- if (getContext()
- .checkCallingPermission(
- android.Manifest.permission.WRITE_SECURE_SETTINGS)
- != PackageManager.PERMISSION_GRANTED) {
- String msg =
- "Permission Denial: enableMultiProcess() from pid="
- + Binder.getCallingPid()
- + ", uid="
- + Binder.getCallingUid()
- + " requires "
- + android.Manifest.permission.WRITE_SECURE_SETTINGS;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
-
- final long callingId = Binder.clearCallingIdentity();
- try {
- WebViewUpdateService.this.mImpl.enableMultiProcess(enable);
- } finally {
- Binder.restoreCallingIdentity(callingId);
- }
- }
-
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
deleted file mode 100644
index b9be4a2..0000000
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ /dev/null
@@ -1,768 +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 com.android.server.webkit;
-
-import android.annotation.Nullable;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.Signature;
-import android.os.AsyncTask;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.util.AndroidRuntimeException;
-import android.util.Slog;
-import android.webkit.UserPackage;
-import android.webkit.WebViewFactory;
-import android.webkit.WebViewProviderInfo;
-import android.webkit.WebViewProviderResponse;
-
-import com.android.modules.expresslog.Counter;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Implementation of the WebViewUpdateService.
- * This class doesn't depend on the android system like the actual Service does and can be used
- * directly by tests (as long as they implement a SystemInterface).
- *
- * This class keeps track of and prepares the current WebView implementation, and needs to keep
- * track of a couple of different things such as what package is used as WebView implementation.
- *
- * The package-visible methods in this class are accessed from WebViewUpdateService either on the UI
- * thread or on one of multiple Binder threads. The WebView preparation code shares state between
- * threads meaning that code that chooses a new WebView implementation or checks which
- * implementation is being used needs to hold a lock.
- *
- * The WebViewUpdateService can be accessed in a couple of different ways.
- * 1. It is started from the SystemServer at boot - at that point we just initiate some state such
- * as the WebView preparation class.
- * 2. The SystemServer calls WebViewUpdateService.prepareWebViewInSystemServer. This happens at boot
- * and the WebViewUpdateService should not have been accessed before this call. In this call we
- * choose WebView implementation for the first time.
- * 3. The update service listens for Intents related to package installs and removals. These intents
- * are received and processed on the UI thread. Each intent can result in changing WebView
- * implementation.
- * 4. The update service can be reached through Binder calls which are handled on specific binder
- * threads. These calls can be made from any process. Generally they are used for changing WebView
- * implementation (from Settings), getting information about the current WebView implementation (for
- * loading WebView into an app process), or notifying the service about Relro creation being
- * completed.
- *
- * @hide
- */
-class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
- private static final String TAG = WebViewUpdateServiceImpl.class.getSimpleName();
-
- private static class WebViewPackageMissingException extends Exception {
- WebViewPackageMissingException(String message) {
- super(message);
- }
-
- WebViewPackageMissingException(Exception e) {
- super(e);
- }
- }
-
- private static final int WAIT_TIMEOUT_MS = 1000; // KEY_DISPATCHING_TIMEOUT is 5000.
- private static final long NS_PER_MS = 1000000;
-
- private static final int VALIDITY_OK = 0;
- private static final int VALIDITY_INCORRECT_SDK_VERSION = 1;
- private static final int VALIDITY_INCORRECT_VERSION_CODE = 2;
- private static final int VALIDITY_INCORRECT_SIGNATURE = 3;
- private static final int VALIDITY_NO_LIBRARY_FLAG = 4;
-
- private static final int MULTIPROCESS_SETTING_ON_VALUE = Integer.MAX_VALUE;
- private static final int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE;
-
- private final SystemInterface mSystemInterface;
-
- private long mMinimumVersionCode = -1;
-
- // Keeps track of the number of running relro creations
- private int mNumRelroCreationsStarted = 0;
- private int mNumRelroCreationsFinished = 0;
- // Implies that we need to rerun relro creation because we are using an out-of-date package
- private boolean mWebViewPackageDirty = false;
- private boolean mAnyWebViewInstalled = false;
-
- private static final int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
-
- // The WebView package currently in use (or the one we are preparing).
- private PackageInfo mCurrentWebViewPackage = null;
-
- private final Object mLock = new Object();
-
- WebViewUpdateServiceImpl(SystemInterface systemInterface) {
- mSystemInterface = systemInterface;
- }
-
- @Override
- public void packageStateChanged(String packageName, int changedState, int userId) {
- // We don't early out here in different cases where we could potentially early-out (e.g. if
- // we receive PACKAGE_CHANGED for another user than the system user) since that would
- // complicate this logic further and open up for more edge cases.
- for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
- String webviewPackage = provider.packageName;
-
- if (webviewPackage.equals(packageName)) {
- boolean updateWebView = false;
- boolean removedOrChangedOldPackage = false;
- String oldProviderName = null;
- PackageInfo newPackage = null;
- synchronized (mLock) {
- try {
- newPackage = findPreferredWebViewPackage();
- if (mCurrentWebViewPackage != null) {
- oldProviderName = mCurrentWebViewPackage.packageName;
- }
- // Only trigger update actions if the updated package is the one
- // that will be used, or the one that was in use before the
- // update, or if we haven't seen a valid WebView package before.
- updateWebView =
- provider.packageName.equals(newPackage.packageName)
- || provider.packageName.equals(oldProviderName)
- || mCurrentWebViewPackage == null;
- // We removed the old package if we received an intent to remove
- // or replace the old package.
- removedOrChangedOldPackage =
- provider.packageName.equals(oldProviderName);
- if (updateWebView) {
- onWebViewProviderChanged(newPackage);
- }
- } catch (WebViewPackageMissingException e) {
- mCurrentWebViewPackage = null;
- Slog.e(TAG, "Could not find valid WebView package to create relro with "
- + e);
- }
- }
- if (updateWebView && !removedOrChangedOldPackage
- && oldProviderName != null) {
- // If the provider change is the result of adding or replacing a
- // package that was not the previous provider then we must kill
- // packages dependent on the old package ourselves. The framework
- // only kills dependents of packages that are being removed.
- mSystemInterface.killPackageDependents(oldProviderName);
- }
- return;
- }
- }
- }
-
- @Override
- public void prepareWebViewInSystemServer() {
- mSystemInterface.notifyZygote(isMultiProcessEnabled());
- try {
- synchronized (mLock) {
- mCurrentWebViewPackage = findPreferredWebViewPackage();
- String userSetting = mSystemInterface.getUserChosenWebViewProvider();
- if (userSetting != null
- && !userSetting.equals(mCurrentWebViewPackage.packageName)) {
- // Don't persist the user-chosen setting across boots if the package being
- // chosen is not used (could be disabled or uninstalled) so that the user won't
- // be surprised by the device switching to using a certain webview package,
- // that was uninstalled/disabled a long time ago, if it is installed/enabled
- // again.
- mSystemInterface.updateUserSetting(mCurrentWebViewPackage.packageName);
- }
- onWebViewProviderChanged(mCurrentWebViewPackage);
- }
- } catch (WebViewPackageMissingException e) {
- Slog.e(TAG, "Could not find valid WebView package to create relro with", e);
- } catch (Throwable t) {
- // We don't know a case when this should happen but we log and discard errors at this
- // stage as we must not crash the system server.
- Slog.wtf(TAG, "error preparing webview provider from system server", t);
- }
-
- if (getCurrentWebViewPackage() == null) {
- // We didn't find a valid WebView implementation. Try explicitly re-enabling the
- // fallback package for all users in case it was disabled, even if we already did the
- // one-time migration before. If this actually changes the state, we will see the
- // PackageManager broadcast shortly and try again.
- WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
- WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
- if (fallbackProvider != null) {
- Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName);
- mSystemInterface.enablePackageForAllUsers(fallbackProvider.packageName, true);
- } else {
- Slog.e(TAG, "No valid provider and no fallback available.");
- }
- }
- }
-
- private void startZygoteWhenReady() {
- // Wait on a background thread for RELRO creation to be done. We ignore the return value
- // because even if RELRO creation failed we still want to start the zygote.
- waitForAndGetProvider();
- mSystemInterface.ensureZygoteStarted();
- }
-
- @Override
- public void handleNewUser(int userId) {
- // The system user is always started at boot, and by that point we have already run one
- // round of the package-changing logic (through prepareWebViewInSystemServer()), so early
- // out here.
- if (userId == UserHandle.USER_SYSTEM) return;
- handleUserChange();
- }
-
- @Override
- public void handleUserRemoved(int userId) {
- handleUserChange();
- }
-
- /**
- * Called when a user was added or removed to ensure WebView preparation is triggered.
- * This has to be done since the WebView package we use depends on the enabled-state
- * of packages for all users (so adding or removing a user might cause us to change package).
- */
- private void handleUserChange() {
- // Potentially trigger package-changing logic.
- updateCurrentWebViewPackage(null);
- }
-
- @Override
- public void notifyRelroCreationCompleted() {
- synchronized (mLock) {
- mNumRelroCreationsFinished++;
- checkIfRelrosDoneLocked();
- }
- }
-
- @Override
- public WebViewProviderResponse waitForAndGetProvider() {
- PackageInfo webViewPackage = null;
- final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
- boolean webViewReady = false;
- int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS;
- synchronized (mLock) {
- webViewReady = webViewIsReadyLocked();
- while (!webViewReady) {
- final long timeNowMs = System.nanoTime() / NS_PER_MS;
- if (timeNowMs >= timeoutTimeMs) break;
- try {
- mLock.wait(timeoutTimeMs - timeNowMs);
- } catch (InterruptedException e) {
- // ignore
- }
- webViewReady = webViewIsReadyLocked();
- }
- // Make sure we return the provider that was used to create the relro file
- webViewPackage = mCurrentWebViewPackage;
- if (webViewReady) {
- // success
- } else if (!mAnyWebViewInstalled) {
- webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
- } else {
- // Either the current relro creation isn't done yet, or the new relro creatioin
- // hasn't kicked off yet (the last relro creation used an out-of-date WebView).
- webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
- String timeoutError = "Timed out waiting for relro creation, relros started "
- + mNumRelroCreationsStarted
- + " relros finished " + mNumRelroCreationsFinished
- + " package dirty? " + mWebViewPackageDirty;
- Slog.e(TAG, timeoutError);
- Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, timeoutError);
- }
- }
- if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
- return new WebViewProviderResponse(webViewPackage, webViewStatus);
- }
-
- /**
- * Change WebView provider and provider setting and kill packages using the old provider.
- * Return the new provider (in case we are in the middle of creating relro files, or
- * replacing that provider it will not be in use directly, but will be used when the relros
- * or the replacement are done).
- */
- @Override
- public String changeProviderAndSetting(String newProviderName) {
- PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
- if (newPackage == null) return "";
- return newPackage.packageName;
- }
-
- /**
- * Update the current WebView package.
- * @param newProviderName the package to switch to, null if no package has been explicitly
- * chosen.
- */
- private PackageInfo updateCurrentWebViewPackage(@Nullable String newProviderName) {
- PackageInfo oldPackage = null;
- PackageInfo newPackage = null;
- boolean providerChanged = false;
- synchronized (mLock) {
- oldPackage = mCurrentWebViewPackage;
-
- if (newProviderName != null) {
- mSystemInterface.updateUserSetting(newProviderName);
- }
-
- try {
- newPackage = findPreferredWebViewPackage();
- providerChanged = (oldPackage == null)
- || !newPackage.packageName.equals(oldPackage.packageName);
- } catch (WebViewPackageMissingException e) {
- // If updated the Setting but don't have an installed WebView package, the
- // Setting will be used when a package is available.
- mCurrentWebViewPackage = null;
- Slog.e(TAG, "Couldn't find WebView package to use " + e);
- return null;
- }
- // Perform the provider change if we chose a new provider
- if (providerChanged) {
- onWebViewProviderChanged(newPackage);
- }
- }
- // Kill apps using the old provider only if we changed provider
- if (providerChanged && oldPackage != null) {
- mSystemInterface.killPackageDependents(oldPackage.packageName);
- }
- // Return the new provider, this is not necessarily the one we were asked to switch to,
- // but the persistent setting will now be pointing to the provider we were asked to
- // switch to anyway.
- return newPackage;
- }
-
- /**
- * This is called when we change WebView provider, either when the current provider is
- * updated or a new provider is chosen / takes precedence.
- */
- private void onWebViewProviderChanged(PackageInfo newPackage) {
- synchronized (mLock) {
- mAnyWebViewInstalled = true;
- if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
- mSystemInterface.pinWebviewIfRequired(newPackage.applicationInfo);
- mCurrentWebViewPackage = newPackage;
-
- // The relro creations might 'finish' (not start at all) before
- // WebViewFactory.onWebViewProviderChanged which means we might not know the
- // number of started creations before they finish.
- mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
- mNumRelroCreationsFinished = 0;
- mNumRelroCreationsStarted =
- mSystemInterface.onWebViewProviderChanged(newPackage);
- Counter.logIncrement("webview.value_on_webview_provider_changed_counter");
- if (newPackage.packageName.equals(getDefaultWebViewPackage().packageName)) {
- Counter.logIncrement(
- "webview.value_on_webview_provider_changed_"
- + "with_default_package_counter");
- }
- // If the relro creations finish before we know the number of started creations
- // we will have to do any cleanup/notifying here.
- checkIfRelrosDoneLocked();
- } else {
- mWebViewPackageDirty = true;
- }
- }
-
- // Once we've notified the system that the provider has changed and started RELRO creation,
- // try to restart the zygote so that it will be ready when apps use it.
- if (isMultiProcessEnabled()) {
- AsyncTask.THREAD_POOL_EXECUTOR.execute(this::startZygoteWhenReady);
- }
- }
-
- /**
- * Fetch only the currently valid WebView packages.
- **/
- @Override
- public WebViewProviderInfo[] getValidWebViewPackages() {
- ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
- WebViewProviderInfo[] providers =
- new WebViewProviderInfo[providersAndPackageInfos.length];
- for (int n = 0; n < providersAndPackageInfos.length; n++) {
- providers[n] = providersAndPackageInfos[n].provider;
- }
- return providers;
- }
-
- @Override
- public WebViewProviderInfo getDefaultWebViewPackage() {
- for (WebViewProviderInfo provider : getWebViewPackages()) {
- if (provider.availableByDefault) {
- return provider;
- }
- }
-
- // This should be unreachable because the config parser enforces that there is at least
- // one availableByDefault provider.
- throw new AndroidRuntimeException("No available by default WebView Provider.");
- }
-
- private static class ProviderAndPackageInfo {
- public final WebViewProviderInfo provider;
- public final PackageInfo packageInfo;
-
- ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) {
- this.provider = provider;
- this.packageInfo = packageInfo;
- }
- }
-
- private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
- WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
- List<ProviderAndPackageInfo> providers = new ArrayList<>();
- for (int n = 0; n < allProviders.length; n++) {
- try {
- PackageInfo packageInfo =
- mSystemInterface.getPackageInfoForProvider(allProviders[n]);
- if (validityResult(allProviders[n], packageInfo) == VALIDITY_OK) {
- providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
- }
- } catch (NameNotFoundException e) {
- // Don't add non-existent packages
- }
- }
- return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
- }
-
- /**
- * Returns either the package info of the WebView provider determined in the following way:
- * If the user has chosen a provider then use that if it is valid,
- * otherwise use the first package in the webview priority list that is valid.
- *
- */
- private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
- ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
-
- String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider();
-
- // If the user has chosen provider, use that (if it's installed and enabled for all
- // users).
- for (ProviderAndPackageInfo providerAndPackage : providers) {
- if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
- // userPackages can contain null objects.
- List<UserPackage> userPackages =
- mSystemInterface.getPackageInfoForProviderAllUsers(
- providerAndPackage.provider);
- if (isInstalledAndEnabledForAllUsers(userPackages)) {
- return providerAndPackage.packageInfo;
- }
- }
- }
-
- // User did not choose, or the choice failed; use the most stable provider that is
- // installed and enabled for all users, and available by default (not through
- // user choice).
- for (ProviderAndPackageInfo providerAndPackage : providers) {
- if (providerAndPackage.provider.availableByDefault) {
- // userPackages can contain null objects.
- List<UserPackage> userPackages =
- mSystemInterface.getPackageInfoForProviderAllUsers(
- providerAndPackage.provider);
- if (isInstalledAndEnabledForAllUsers(userPackages)) {
- return providerAndPackage.packageInfo;
- }
- }
- }
-
- // This should never happen during normal operation (only with modified system images).
- mAnyWebViewInstalled = false;
- throw new WebViewPackageMissingException("Could not find a loadable WebView package");
- }
-
- /**
- * Return true iff {@param packageInfos} point to only installed and enabled packages.
- * The given packages {@param packageInfos} should all be pointing to the same package, but each
- * PackageInfo representing a different user's package.
- */
- private static boolean isInstalledAndEnabledForAllUsers(
- List<UserPackage> userPackages) {
- for (UserPackage userPackage : userPackages) {
- if (!userPackage.isInstalledPackage() || !userPackage.isEnabledPackage()) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public WebViewProviderInfo[] getWebViewPackages() {
- return mSystemInterface.getWebViewPackages();
- }
-
- @Override
- public PackageInfo getCurrentWebViewPackage() {
- synchronized (mLock) {
- return mCurrentWebViewPackage;
- }
- }
-
- /**
- * Returns whether WebView is ready and is not going to go through its preparation phase
- * again directly.
- */
- private boolean webViewIsReadyLocked() {
- return !mWebViewPackageDirty
- && (mNumRelroCreationsStarted == mNumRelroCreationsFinished)
- // The current package might be replaced though we haven't received an intent
- // declaring this yet, the following flag makes anyone loading WebView to wait in
- // this case.
- && mAnyWebViewInstalled;
- }
-
- private void checkIfRelrosDoneLocked() {
- if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
- if (mWebViewPackageDirty) {
- mWebViewPackageDirty = false;
- // If we have changed provider since we started the relro creation we need to
- // redo the whole process using the new package instead.
- try {
- PackageInfo newPackage = findPreferredWebViewPackage();
- onWebViewProviderChanged(newPackage);
- } catch (WebViewPackageMissingException e) {
- mCurrentWebViewPackage = null;
- // If we can't find any valid WebView package we are now in a state where
- // mAnyWebViewInstalled is false, so loading WebView will be blocked and we
- // should simply wait until we receive an intent declaring a new package was
- // installed.
- }
- } else {
- mLock.notifyAll();
- }
- }
- }
-
- private int validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo) {
- // Ensure the provider targets this framework release (or a later one).
- if (!UserPackage.hasCorrectTargetSdkVersion(packageInfo)) {
- return VALIDITY_INCORRECT_SDK_VERSION;
- }
- if (!versionCodeGE(packageInfo.getLongVersionCode(), getMinimumVersionCode())
- && !mSystemInterface.systemIsDebuggable()) {
- // Webview providers may be downgraded arbitrarily low, prevent that by enforcing
- // minimum version code. This check is only enforced for user builds.
- return VALIDITY_INCORRECT_VERSION_CODE;
- }
- if (!providerHasValidSignature(configInfo, packageInfo, mSystemInterface)) {
- return VALIDITY_INCORRECT_SIGNATURE;
- }
- if (WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) == null) {
- return VALIDITY_NO_LIBRARY_FLAG;
- }
- return VALIDITY_OK;
- }
-
- /**
- * Both versionCodes should be from a WebView provider package implemented by Chromium.
- * VersionCodes from other kinds of packages won't make any sense in this method.
- *
- * An introduction to Chromium versionCode scheme:
- * "BBBBPPPXX"
- * BBBB: 4 digit branch number. It monotonically increases over time.
- * PPP: patch number in the branch. It is padded with zeroes to the left. These three digits
- * may change their meaning in the future.
- * XX: Digits to differentiate different APK builds of the same source version.
- *
- * This method takes the "BBBB" of versionCodes and compare them.
- *
- * https://www.chromium.org/developers/version-numbers describes general Chromium versioning;
- * https://source.chromium.org/chromium/chromium/src/+/master:build/util/android_chrome_version.py
- * is the canonical source for how Chromium versionCodes are calculated.
- *
- * @return true if versionCode1 is higher than or equal to versionCode2.
- */
- private static boolean versionCodeGE(long versionCode1, long versionCode2) {
- long v1 = versionCode1 / 100000;
- long v2 = versionCode2 / 100000;
-
- return v1 >= v2;
- }
-
- /**
- * Gets the minimum version code allowed for a valid provider. It is the minimum versionCode
- * of all available-by-default WebView provider packages. If there is no such WebView provider
- * package on the system, then return -1, which means all positive versionCode WebView packages
- * are accepted.
- *
- * Note that this is a private method that handles a variable (mMinimumVersionCode) which is
- * shared between threads. Furthermore, this method does not hold mLock meaning that we must
- * take extra care to ensure this method is thread-safe.
- */
- private long getMinimumVersionCode() {
- if (mMinimumVersionCode > 0) {
- return mMinimumVersionCode;
- }
-
- long minimumVersionCode = -1;
- for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
- if (provider.availableByDefault) {
- try {
- long versionCode =
- mSystemInterface.getFactoryPackageVersion(provider.packageName);
- if (minimumVersionCode < 0 || versionCode < minimumVersionCode) {
- minimumVersionCode = versionCode;
- }
- } catch (NameNotFoundException e) {
- // Safe to ignore.
- }
- }
- }
-
- mMinimumVersionCode = minimumVersionCode;
- return mMinimumVersionCode;
- }
-
- private static boolean providerHasValidSignature(WebViewProviderInfo provider,
- PackageInfo packageInfo, SystemInterface systemInterface) {
- // Skip checking signatures on debuggable builds, for development purposes.
- if (systemInterface.systemIsDebuggable()) return true;
-
- // Allow system apps to be valid providers regardless of signature.
- if (packageInfo.applicationInfo.isSystemApp()) return true;
-
- // We don't support packages with multiple signatures.
- if (packageInfo.signatures.length != 1) return false;
-
- // If any of the declared signatures match the package signature, it's valid.
- for (Signature signature : provider.signatures) {
- if (signature.equals(packageInfo.signatures[0])) return true;
- }
-
- return false;
- }
-
- /**
- * Returns the only fallback provider in the set of given packages, or null if there is none.
- */
- private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) {
- for (WebViewProviderInfo provider : webviewPackages) {
- if (provider.isFallback) {
- return provider;
- }
- }
- return null;
- }
-
- @Override
- public boolean isMultiProcessEnabled() {
- int settingValue = mSystemInterface.getMultiProcessSetting();
- if (mSystemInterface.isMultiProcessDefaultEnabled()) {
- // Multiprocess should be enabled unless the user has turned it off manually.
- return settingValue > MULTIPROCESS_SETTING_OFF_VALUE;
- } else {
- // Multiprocess should not be enabled, unless the user has turned it on manually.
- return settingValue >= MULTIPROCESS_SETTING_ON_VALUE;
- }
- }
-
- @Override
- public void enableMultiProcess(boolean enable) {
- PackageInfo current = getCurrentWebViewPackage();
- mSystemInterface.setMultiProcessSetting(
- enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE);
- mSystemInterface.notifyZygote(enable);
- if (current != null) {
- mSystemInterface.killPackageDependents(current.packageName);
- }
- }
-
- /**
- * Dump the state of this Service.
- */
- @Override
- public void dumpState(PrintWriter pw) {
- pw.println("Current WebView Update Service state");
- pw.println(String.format(" Multiprocess enabled: %b", isMultiProcessEnabled()));
- synchronized (mLock) {
- if (mCurrentWebViewPackage == null) {
- pw.println(" Current WebView package is null");
- } else {
- pw.println(String.format(" Current WebView package (name, version): (%s, %s)",
- mCurrentWebViewPackage.packageName,
- mCurrentWebViewPackage.versionName));
- }
- pw.println(String.format(" Minimum targetSdkVersion: %d",
- UserPackage.MINIMUM_SUPPORTED_SDK));
- pw.println(String.format(" Minimum WebView version code: %d",
- mMinimumVersionCode));
- pw.println(String.format(" Number of relros started: %d",
- mNumRelroCreationsStarted));
- pw.println(String.format(" Number of relros finished: %d",
- mNumRelroCreationsFinished));
- pw.println(String.format(" WebView package dirty: %b", mWebViewPackageDirty));
- pw.println(String.format(" Any WebView package installed: %b",
- mAnyWebViewInstalled));
-
- try {
- PackageInfo preferredWebViewPackage = findPreferredWebViewPackage();
- pw.println(String.format(
- " Preferred WebView package (name, version): (%s, %s)",
- preferredWebViewPackage.packageName,
- preferredWebViewPackage.versionName));
- } catch (WebViewPackageMissingException e) {
- pw.println(String.format(" Preferred WebView package: none"));
- }
-
- dumpAllPackageInformationLocked(pw);
- }
- }
-
- private void dumpAllPackageInformationLocked(PrintWriter pw) {
- WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
- pw.println(" WebView packages:");
- for (WebViewProviderInfo provider : allProviders) {
- List<UserPackage> userPackages =
- mSystemInterface.getPackageInfoForProviderAllUsers(provider);
- PackageInfo systemUserPackageInfo =
- userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo();
- if (systemUserPackageInfo == null) {
- pw.println(String.format(" %s is NOT installed.", provider.packageName));
- continue;
- }
-
- int validity = validityResult(provider, systemUserPackageInfo);
- String packageDetails = String.format(
- "versionName: %s, versionCode: %d, targetSdkVersion: %d",
- systemUserPackageInfo.versionName,
- systemUserPackageInfo.getLongVersionCode(),
- systemUserPackageInfo.applicationInfo.targetSdkVersion);
- if (validity == VALIDITY_OK) {
- boolean installedForAllUsers = isInstalledAndEnabledForAllUsers(
- mSystemInterface.getPackageInfoForProviderAllUsers(provider));
- pw.println(String.format(
- " Valid package %s (%s) is %s installed/enabled for all users",
- systemUserPackageInfo.packageName,
- packageDetails,
- installedForAllUsers ? "" : "NOT"));
- } else {
- pw.println(String.format(" Invalid package %s (%s), reason: %s",
- systemUserPackageInfo.packageName,
- packageDetails,
- getInvalidityReason(validity)));
- }
- }
- }
-
- private static String getInvalidityReason(int invalidityReason) {
- switch (invalidityReason) {
- case VALIDITY_INCORRECT_SDK_VERSION:
- return "SDK version too low";
- case VALIDITY_INCORRECT_VERSION_CODE:
- return "Version code too low";
- case VALIDITY_INCORRECT_SIGNATURE:
- return "Incorrect signature";
- case VALIDITY_NO_LIBRARY_FLAG:
- return "No WebView-library manifest flag";
- default:
- return "Unexcepted validity-reason";
- }
- }
-}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index 307c15b..a5a02cd 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -66,7 +66,7 @@
*
* @hide
*/
-class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
+class WebViewUpdateServiceImpl2 {
private static final String TAG = WebViewUpdateServiceImpl2.class.getSimpleName();
private static class WebViewPackageMissingException extends Exception {
@@ -125,7 +125,6 @@
mDefaultProvider = defaultProvider;
}
- @Override
public void packageStateChanged(String packageName, int changedState, int userId) {
// We don't early out here in different cases where we could potentially early-out (e.g. if
// we receive PACKAGE_CHANGED for another user than the system user) since that would
@@ -216,7 +215,6 @@
mSystemInterface.enablePackageForAllUsers(mDefaultProvider.packageName, true);
}
- @Override
public void prepareWebViewInSystemServer() {
try {
boolean repairNeeded = true;
@@ -256,7 +254,6 @@
mSystemInterface.ensureZygoteStarted();
}
- @Override
public void handleNewUser(int userId) {
// The system user is always started at boot, and by that point we have already run one
// round of the package-changing logic (through prepareWebViewInSystemServer()), so early
@@ -265,7 +262,6 @@
handleUserChange();
}
- @Override
public void handleUserRemoved(int userId) {
handleUserChange();
}
@@ -280,7 +276,6 @@
updateCurrentWebViewPackage(null);
}
- @Override
public void notifyRelroCreationCompleted() {
synchronized (mLock) {
mNumRelroCreationsFinished++;
@@ -288,7 +283,6 @@
}
}
- @Override
public WebViewProviderResponse waitForAndGetProvider() {
PackageInfo webViewPackage = null;
final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
@@ -334,7 +328,6 @@
* replacing that provider it will not be in use directly, but will be used when the relros
* or the replacement are done).
*/
- @Override
public String changeProviderAndSetting(String newProviderName) {
PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
if (newPackage == null) return "";
@@ -430,7 +423,6 @@
}
/** Fetch only the currently valid WebView packages. */
- @Override
public WebViewProviderInfo[] getValidWebViewPackages() {
ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
WebViewProviderInfo[] providers =
@@ -445,7 +437,6 @@
* Returns the default WebView provider which should be first availableByDefault option in the
* system config.
*/
- @Override
public WebViewProviderInfo getDefaultWebViewPackage() {
return mDefaultProvider;
}
@@ -550,12 +541,10 @@
return true;
}
- @Override
public WebViewProviderInfo[] getWebViewPackages() {
return mSystemInterface.getWebViewPackages();
}
- @Override
public PackageInfo getCurrentWebViewPackage() {
synchronized (mLock) {
return mCurrentWebViewPackage;
@@ -708,20 +697,7 @@
return null;
}
- @Override
- public boolean isMultiProcessEnabled() {
- throw new IllegalStateException(
- "isMultiProcessEnabled shouldn't be called if update_service_v2 flag is set.");
- }
-
- @Override
- public void enableMultiProcess(boolean enable) {
- throw new IllegalStateException(
- "enableMultiProcess shouldn't be called if update_service_v2 flag is set.");
- }
-
/** Dump the state of this Service. */
- @Override
public void dumpState(PrintWriter pw) {
pw.println("Current WebView Update Service state");
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
deleted file mode 100644
index 1772ef9..0000000
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
+++ /dev/null
@@ -1,52 +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.webkit;
-
-import android.content.pm.PackageInfo;
-import android.webkit.WebViewProviderInfo;
-import android.webkit.WebViewProviderResponse;
-
-import java.io.PrintWriter;
-
-interface WebViewUpdateServiceInterface {
- void packageStateChanged(String packageName, int changedState, int userId);
-
- void handleNewUser(int userId);
-
- void handleUserRemoved(int userId);
-
- WebViewProviderInfo[] getWebViewPackages();
-
- void prepareWebViewInSystemServer();
-
- void notifyRelroCreationCompleted();
-
- WebViewProviderResponse waitForAndGetProvider();
-
- String changeProviderAndSetting(String newProviderName);
-
- WebViewProviderInfo[] getValidWebViewPackages();
-
- WebViewProviderInfo getDefaultWebViewPackage();
-
- PackageInfo getCurrentWebViewPackage();
-
- boolean isMultiProcessEnabled();
-
- void enableMultiProcess(boolean enable);
-
- void dumpState(PrintWriter pw);
-}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceShellCommand.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceShellCommand.java
deleted file mode 100644
index 7529c41..0000000
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceShellCommand.java
+++ /dev/null
@@ -1,103 +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 com.android.server.webkit;
-
-import android.os.RemoteException;
-import android.os.ShellCommand;
-import android.webkit.IWebViewUpdateService;
-
-import java.io.PrintWriter;
-
-class WebViewUpdateServiceShellCommand extends ShellCommand {
- final IWebViewUpdateService mInterface;
-
- WebViewUpdateServiceShellCommand(IWebViewUpdateService service) {
- mInterface = service;
- }
-
- @Override
- public int onCommand(String cmd) {
- if (cmd == null) {
- return handleDefaultCommands(cmd);
- }
-
- final PrintWriter pw = getOutPrintWriter();
- try {
- switch(cmd) {
- case "set-webview-implementation":
- return setWebViewImplementation();
- case "enable-multiprocess":
- return enableMultiProcess(true);
- case "disable-multiprocess":
- return enableMultiProcess(false);
- default:
- return handleDefaultCommands(cmd);
- }
- } catch (RemoteException e) {
- pw.println("Remote exception: " + e);
- }
- return -1;
- }
-
- private int setWebViewImplementation() throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
- String shellChosenPackage = getNextArg();
- if (shellChosenPackage == null) {
- pw.println("Failed to switch, no PACKAGE provided.");
- pw.println("");
- helpSetWebViewImplementation();
- return 1;
- }
- String newPackage = mInterface.changeProviderAndSetting(shellChosenPackage);
- if (shellChosenPackage.equals(newPackage)) {
- pw.println("Success");
- return 0;
- } else {
- pw.println(String.format(
- "Failed to switch to %s, the WebView implementation is now provided by %s.",
- shellChosenPackage, newPackage));
- return 1;
- }
- }
-
- private int enableMultiProcess(boolean enable) throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
- mInterface.enableMultiProcess(enable);
- pw.println("Success");
- return 0;
- }
-
- public void helpSetWebViewImplementation() {
- PrintWriter pw = getOutPrintWriter();
- pw.println(" set-webview-implementation PACKAGE");
- pw.println(" Set the WebView implementation to the specified package.");
- }
-
- @Override
- public void onHelp() {
- PrintWriter pw = getOutPrintWriter();
- pw.println("WebView updater commands:");
- pw.println(" help");
- pw.println(" Print this help text.");
- pw.println("");
- helpSetWebViewImplementation();
- pw.println(" enable-multiprocess");
- pw.println(" Enable multi-process mode for WebView");
- pw.println(" disable-multiprocess");
- pw.println(" Disable multi-process mode for WebView");
- pw.println();
- }
-}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 5c53c2a..2fe023e 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -107,6 +107,12 @@
mNavigationMonitor.onFocusWindowChanged(newFocus);
}
+ void onEmbeddedWindowGestureTransferred(@NonNull WindowState host) {
+ if (Flags.disallowAppProgressEmbeddedWindow()) {
+ mNavigationMonitor.onEmbeddedWindowGestureTransferred(host);
+ }
+ }
+
/**
* Set up the necessary leashes and build a {@link BackNavigationInfo} instance for an upcoming
* back gesture animation.
@@ -178,6 +184,9 @@
return null;
}
+ final ArrayList<EmbeddedWindowController.EmbeddedWindow> embeddedWindows = wmService
+ .mEmbeddedWindowController.getByHostWindow(window);
+
currentActivity = window.mActivityRecord;
currentTask = window.getTask();
if ((currentTask != null && !currentTask.isVisibleRequested())
@@ -199,11 +208,22 @@
infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback());
infoBuilder.setAnimationCallback(callbackInfo.isAnimationCallback());
infoBuilder.setTouchableRegion(window.getFrame());
- infoBuilder.setAppProgressAllowed((window.getAttrs().privateFlags
- & PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED) != 0);
if (currentTask != null) {
infoBuilder.setFocusedTaskId(currentTask.mTaskId);
}
+ boolean transferGestureToEmbedded = false;
+ if (Flags.disallowAppProgressEmbeddedWindow() && embeddedWindows != null) {
+ for (int i = embeddedWindows.size() - 1; i >= 0; --i) {
+ if (embeddedWindows.get(i).mGestureToEmbedded) {
+ transferGestureToEmbedded = true;
+ break;
+ }
+ }
+ }
+ final boolean canInterruptInView = (window.getAttrs().privateFlags
+ & PRIVATE_FLAG_APP_PROGRESS_GENERATION_ALLOWED) != 0;
+ infoBuilder.setAppProgressAllowed(canInterruptInView && !transferGestureToEmbedded
+ && callbackInfo.isAnimationCallback());
mNavigationMonitor.startMonitor(window, navigationObserver);
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, "
@@ -742,6 +762,20 @@
}
/**
+ * Notify focus window has transferred touch gesture to embedded window. Shell should pilfer
+ * pointers so embedded process won't receive motion event.
+ *
+ */
+ void onEmbeddedWindowGestureTransferred(@NonNull WindowState host) {
+ if (!isMonitorForRemote() || host != mNavigatingWindow) {
+ return;
+ }
+ final Bundle result = new Bundle();
+ result.putBoolean(BackNavigationInfo.KEY_TOUCH_GESTURE_TRANSFERRED, true);
+ mObserver.sendResult(result);
+ }
+
+ /**
* Notify an unexpected transition has happened during back navigation.
*/
private void onTransitionReadyWhileNavigate(ArrayList<WindowContainer> opening,
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index a462c67..1933408 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -52,7 +52,7 @@
import static com.android.window.flags.Flags.balRequireOptInSameUid;
import static com.android.window.flags.Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid;
import static com.android.window.flags.Flags.balShowToastsBlocked;
-import static com.android.window.flags.Flags.balStrictMode;
+import static com.android.window.flags.Flags.balStrictModeRo;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import static java.util.Objects.requireNonNull;
@@ -851,7 +851,7 @@
showToast("BAL blocked. goo.gle/android-bal");
}
BalVerdict verdict = statsLog(BalVerdict.BLOCK, state);
- if (balStrictMode()) {
+ if (balStrictModeRo()) {
String abortDebugMessage;
if (state.isPendingIntent()) {
abortDebugMessage =
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index cbe3d79..6f8c17a 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -402,6 +402,7 @@
|| first.modeId != second.modeId
|| first.renderFrameRate != second.renderFrameRate
|| first.hasArrSupport != second.hasArrSupport
+ || !Objects.equals(first.frameRateCategoryRate, second.frameRateCategoryRate)
|| first.defaultModeId != second.defaultModeId
|| first.userPreferredModeId != second.userPreferredModeId
|| !Arrays.equals(first.supportedModes, second.supportedModes)
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 5ac4cf8..907d0dc 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -39,6 +39,8 @@
import com.android.internal.protolog.ProtoLog;
import com.android.server.input.InputManagerService;
+import java.util.ArrayList;
+
/**
* Keeps track of embedded windows.
*
@@ -146,6 +148,20 @@
return mWindowsByWindowToken.get(windowToken);
}
+ @Nullable ArrayList<EmbeddedWindow> getByHostWindow(WindowState host) {
+ ArrayList<EmbeddedWindow> windows = null;
+ for (int i = mWindows.size() - 1; i >= 0; i--) {
+ final EmbeddedWindow ew = mWindows.valueAt(i);
+ if (ew.mHostWindowState == host) {
+ if (windows == null) {
+ windows = new ArrayList<>();
+ }
+ windows.add(ew);
+ }
+ }
+ return windows;
+ }
+
private boolean isValidTouchGestureParams(WindowState hostWindowState,
EmbeddedWindow embeddedWindow) {
if (embeddedWindow == null) {
@@ -191,8 +207,12 @@
throw new SecurityException(
"Transfer request must originate from owner of transferFromToken");
}
- return mInputManagerService.transferTouchGesture(ew.getInputChannelToken(),
- transferToHostWindowState.mInputChannelToken);
+ final boolean didTransfer = mInputManagerService.transferTouchGesture(
+ ew.getInputChannelToken(), transferToHostWindowState.mInputChannelToken);
+ if (didTransfer) {
+ ew.mGestureToEmbedded = false;
+ }
+ return didTransfer;
}
boolean transferToEmbedded(int callingUid, WindowState hostWindowState,
@@ -205,8 +225,15 @@
throw new SecurityException(
"Transfer request must originate from owner of transferFromToken");
}
- return mInputManagerService.transferTouchGesture(hostWindowState.mInputChannelToken,
+ final boolean didTransfer = mInputManagerService.transferTouchGesture(
+ hostWindowState.mInputChannelToken,
ew.getInputChannelToken());
+ if (didTransfer) {
+ ew.mGestureToEmbedded = true;
+ mAtmService.mBackNavigationController.onEmbeddedWindowGestureTransferred(
+ hostWindowState);
+ }
+ return didTransfer;
}
static class EmbeddedWindow implements InputTarget {
@@ -235,6 +262,9 @@
// the host window.
private @WindowInsets.Type.InsetsType int mRequestedVisibleTypes = 0;
+ /** Whether the gesture is transferred to embedded window. */
+ boolean mGestureToEmbedded = false;
+
/**
* @param session calling session to check ownership of the window
* @param clientToken client token used to clean up the map if the embedding process dies
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
index 15c9b9f..a4546ae 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt
@@ -297,6 +297,7 @@
private const val MASK_ANY_FIXED =
PermissionFlags.USER_SET or
+ PermissionFlags.ONE_TIME or
PermissionFlags.USER_FIXED or
PermissionFlags.POLICY_FIXED or
PermissionFlags.SYSTEM_FIXED
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index d4b57f1..f439770 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -26,6 +26,8 @@
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.util.ArraySet
import android.util.SparseArray
import android.util.SparseIntArray
@@ -47,14 +49,19 @@
import com.android.server.pm.pkg.AndroidPackage
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
+import org.junit.Rule
import java.security.KeyPairGenerator
import java.security.PublicKey
import java.util.UUID
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
+@EnableFlags(android.content.pm.Flags.FLAG_INCLUDE_FEATURE_FLAGS_IN_PACKAGE_CACHER)
class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, PackageImpl::class) {
+ @get:Rule
+ val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
companion object {
private val TEST_UUID = UUID.fromString("57554103-df3e-4475-ae7a-8feba49353ac")
}
@@ -93,6 +100,8 @@
"getUsesOptionalLibrariesSorted",
"getUsesSdkLibrariesSorted",
"getUsesStaticLibrariesSorted",
+ "readFeatureFlagState",
+ "writeFeatureFlagState",
// Tested through setting minor/major manually
"setLongVersionCode",
"getLongVersionCode",
@@ -149,6 +158,10 @@
"isSystem",
"isSystemExt",
"isVendor",
+
+ // Tested through addFeatureFlag
+ "addFeatureFlag",
+ "getFeatureFlagState",
)
override val baseParams = listOf(
@@ -613,6 +626,9 @@
.setSplitClassLoaderName(1, "testSplitClassLoaderNameOne")
.addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1"), true)
.addUsesStaticLibrary("testStatic", 3L, arrayOf("testCertDigest2"))
+ .addFeatureFlag("testFlag1", null)
+ .addFeatureFlag("testFlag2", true)
+ .addFeatureFlag("testFlag3", false)
override fun finalizeObject(parcelable: Parcelable) {
(parcelable as PackageImpl).hideAsParsed().hideAsFinal()
@@ -673,6 +689,12 @@
.containsExactly("testCertDigest2")
expect.that(after.storageUuid).isEqualTo(TEST_UUID)
+
+ expect.that(after.featureFlagState).containsExactlyEntriesIn(mapOf(
+ "testFlag1" to null,
+ "testFlag2" to true,
+ "testFlag3" to false,
+ ))
}
private fun testKey() = KeyPairGenerator.getInstance("RSA")
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
index c2c67e6..e04aeec 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
@@ -44,10 +44,13 @@
import android.content.ComponentName;
import android.content.Context;
import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
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 android.provider.DeviceConfig;
import android.util.DebugUtils;
@@ -74,6 +77,8 @@
private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
private static final int SOURCE_USER_ID = 0;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private ThermalStatusRestriction mThermalStatusRestriction;
private PowerManager.OnThermalStatusChangedListener mStatusChangedListener;
@@ -427,6 +432,7 @@
*/
@Test
@RequiresFlagsEnabled(FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS)
+ @DisableFlags(android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND)
public void testIsJobRestrictedBiasFgs_flagThermalRestrictionsToFgsJobsEnabled() {
JobStatusContainer jc =
new JobStatusContainer("testIsJobRestrictedBiasFgs", mJobSchedulerService);
@@ -508,6 +514,91 @@
}
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS)
+ @EnableFlags(android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND)
+ public void testIsJobRestrictedBiasFgs_flagThermalRestrictionsToFgsJobsEnabled_ignoreIWF() {
+ JobStatusContainer jc =
+ new JobStatusContainer("testIsJobRestrictedBiasFgs", mJobSchedulerService);
+ int jobBias = JobInfo.BIAS_FOREGROUND_SERVICE;
+ for (int thermalStatus : jc.thermalStatuses) {
+ String msg = debugTag(jobBias, thermalStatus);
+ mStatusChangedListener.onThermalStatusChanged(thermalStatus);
+ if (thermalStatus >= THERMAL_STATUS_SEVERE) {
+ // Full restrictions on all jobs
+ assertTrue(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ej, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ejRetried, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ejRunning, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ui, jobBias));
+ assertTrue(msg, isJobRestricted(jc.uiRetried, jobBias));
+ assertTrue(msg, isJobRestricted(jc.uiRunning, jobBias));
+ assertTrue(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+ } else if (thermalStatus >= THERMAL_STATUS_MODERATE) {
+ // No restrictions on user related jobs
+ assertFalse(msg, isJobRestricted(jc.ui, jobBias));
+ assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias));
+ assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias));
+ assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+ // Some restrictions on expedited jobs
+ assertFalse(msg, isJobRestricted(jc.ej, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ejRetried, jobBias));
+ assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias));
+ assertTrue(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+ // Some restrictions on high priority jobs
+ assertTrue(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+ // Full restructions on important while foreground jobs as
+ // the important while foreground flag is ignored.
+ assertTrue(isJobRestricted(jc.importantWhileForeground, jobBias));
+ assertTrue(isJobRestricted(jc.importantWhileForegroundRunning, jobBias));
+ assertTrue(isJobRestricted(jc.importantWhileForegroundRunningLong, jobBias));
+ // Full restriction on default priority jobs
+ assertTrue(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+ // Full restriction on low priority jobs
+ assertTrue(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+ assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+ // Full restriction on min priority jobs
+ assertTrue(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+ } else {
+ // thermalStatus < THERMAL_STATUS_MODERATE
+ // No restrictions on any job type
+ assertFalse(msg, isJobRestricted(jc.ui, jobBias));
+ assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias));
+ assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias));
+ assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias));
+ assertFalse(msg, isJobRestricted(jc.ej, jobBias));
+ assertFalse(msg, isJobRestricted(jc.ejDowngraded, jobBias));
+ assertFalse(msg, isJobRestricted(jc.ejRetried, jobBias));
+ assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias));
+ assertFalse(msg, isJobRestricted(jc.ejRunningLong, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobHighPriority, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias));
+ assertFalse(msg, isJobRestricted(jc.importantWhileForeground, jobBias));
+ assertFalse(msg, isJobRestricted(jc.importantWhileForegroundRunning, jobBias));
+ assertFalse(msg, isJobRestricted(jc.importantWhileForegroundRunningLong, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobDefaultPriority, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobLowPriority, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias));
+ assertFalse(msg, isJobRestricted(jc.jobMinPriority, jobBias));
+ }
+ }
+ }
+
/**
* Test {@link JobSchedulerService#isJobRestricted(JobStatus)} when Job Bias is less than
* Foreground Service and all Thermal states.
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java
index 574f369..ae404cf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java
@@ -18,6 +18,7 @@
import static com.android.server.pm.BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY;
import static com.android.server.pm.BackgroundInstallControlCallbackHelper.FLAGGED_USER_ID_KEY;
+import static com.android.server.pm.BackgroundInstallControlCallbackHelper.INSTALL_EVENT_TYPE_KEY;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.after;
@@ -84,12 +85,18 @@
int testUserId = 1;
mCallbackHelper.registerBackgroundInstallCallback(mCallback);
- mCallbackHelper.notifyAllCallbacks(testUserId, testPackageName);
+ mCallbackHelper.notifyAllCallbacks(
+ testUserId,
+ testPackageName,
+ BackgroundInstallControlService.INSTALL_EVENT_TYPE_INSTALL);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mCallback, after(1000).times(1)).sendResult(bundleCaptor.capture());
Bundle receivedBundle = bundleCaptor.getValue();
assertEquals(testPackageName, receivedBundle.getString(FLAGGED_PACKAGE_NAME_KEY));
assertEquals(testUserId, receivedBundle.getInt(FLAGGED_USER_ID_KEY));
+ assertEquals(
+ BackgroundInstallControlService.INSTALL_EVENT_TYPE_INSTALL,
+ receivedBundle.getInt(INSTALL_EVENT_TYPE_KEY));
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
index bf946a1..3d0c637 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java
@@ -103,6 +103,7 @@
assumeTrue(UserManager.supportsMultipleUsers());
AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+
final int fgUserId = mSpiedContext.getUserId();
final int bgUserUid = user.id * 100000;
doReturn(UserHandle.of(fgUserId)).when(mSpiedContext).getUser();
@@ -209,6 +210,28 @@
eq(UserHandle.of(fgUserId)));
}
+ @Test
+ public void testOnAudioFocusGrant_alarmOnProfileOfForegroundUser_foregroundUserNotNotified() {
+ assumeTrue(UserManager.supportsMultipleUsers());
+ final int fgUserId = mSpiedContext.getUserId();
+ UserInfo fgUserProfile = createProfileForUser("Background profile",
+ UserManager.USER_TYPE_PROFILE_MANAGED, fgUserId, null);
+ assumeTrue("Cannot add a profile", fgUserProfile != null);
+ int fgUserProfileUid = fgUserProfile.id * 100_000;
+
+ AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build();
+ AudioFocusInfo afi = new AudioFocusInfo(aa, fgUserProfileUid, "", "",
+ AudioManager.AUDIOFOCUS_GAIN, 0, 0, Build.VERSION.SDK_INT);
+
+ mBackgroundUserSoundNotifier.getAudioPolicyFocusListener()
+ .onAudioFocusGrant(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+
+ verify(mNotificationManager, never())
+ .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()),
+ eq(afi.getClientUid()), any(Notification.class),
+ eq(UserHandle.of(fgUserId)));
+ }
+
@Test
public void testCreateNotification_UserSwitcherEnabled_bothActionsAvailable() {
@@ -327,6 +350,17 @@
}
return user;
}
+
+ private UserInfo createProfileForUser(String name, String userType, int userHandle,
+ String[] disallowedPackages) {
+ UserInfo profile = mUserManager.createProfileForUser(
+ name, userType, 0, userHandle, disallowedPackages);
+ if (profile != null) {
+ mUsersToRemove.add(profile.id);
+ }
+ return profile;
+ }
+
private void removeUser(int userId) {
mUserManager.removeUser(userId);
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 7481fc8..2edde9b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -1615,7 +1615,8 @@
List.of(tile)
);
- assertThat(mA11yms.getCurrentUserState().getA11yQsTargets()).doesNotContain(tile);
+ assertThat(mA11yms.getCurrentUserState()
+ .getShortcutTargetsLocked(QUICK_SETTINGS)).doesNotContain(tile.flattenToString());
}
@Test
@@ -1636,7 +1637,7 @@
List.of(tile)
);
- assertThat(mA11yms.getCurrentUserState().getA11yQsTargets())
+ assertThat(mA11yms.getCurrentUserState().getShortcutTargetsLocked(QUICK_SETTINGS))
.contains(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString());
}
@@ -1656,7 +1657,7 @@
);
assertThat(
- mA11yms.getCurrentUserState().getA11yQsTargets()
+ mA11yms.getCurrentUserState().getShortcutTargetsLocked(QUICK_SETTINGS)
).containsExactlyElementsIn(List.of(
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(),
AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString())
@@ -1676,7 +1677,7 @@
);
assertThat(
- mA11yms.getCurrentUserState().getA11yQsTargets()
+ mA11yms.getCurrentUserState().getShortcutTargetsLocked(QUICK_SETTINGS)
).doesNotContain(
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString());
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 627b5e3..8c35925 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -29,6 +29,7 @@
import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.ALL;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
@@ -72,6 +73,7 @@
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
+import com.android.internal.accessibility.util.ShortcutUtils;
import com.android.internal.util.test.FakeSettingsProvider;
import org.junit.After;
@@ -454,17 +456,7 @@
mUserState.updateShortcutTargetsLocked(newTargets, QUICK_SETTINGS);
- assertThat(mUserState.getA11yQsTargets()).isEqualTo(newTargets);
- }
-
- @Test
- public void getA11yQsTargets_returnsCopiedData() {
- updateShortcutTargetsLocked_quickSettings_valueUpdated();
-
- Set<String> targets = mUserState.getA11yQsTargets();
- targets.clear();
-
- assertThat(mUserState.getA11yQsTargets()).isNotEmpty();
+ assertThat(mUserState.getShortcutTargetsLocked(QUICK_SETTINGS)).isEqualTo(newTargets);
}
@Test
@@ -539,6 +531,31 @@
assertThat(mUserState.isShortcutMagnificationEnabledLocked()).isFalse();
}
+ @Test
+ public void getShortcutTargetsLocked_returnsCorrectTargets() {
+ for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
+ if (((TRIPLETAP | TWOFINGER_DOUBLETAP) & shortcutType) == shortcutType) {
+ continue;
+ }
+ Set<String> expectedSet = Set.of(ShortcutUtils.convertToKey(shortcutType));
+ mUserState.updateShortcutTargetsLocked(expectedSet, shortcutType);
+
+ assertThat(mUserState.getShortcutTargetsLocked(shortcutType))
+ .containsExactlyElementsIn(expectedSet);
+ }
+ }
+
+ @Test
+ public void getShortcutTargetsLocked_returnsCopiedData() {
+ Set<String> set = Set.of("FOO", "BAR");
+ mUserState.updateShortcutTargetsLocked(set, SOFTWARE);
+
+ Set<String> targets = mUserState.getShortcutTargetsLocked(ALL);
+ targets.clear();
+
+ assertThat(mUserState.getShortcutTargetsLocked(ALL)).isNotEmpty();
+ }
+
private int getSecureIntForUser(String key, int userId) {
return Settings.Secure.getIntForUser(mMockResolver, key, -1, userId);
}
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 fa94821..3c27af4 100644
--- a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
@@ -34,6 +34,8 @@
import com.google.common.truth.Expect;
+import com.android.server.utils.EventLogger;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -67,6 +69,8 @@
PlayerBase.PlayerIdCard mMockPlayerIdCard;
@Mock
AudioPlaybackConfiguration mMockPlaybackConfiguration;
+ @Mock
+ EventLogger mMockEventLogger;
@Rule
public final Expect expect = Expect.create();
@@ -193,7 +197,7 @@
String packageName, int uid, int flags) {
MediaFocusControl mfc = new MediaFocusControl(mContext, null);
return new FocusRequester(aa, AudioManager.AUDIOFOCUS_GAIN, flags, null, null, clientId,
- null, packageName, uid, mfc, 1);
+ null, packageName, uid, mfc, 1, mMockEventLogger);
}
private PlayerBase.PlayerIdCard createPlayerIdCard(AudioAttributes aa, int playerType) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 2f7b8d2..4ef37b9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -69,6 +69,7 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.security.KeyStoreAuthorization;
import android.testing.TestableContext;
@@ -119,6 +120,7 @@
@Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
@Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
@Mock private BiometricCameraManager mBiometricCameraManager;
+ @Mock private UserManager mUserManager;
@Mock private BiometricManager mBiometricManager;
private Random mRandom;
@@ -846,7 +848,8 @@
TEST_PACKAGE,
checkDevicePolicyManager,
mContext,
- mBiometricCameraManager);
+ mBiometricCameraManager,
+ mUserManager);
}
private AuthSession createAuthSession(List<BiometricSensor> sensors,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index 85e45f4..510dd4d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -39,6 +39,7 @@
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.PromptInfo;
import android.os.RemoteException;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -64,6 +65,8 @@
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
+ private static final int USER_ID = 0;
+ private static final int OWNER_ID = 10;
private static final int SENSOR_ID_FINGERPRINT = 0;
private static final int SENSOR_ID_FACE = 1;
private static final String TEST_PACKAGE_NAME = "PreAuthInfoTestPackage";
@@ -84,6 +87,8 @@
BiometricService.SettingObserver mSettingObserver;
@Mock
BiometricCameraManager mBiometricCameraManager;
+ @Mock
+ UserManager mUserManager;
@Before
public void setup() throws RemoteException {
@@ -118,9 +123,9 @@
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(sensor),
- 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(sensor), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.eligibleSensors).isEmpty();
}
@@ -134,9 +139,9 @@
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(sensor),
- 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(sensor), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(1);
}
@@ -151,9 +156,9 @@
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(sensor),
- 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(sensor), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(0);
}
@@ -171,9 +176,9 @@
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(faceSensor, fingerprintSensor),
- 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(faceSensor, fingerprintSensor), USER_ID,
+ promptInfo, TEST_PACKAGE_NAME, false /* checkDevicePolicyManager */, mContext,
+ mBiometricCameraManager, mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(0);
assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo(
@@ -191,9 +196,9 @@
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(faceSensor, fingerprintSensor),
- 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(faceSensor, fingerprintSensor), USER_ID,
+ promptInfo, TEST_PACKAGE_NAME, false /* checkDevicePolicyManager */, mContext,
+ mBiometricCameraManager, mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(1);
assertThat(preAuthInfo.eligibleSensors.get(0).modality).isEqualTo(TYPE_FINGERPRINT);
@@ -209,8 +214,9 @@
final PromptInfo promptInfo = new PromptInfo();
promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK);
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(sensor), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(1);
}
@@ -224,8 +230,9 @@
final PromptInfo promptInfo = new PromptInfo();
promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK);
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(0);
}
@@ -241,8 +248,9 @@
promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK
| BiometricManager.Authenticators.BIOMETRIC_STRONG);
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(sensor), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(1);
}
@@ -257,8 +265,9 @@
final PromptInfo promptInfo = new PromptInfo();
promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK);
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(sensor), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo(
BiometricManager.BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE);
@@ -279,9 +288,9 @@
promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(faceSensor, fingerprintSensor),
- 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(faceSensor, fingerprintSensor), USER_ID,
+ promptInfo, TEST_PACKAGE_NAME, false /* checkDevicePolicyManager */, mContext,
+ mBiometricCameraManager, mUserManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(0);
assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo(
@@ -299,11 +308,30 @@
promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK);
promptInfo.setNegativeButtonText(TEST_PACKAGE_NAME);
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
- mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+ mSettingObserver, List.of(sensor), USER_ID, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
assertThat(promptInfo.getNegativeButtonText()).isEqualTo(TEST_PACKAGE_NAME);
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_PRIVATE_SPACE_BP)
+ public void testCredentialOwnerIdAsUserId() throws Exception {
+ when(mUserManager.getCredentialOwnerProfile(USER_ID)).thenReturn(OWNER_ID);
+
+ final BiometricSensor sensor = getFaceSensor();
+ final PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setNegativeButtonText(TEST_PACKAGE_NAME);
+ final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor), USER_ID , promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager,
+ mUserManager);
+
+ assertThat(preAuthInfo.userId).isEqualTo(OWNER_ID);
+ assertThat(preAuthInfo.callingUserId).isEqualTo(USER_ID);
+ }
+
private BiometricSensor getFingerprintSensor() {
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT,
TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index 4f07380..26e2614 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -147,7 +147,7 @@
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
assertThat(mInterruptableOperation.startWithCookie(mOnStartCallback, cookie)).isTrue();
- assertThat(mInterruptableOperation.startWithCookie(mOnStartCallback, cookie)).isFalse();
+ assertThat(mInterruptableOperation.startWithCookie(mOnStartCallback, cookie)).isTrue();
}
@Test
@@ -201,7 +201,7 @@
when(mInterruptableClientMonitor.getFreshDaemon()).thenReturn(mHal);
assertThat(mInterruptableOperation.start(mOnStartCallback)).isTrue();
- assertThat(mInterruptableOperation.start(mOnStartCallback)).isFalse();
+ assertThat(mInterruptableOperation.start(mOnStartCallback)).isTrue();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 3360e1d..c741c6c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -37,6 +37,7 @@
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.test.TestLooper;
@@ -2676,6 +2677,60 @@
}
@Test
+ public void onActiveSourceLost_oneTouchPlay_noStandbyAfterTimeout() {
+ mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setStringValue(
+ HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
+ HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW);
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mPowerManager.setInteractive(true);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage activeSourceFromPlayback =
+ HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+
+ assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(activeSourceFromTv))
+ .isEqualTo(Constants.HANDLED);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(ADDR_TV);
+ mTestLooper.dispatchAll();
+
+ // Pop-up is triggered.
+ mTestLooper.moveTimeForward(POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ mTestLooper.dispatchAll();
+ // RequestActiveSourceAction is triggered and TV confirms active source.
+ mNativeWrapper.onCecMessage(activeSourceFromTv);
+ mTestLooper.dispatchAll();
+
+ assertThat(mIsOnActiveSourceLostPopupActive).isTrue();
+ mHdmiControlService.oneTouchPlay(new IHdmiControlCallback() {
+ @Override
+ public void onComplete(int result) throws RemoteException {
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ });
+ mTestLooper.dispatchAll();
+
+ assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
+ assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress)
+ .isEqualTo(mPlaybackLogicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress)
+ .isEqualTo(mPlaybackPhysicalAddress);
+ mTestLooper.moveTimeForward(STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPowerManager.isInteractive()).isTrue();
+ }
+
+ @Test
public void handleRoutingChange_addressNotAllocated_removeActiveSourceAction() {
long allocationDelay = TimeUnit.SECONDS.toMillis(60);
mHdmiCecLocalDevicePlayback.mPlaybackDeviceActionOnRoutingControl =
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index a222ef0..5852af7 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -15,6 +15,8 @@
*/
package com.android.server.tv.tunerresourcemanager;
+import static android.media.tv.flags.Flags.FLAG_SET_RESOURCE_HOLDER_RETAIN;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
@@ -39,7 +41,9 @@
import android.media.tv.tunerresourcemanager.TunerLnbRequest;
import android.media.tv.tunerresourcemanager.TunerResourceManager;
import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -47,6 +51,7 @@
import com.google.common.truth.Correspondence;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -70,6 +75,8 @@
private TunerResourceManagerService mTunerResourceManagerService;
private boolean mIsForeground;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private final class TunerClient extends IResourcesReclaimListener.Stub {
int[] mClientId;
ClientProfile mProfile;
@@ -125,19 +132,6 @@
}
}
- private static final class TestResourcesReclaimListener extends IResourcesReclaimListener.Stub {
- boolean mReclaimed;
-
- @Override
- public void onReclaimResources() {
- mReclaimed = true;
- }
-
- public boolean isReclaimed() {
- return mReclaimed;
- }
- }
-
// A correspondence to compare a FrontendResource and a TunerFrontendInfo.
private static final Correspondence<FrontendResource, TunerFrontendInfo> FR_TFI_COMPARE =
Correspondence.from((FrontendResource actual, TunerFrontendInfo expected) -> {
@@ -485,6 +479,62 @@
}
@Test
+ @EnableFlags({FLAG_SET_RESOURCE_HOLDER_RETAIN})
+ public void requestFrontendTest_NoFrontendAvailable_RequestWithEqualPriority()
+ throws RemoteException {
+ // Register clients
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ client0.register(
+ "0" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+ client1.register(
+ "1" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+
+ // Init frontend resource.
+ TunerFrontendInfo[] infos = new TunerFrontendInfo[1];
+ infos[0] =
+ tunerFrontendInfo(0 /*handle*/, FrontendSettings.TYPE_DVBT, 1 /*exclusiveGroupId*/);
+ mTunerResourceManagerService.setFrontendInfoListInternal(infos);
+
+ // client0 requests for 1 frontend
+ TunerFrontendRequest request =
+ tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
+ long[] frontendHandle = new long[1];
+ assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendHandle))
+ .isTrue();
+ assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
+ assertThat(client0.getProfile().getInUseFrontendHandles())
+ .isEqualTo(new HashSet<Long>(Arrays.asList(infos[0].handle)));
+
+ // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
+ // (client0) to maintain ownership such as requester will not get the resources.
+ client1.getProfile().setResourceHolderRetain(true);
+
+ request = tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
+ assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendHandle))
+ .isFalse();
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle).isInUse())
+ .isTrue();
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
+ .getOwnerClientId())
+ .isEqualTo(client0.getId());
+ assertThat(client0.isReclaimed()).isFalse();
+
+ // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+ // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
+ // resources.
+ client1.getProfile().setResourceHolderRetain(false);
+
+ assertThat(mTunerResourceManagerService.requestFrontendInternal(request, frontendHandle))
+ .isTrue();
+ assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
+ assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
+ .getOwnerClientId())
+ .isEqualTo(client1.getId());
+ assertThat(client0.isReclaimed()).isTrue();
+ }
+
+ @Test
public void releaseFrontendTest_UnderTheSameExclusiveGroup() throws RemoteException {
// Register clients
TunerClient client0 = new TunerClient();
@@ -565,6 +615,74 @@
}
@Test
+ @EnableFlags({FLAG_SET_RESOURCE_HOLDER_RETAIN})
+ public void requestCasTest_NoCasAvailable_RequestWithEqualPriority() throws RemoteException {
+ // Register clients
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ client0.register(
+ "0" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+ client1.register(
+ "1" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+
+ // Init cas resources.
+ mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
+
+ CasSessionRequest request = casSessionRequest(client0.getId(), 1 /*casSystemId*/);
+ long[] casSessionHandle = new long[1];
+
+ // client0 requests for 2 cas sessions.
+ assertThat(
+ mTunerResourceManagerService.requestCasSessionInternal(request, casSessionHandle))
+ .isTrue();
+ assertThat(
+ mTunerResourceManagerService.requestCasSessionInternal(request, casSessionHandle))
+ .isTrue();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+ .isEqualTo(1);
+ assertThat(client0.getProfile().getInUseCasSystemId()).isEqualTo(1);
+ assertThat(mTunerResourceManagerService.getCasResource(1).getOwnerClientIds())
+ .isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId())));
+ assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
+
+ // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
+ // to maintain ownership such as requester (client1) will not get the resources.
+ client1.getProfile().setResourceHolderRetain(true);
+
+ request = casSessionRequest(client1.getId(), 1);
+ assertThat(
+ mTunerResourceManagerService.requestCasSessionInternal(request, casSessionHandle))
+ .isFalse();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+ .isEqualTo(-1);
+ assertThat(client0.getProfile().getInUseCasSystemId()).isEqualTo(1);
+ assertThat(client1.getProfile().getInUseCasSystemId())
+ .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+ assertThat(mTunerResourceManagerService.getCasResource(1).getOwnerClientIds())
+ .isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId())));
+ assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
+ assertThat(client0.isReclaimed()).isFalse();
+
+ // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+ // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
+ // resources.
+ client1.getProfile().setResourceHolderRetain(false);
+
+ assertThat(
+ mTunerResourceManagerService.requestCasSessionInternal(request, casSessionHandle))
+ .isTrue();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+ .isEqualTo(1);
+ assertThat(client0.getProfile().getInUseCasSystemId())
+ .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+ assertThat(client1.getProfile().getInUseCasSystemId()).isEqualTo(1);
+ assertThat(mTunerResourceManagerService.getCasResource(1).getOwnerClientIds())
+ .isEqualTo(new HashSet<Integer>(Arrays.asList(client1.getId())));
+ assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
+ assertThat(client0.isReclaimed()).isTrue();
+ }
+
+ @Test
public void requestCiCamTest_NoCiCamAvailable_RequestWithHigherPriority()
throws RemoteException {
// Register clients
@@ -612,6 +730,71 @@
}
@Test
+ @EnableFlags({FLAG_SET_RESOURCE_HOLDER_RETAIN})
+ public void requestCiCamTest_NoCiCamAvailable_RequestWithEqualPriority()
+ throws RemoteException {
+ // Register clients
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ client0.register(
+ "0" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+ client1.register(
+ "1" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+
+ // Init cicam/cas resources.
+ mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
+
+ TunerCiCamRequest request = tunerCiCamRequest(client0.getId(), 1 /*ciCamId*/);
+ long[] ciCamHandle = new long[1];
+
+ // client0 request for 2 ciCam sessions.
+ assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle))
+ .isTrue();
+ assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle))
+ .isTrue();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0]))
+ .isEqualTo(1);
+ assertThat(client0.getProfile().getInUseCiCamId()).isEqualTo(1);
+ assertThat(mTunerResourceManagerService.getCiCamResource(1).getOwnerClientIds())
+ .isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId())));
+ assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue();
+
+ // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
+ // (client0) to maintain ownership such as requester will not get the resources.
+ client1.getProfile().setResourceHolderRetain(true);
+
+ request = tunerCiCamRequest(client1.getId(), 1);
+ assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle))
+ .isFalse();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0]))
+ .isEqualTo(-1);
+ assertThat(client0.getProfile().getInUseCiCamId()).isEqualTo(1);
+ assertThat(client1.getProfile().getInUseCiCamId())
+ .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+ assertThat(mTunerResourceManagerService.getCiCamResource(1).getOwnerClientIds())
+ .isEqualTo(new HashSet<Integer>(Arrays.asList(client0.getId())));
+ assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue();
+ assertThat(client0.isReclaimed()).isFalse();
+
+ // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+ // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
+ // resources.
+ client1.getProfile().setResourceHolderRetain(false);
+
+ assertThat(mTunerResourceManagerService.requestCiCamInternal(request, ciCamHandle))
+ .isTrue();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0]))
+ .isEqualTo(1);
+ assertThat(client0.getProfile().getInUseCiCamId())
+ .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+ assertThat(client1.getProfile().getInUseCiCamId()).isEqualTo(1);
+ assertThat(mTunerResourceManagerService.getCiCamResource(1).getOwnerClientIds())
+ .isEqualTo(new HashSet<Integer>(Arrays.asList(client1.getId())));
+ assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isFalse();
+ assertThat(client0.isReclaimed()).isTrue();
+ }
+
+ @Test
public void releaseCasTest() throws RemoteException {
// Register clients
TunerClient client0 = new TunerClient();
@@ -721,6 +904,59 @@
}
@Test
+ @EnableFlags({FLAG_SET_RESOURCE_HOLDER_RETAIN})
+ public void requestLnbTest_NoLnbAvailable_RequestWithEqualPriority() throws RemoteException {
+ // Register clients
+ TunerClient client0 = new TunerClient();
+ TunerClient client1 = new TunerClient();
+ client0.register(
+ "0" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+ client1.register(
+ "1" /*sessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+
+ // Init lnb resources.
+ long[] lnbHandles = {1};
+ mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles);
+
+ // client0 requests 1 lnb
+ TunerLnbRequest request = new TunerLnbRequest();
+ request.clientId = client0.getId();
+ long[] lnbHandle = new long[1];
+ assertThat(mTunerResourceManagerService.requestLnbInternal(request, lnbHandle)).isTrue();
+ assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]);
+ assertThat(client0.getProfile().getInUseLnbHandles())
+ .isEqualTo(new HashSet<Long>(Arrays.asList(lnbHandles[0])));
+
+ // setResourceHolderRetain sets mResourceHolderRetain to true to allow the Resource Holder
+ // (client0) to maintain ownership such as requester will not get the resources.
+ client1.getProfile().setResourceHolderRetain(true);
+
+ request = new TunerLnbRequest();
+ request.clientId = client1.getId();
+
+ assertThat(mTunerResourceManagerService.requestLnbInternal(request, lnbHandle)).isFalse();
+ assertThat(lnbHandle[0]).isNotEqualTo(lnbHandles[0]);
+ assertThat(mTunerResourceManagerService.getLnbResource(lnbHandles[0]).isInUse()).isTrue();
+ assertThat(mTunerResourceManagerService.getLnbResource(lnbHandles[0]).getOwnerClientId())
+ .isEqualTo(client0.getId());
+ assertThat(client0.isReclaimed()).isFalse();
+ assertThat(client1.getProfile().getInUseLnbHandles().size()).isEqualTo(0);
+
+ // setResourceHolderRetain sets mResourceHolderRetain to false to allow the Resource
+ // Challenger (client1) to acquire the resource and Resource Holder loses ownership of the
+ // resources.
+ client1.getProfile().setResourceHolderRetain(false);
+
+ assertThat(mTunerResourceManagerService.requestLnbInternal(request, lnbHandle)).isTrue();
+ assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]);
+ assertThat(mTunerResourceManagerService.getLnbResource(lnbHandles[0]).isInUse()).isTrue();
+ assertThat(mTunerResourceManagerService.getLnbResource(lnbHandles[0]).getOwnerClientId())
+ .isEqualTo(client1.getId());
+ assertThat(client0.isReclaimed()).isTrue();
+ assertThat(client0.getProfile().getInUseLnbHandles().size()).isEqualTo(0);
+ }
+
+ @Test
public void releaseLnbTest() throws RemoteException {
// Register clients
TunerClient client0 = new TunerClient();
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index def3355..aee9f0f 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -36,18 +36,15 @@
Map<String, Map<Integer, PackageInfo>> mPackages = new HashMap();
private final int mNumRelros;
private final boolean mIsDebuggable;
- private int mMultiProcessSetting;
- private final boolean mMultiProcessDefault;
public static final int PRIMARY_USER_ID = 0;
- public TestSystemImpl(WebViewProviderInfo[] packageConfigs, int numRelros, boolean isDebuggable,
- boolean multiProcessDefault) {
+ public TestSystemImpl(WebViewProviderInfo[] packageConfigs, int numRelros,
+ boolean isDebuggable) {
mPackageConfigs = packageConfigs;
mNumRelros = numRelros;
mIsDebuggable = isDebuggable;
mUsers.add(PRIMARY_USER_ID);
- mMultiProcessDefault = multiProcessDefault;
}
public void addUser(int userId) {
@@ -181,26 +178,8 @@
}
@Override
- public int getMultiProcessSetting() {
- return mMultiProcessSetting;
- }
-
- @Override
- public void setMultiProcessSetting(int value) {
- mMultiProcessSetting = value;
- }
-
- @Override
- public void notifyZygote(boolean enableMultiProcess) {}
-
- @Override
public void ensureZygoteStarted() {}
@Override
- public boolean isMultiProcessDefaultEnabled() {
- return mMultiProcessDefault;
- }
-
- @Override
public void pinWebviewIfRequired(ApplicationInfo appInfo) {}
}
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 06479c8..42eb609 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -16,8 +16,6 @@
package com.android.server.webkit;
-import static android.webkit.Flags.updateServiceV2;
-
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -27,8 +25,6 @@
import android.content.pm.Signature;
import android.os.Build;
import android.os.Bundle;
-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.util.Base64;
@@ -62,7 +58,7 @@
public class WebViewUpdateServiceTest {
private final static String TAG = WebViewUpdateServiceTest.class.getSimpleName();
- private WebViewUpdateServiceInterface mWebViewUpdateServiceImpl;
+ private WebViewUpdateServiceImpl2 mWebViewUpdateServiceImpl;
private TestSystemImpl mTestSystemImpl;
private static final String WEBVIEW_LIBRARY_FLAG = "com.android.webview.WebViewLibrary";
@@ -77,38 +73,23 @@
}
private void setupWithPackages(WebViewProviderInfo[] packages) {
- setupWithAllParameters(packages, 1 /* numRelros */, true /* isDebuggable */,
- false /* multiProcessDefault */);
+ setupWithAllParameters(packages, 1 /* numRelros */, true /* isDebuggable */);
}
private void setupWithPackagesAndRelroCount(WebViewProviderInfo[] packages, int numRelros) {
- setupWithAllParameters(packages, numRelros, true /* isDebuggable */,
- false /* multiProcessDefault */);
+ setupWithAllParameters(packages, numRelros, true /* isDebuggable */);
}
private void setupWithPackagesNonDebuggable(WebViewProviderInfo[] packages) {
- setupWithAllParameters(packages, 1 /* numRelros */, false /* isDebuggable */,
- false /* multiProcessDefault */);
- }
-
- private void setupWithPackagesAndMultiProcess(WebViewProviderInfo[] packages,
- boolean multiProcessDefault) {
- setupWithAllParameters(packages, 1 /* numRelros */, true /* isDebuggable */,
- multiProcessDefault);
+ setupWithAllParameters(packages, 1 /* numRelros */, false /* isDebuggable */);
}
private void setupWithAllParameters(WebViewProviderInfo[] packages, int numRelros,
- boolean isDebuggable, boolean multiProcessDefault) {
- TestSystemImpl testing = new TestSystemImpl(packages, numRelros, isDebuggable,
- multiProcessDefault);
+ boolean isDebuggable) {
+ TestSystemImpl testing = new TestSystemImpl(packages, numRelros, isDebuggable);
mTestSystemImpl = Mockito.spy(testing);
- if (updateServiceV2()) {
- mWebViewUpdateServiceImpl =
- new WebViewUpdateServiceImpl2(mTestSystemImpl);
- } else {
- mWebViewUpdateServiceImpl =
- new WebViewUpdateServiceImpl(mTestSystemImpl);
- }
+ mWebViewUpdateServiceImpl =
+ new WebViewUpdateServiceImpl2(mTestSystemImpl);
}
private void setEnabledAndValidPackageInfos(WebViewProviderInfo[] providers) {
@@ -350,24 +331,6 @@
}
@Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- // If the flag is set, will throw an exception because of no available by default provider.
- public void testEmptyConfig() {
- WebViewProviderInfo[] packages = new WebViewProviderInfo[0];
- setupWithPackages(packages);
- setEnabledAndValidPackageInfos(packages);
-
- runWebViewBootPreparationOnMainSync();
-
- Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged(
- Matchers.anyObject());
-
- WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
- assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status);
- assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage());
- }
-
- @Test
public void testFailListingEmptyWebviewPackages() {
String singlePackage = "singlePackage";
WebViewProviderInfo[] packages = new WebViewProviderInfo[]{
@@ -554,73 +517,6 @@
}
}
- /**
- * Scenario for testing re-enabling a fallback package.
- */
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- public void testFallbackPackageEnabling() {
- String testPackage = "testFallback";
- WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(
- testPackage, "", true /* default available */, true /* fallback */, null)};
- setupWithPackages(packages);
- mTestSystemImpl.setPackageInfo(
- createPackageInfo(testPackage, false /* enabled */ , true /* valid */,
- true /* installed */));
-
- // Check that the boot time logic re-enables the fallback package.
- runWebViewBootPreparationOnMainSync();
- Mockito.verify(mTestSystemImpl).enablePackageForAllUsers(
- Mockito.eq(testPackage), Mockito.eq(true));
-
- // Fake the message about the enabling having changed the package state,
- // and check we now use that package.
- mWebViewUpdateServiceImpl.packageStateChanged(
- testPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID);
- checkPreparationPhasesForPackage(testPackage, 1);
- }
-
- /**
- * Scenario for installing primary package when secondary in use.
- * 1. Start with only secondary installed
- * 2. Install primary
- * 3. Primary should be used
- */
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- // If the flag is set, we don't automitally switch to secondary package unless it is
- // chosen directly.
- public void testInstallingPrimaryPackage() {
- String primaryPackage = "primary";
- String secondaryPackage = "secondary";
- WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(
- primaryPackage, "", true /* default available */, false /* fallback */, null),
- new WebViewProviderInfo(
- secondaryPackage, "", true /* default available */, false /* fallback */,
- null)};
- setupWithPackages(packages);
- mTestSystemImpl.setPackageInfo(
- createPackageInfo(secondaryPackage, true /* enabled */ , true /* valid */,
- true /* installed */));
-
- runWebViewBootPreparationOnMainSync();
- checkPreparationPhasesForPackage(secondaryPackage,
- 1 /* first preparation for this package*/);
-
- // Install primary package
- mTestSystemImpl.setPackageInfo(
- createPackageInfo(primaryPackage, true /* enabled */ , true /* valid */,
- true /* installed */));
- mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
- WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID);
-
- // Verify primary package used as provider, and secondary package killed
- checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation for this package*/);
- Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(secondaryPackage));
- }
-
@Test
public void testRemovingSecondarySelectsPrimarySingleUser() {
for (PackageRemovalType removalType : REMOVAL_TYPES) {
@@ -848,14 +744,6 @@
checkRecoverAfterFailListingWebviewPackages(true);
}
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- // If the flag is set, we don't automitally switch to second package unless it is chosen
- // directly.
- public void testRecoverFailedListingWebViewPackagesAddedPackage() {
- checkRecoverAfterFailListingWebviewPackages(false);
- }
-
/**
* Test that we can recover correctly from failing to list WebView packages.
* settingsChange: whether to fail during changeProviderAndSetting or packageStateChanged
@@ -1114,31 +1002,6 @@
}
}
- /**
- * Ensure that the update service does not use an uninstalled package even if it is the only
- * package available.
- */
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- // If the flag is set, we return the package even if it is not installed.
- public void testWithSingleUninstalledPackage() {
- String testPackageName = "test.package.name";
- WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(testPackageName, "",
- true /*default available*/, false /* fallback */, null)};
- setupWithPackages(webviewPackages);
- mTestSystemImpl.setPackageInfo(createPackageInfo(testPackageName, true /* enabled */,
- true /* valid */, false /* installed */));
-
- runWebViewBootPreparationOnMainSync();
-
- Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged(
- Matchers.anyObject());
- WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
- assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status);
- assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage());
- }
-
@Test
public void testNonhiddenPackageUserOverHidden() {
checkVisiblePackageUserOverNonVisible(false /* multiUser*/, PackageRemovalType.HIDE);
@@ -1374,95 +1237,6 @@
mWebViewUpdateServiceImpl.getCurrentWebViewPackage().versionName);
}
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- public void testMultiProcessEnabledByDefault() {
- testMultiProcessByDefault(true /* enabledByDefault */);
- }
-
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- public void testMultiProcessDisabledByDefault() {
- testMultiProcessByDefault(false /* enabledByDefault */);
- }
-
- private void testMultiProcessByDefault(boolean enabledByDefault) {
- String primaryPackage = "primary";
- WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(
- primaryPackage, "", true /* default available */, false /* fallback */, null)};
- setupWithPackagesAndMultiProcess(packages, enabledByDefault);
- mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
- true /* valid */, true /* installed */, null /* signatures */,
- 10 /* lastUpdateTime*/, false /* not hidden */, 1000 /* versionCode */,
- false /* isSystemApp */));
-
- runWebViewBootPreparationOnMainSync();
- checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */);
-
- // Check it's off by default
- assertEquals(enabledByDefault, mWebViewUpdateServiceImpl.isMultiProcessEnabled());
-
- // Test toggling it
- mWebViewUpdateServiceImpl.enableMultiProcess(!enabledByDefault);
- assertEquals(!enabledByDefault, mWebViewUpdateServiceImpl.isMultiProcessEnabled());
- mWebViewUpdateServiceImpl.enableMultiProcess(enabledByDefault);
- assertEquals(enabledByDefault, mWebViewUpdateServiceImpl.isMultiProcessEnabled());
- }
-
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- public void testMultiProcessEnabledByDefaultWithSettingsValue() {
- testMultiProcessByDefaultWithSettingsValue(
- true /* enabledByDefault */, Integer.MIN_VALUE, false /* expectEnabled */);
- testMultiProcessByDefaultWithSettingsValue(
- true /* enabledByDefault */, -999999, true /* expectEnabled */);
- testMultiProcessByDefaultWithSettingsValue(
- true /* enabledByDefault */, 0, true /* expectEnabled */);
- testMultiProcessByDefaultWithSettingsValue(
- true /* enabledByDefault */, 999999, true /* expectEnabled */);
- }
-
- @Test
- @RequiresFlagsDisabled("android.webkit.update_service_v2")
- public void testMultiProcessDisabledByDefaultWithSettingsValue() {
- testMultiProcessByDefaultWithSettingsValue(
- false /* enabledByDefault */, Integer.MIN_VALUE, false /* expectEnabled */);
- testMultiProcessByDefaultWithSettingsValue(
- false /* enabledByDefault */, 0, false /* expectEnabled */);
- testMultiProcessByDefaultWithSettingsValue(
- false /* enabledByDefault */, 999999, false /* expectEnabled */);
- testMultiProcessByDefaultWithSettingsValue(
- false /* enabledByDefault */, Integer.MAX_VALUE, true /* expectEnabled */);
- }
-
- /**
- * Test the logic of the multiprocess setting depending on whether multiprocess is enabled by
- * default, and what the setting is set to.
- * @param enabledByDefault whether multiprocess is enabled by default.
- * @param settingValue value of the multiprocess setting.
- */
- private void testMultiProcessByDefaultWithSettingsValue(
- boolean enabledByDefault, int settingValue, boolean expectEnabled) {
- String primaryPackage = "primary";
- WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
- new WebViewProviderInfo(
- primaryPackage, "", true /* default available */, false /* fallback */, null)};
- setupWithPackagesAndMultiProcess(packages, enabledByDefault);
- mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
- true /* valid */, true /* installed */, null /* signatures */,
- 10 /* lastUpdateTime*/, false /* not hidden */, 1000 /* versionCode */,
- false /* isSystemApp */));
-
- runWebViewBootPreparationOnMainSync();
- checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */);
-
- mTestSystemImpl.setMultiProcessSetting(settingValue);
-
- assertEquals(expectEnabled, mWebViewUpdateServiceImpl.isMultiProcessEnabled());
- }
-
-
/**
* Ensure that packages with a targetSdkVersion targeting the current platform are valid, and
* that packages targeting an older version are not valid.
@@ -1507,7 +1281,6 @@
}
@Test
- @RequiresFlagsEnabled("android.webkit.update_service_v2")
public void testDefaultWebViewPackageIsTheFirstAvailableByDefault() {
String nonDefaultPackage = "nonDefaultPackage";
String defaultPackage1 = "defaultPackage1";
@@ -1524,7 +1297,6 @@
}
@Test
- @RequiresFlagsEnabled("android.webkit.update_service_v2")
public void testDefaultWebViewPackageEnabling() {
String testPackage = "testDefault";
WebViewProviderInfo[] packages =
@@ -1548,7 +1320,6 @@
}
@Test
- @RequiresFlagsEnabled("android.webkit.update_service_v2")
public void testDefaultWebViewPackageInstallingDuringStartUp() {
String testPackage = "testDefault";
WebViewProviderInfo[] packages =
@@ -1572,7 +1343,6 @@
}
@Test
- @RequiresFlagsEnabled("android.webkit.update_service_v2")
public void testDefaultWebViewPackageInstallingAfterStartUp() {
String testPackage = "testDefault";
WebViewProviderInfo[] packages =
@@ -1603,7 +1373,6 @@
* the repair logic.
*/
@Test
- @RequiresFlagsEnabled("android.webkit.update_service_v2")
public void testAddingNewUserWithDefaultdPackageNotInstalled() {
String testPackage = "testDefault";
WebViewProviderInfo[] packages =
@@ -1651,7 +1420,6 @@
}
@Test
- @RequiresFlagsEnabled("android.webkit.update_service_v2")
public void testDisabledDefaultPackageChosen() {
PackageInfo disabledPackage =
createPackageInfo(
@@ -1664,7 +1432,6 @@
}
@Test
- @RequiresFlagsEnabled("android.webkit.update_service_v2")
public void testUninstalledDefaultPackageChosen() {
PackageInfo uninstalledPackage =
createPackageInfo(
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 dec7f09..b99ab05 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -77,6 +77,8 @@
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.PackageManager.FEATURE_TELECOM;
import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -92,6 +94,7 @@
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
+import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
@@ -361,6 +364,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
+import java.io.OutputStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
@@ -6499,6 +6503,35 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_BACKUP_RESTORE_LOGGING)
+ public void testReadPolicyXml_backupRestoreLogging() throws Exception {
+ BackupRestoreEventLogger logger = mock(BackupRestoreEventLogger.class);
+
+ UserInfo ui = new UserInfo(ActivityManager.getCurrentUser(), "Clone", UserInfo.FLAG_FULL);
+ ui.userType = USER_TYPE_FULL_SYSTEM;
+ when(mUmInternal.getUserInfo(ActivityManager.getCurrentUser())).thenReturn(ui);
+ when(mPermissionHelper.getNotificationPermissionValues(0)).thenReturn(new ArrayMap<>());
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mService.writePolicyXml(baos, true, ActivityManager.getCurrentUser(), logger);
+ serializer.flush();
+
+ mService.readPolicyXml(
+ new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
+ true, ActivityManager.getCurrentUser(), logger);
+
+ verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_CONFIG, 1);
+ verify(logger, never())
+ .logItemsBackupFailed(eq(DATA_TYPE_ZEN_CONFIG), anyInt(), anyString());
+
+ verify(logger).logItemsRestored(DATA_TYPE_ZEN_CONFIG, 1);
+ verify(logger, never())
+ .logItemsRestoreFailed(eq(DATA_TYPE_ZEN_CONFIG), anyInt(), anyString());
+ }
+
+ @Test
public void testLocaleChangedCallsUpdateDefaultZenModeRules() throws Exception {
ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = mZenModeHelper;
@@ -7662,7 +7695,7 @@
mService.mZenModeHelper = mZenModeHelper;
NotificationManager.Policy userPolicy =
new NotificationManager.Policy(0, 0, 0, SUPPRESSED_EFFECT_BADGE);
- when(mZenModeHelper.getNotificationPolicy()).thenReturn(userPolicy);
+ when(mZenModeHelper.getNotificationPolicy(any())).thenReturn(userPolicy);
NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_SCREEN_OFF);
@@ -7682,7 +7715,7 @@
mService.mZenModeHelper = mZenModeHelper;
NotificationManager.Policy userPolicy =
new NotificationManager.Policy(0, 0, 0, SUPPRESSED_EFFECT_BADGE);
- when(mZenModeHelper.getNotificationPolicy()).thenReturn(userPolicy);
+ when(mZenModeHelper.getNotificationPolicy(any())).thenReturn(userPolicy);
NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_NOTIFICATION_LIST);
@@ -7699,7 +7732,7 @@
mService.mZenModeHelper = mZenModeHelper;
NotificationManager.Policy userPolicy =
new NotificationManager.Policy(0, 0, 0, SUPPRESSED_EFFECT_BADGE);
- when(mZenModeHelper.getNotificationPolicy()).thenReturn(userPolicy);
+ when(mZenModeHelper.getNotificationPolicy(any())).thenReturn(userPolicy);
NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_STATUS_BAR);
@@ -7717,7 +7750,7 @@
mService.mZenModeHelper = mZenModeHelper;
NotificationManager.Policy userPolicy =
new NotificationManager.Policy(0, 0, 0, SUPPRESSED_EFFECT_BADGE);
- when(mZenModeHelper.getNotificationPolicy()).thenReturn(userPolicy);
+ when(mZenModeHelper.getNotificationPolicy(any())).thenReturn(userPolicy);
NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_SCREEN_OFF);
@@ -7736,7 +7769,7 @@
mService.mZenModeHelper = mZenModeHelper;
NotificationManager.Policy userPolicy =
new NotificationManager.Policy(0, 0, 0, SUPPRESSED_EFFECT_BADGE);
- when(mZenModeHelper.getNotificationPolicy()).thenReturn(userPolicy);
+ when(mZenModeHelper.getNotificationPolicy(any())).thenReturn(userPolicy);
NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_NOTIFICATION_LIST | SUPPRESSED_EFFECT_AMBIENT
@@ -7756,7 +7789,7 @@
mService.mZenModeHelper = mZenModeHelper;
NotificationManager.Policy userPolicy =
new NotificationManager.Policy(0, 0, 0, SUPPRESSED_EFFECT_BADGE);
- when(mZenModeHelper.getNotificationPolicy()).thenReturn(userPolicy);
+ when(mZenModeHelper.getNotificationPolicy(any())).thenReturn(userPolicy);
NotificationManager.Policy appPolicy = new NotificationManager.Policy(0, 0, 0,
SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_STATUS_BAR);
@@ -10398,7 +10431,7 @@
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),
+ verify(mockZenModeHelper).addAutomaticZenRule(any(), eq("android"), eq(rule),
eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), anyInt());
}
@@ -10420,7 +10453,7 @@
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),
+ verify(mockZenModeHelper).addAutomaticZenRule(any(), eq("android"), eq(rule),
eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), anyInt());
}
@@ -10440,9 +10473,9 @@
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(
- eq("another.package"), eq(rule), eq(ZenModeConfig.ORIGIN_APP),
- anyString(), anyInt()); // doesn't count as a system/systemui call
+ verify(mockZenModeHelper).addAutomaticZenRule(any(), eq("another.package"), eq(rule),
+ eq(ZenModeConfig.ORIGIN_APP), anyString(),
+ anyInt()); // doesn't count as a system/systemui call
}
@Test
@@ -10459,7 +10492,8 @@
mBinderService.addAutomaticZenRule(rule, mPkg, /* fromUser= */ false);
- verify(zenModeHelper).addAutomaticZenRule(eq(mPkg), eq(rule), anyInt(), any(), anyInt());
+ verify(zenModeHelper).addAutomaticZenRule(any(), eq(mPkg), eq(rule), anyInt(), any(),
+ anyInt());
}
@Test
@@ -10494,7 +10528,8 @@
mBinderService.addAutomaticZenRule(rule, mPkg, /* fromUser= */ false);
- verify(zenModeHelper).addAutomaticZenRule(eq(mPkg), eq(rule), anyInt(), any(), anyInt());
+ verify(zenModeHelper).addAutomaticZenRule(any(), eq(mPkg), eq(rule), anyInt(), any(),
+ anyInt());
}
@Test
@@ -10527,7 +10562,8 @@
mBinderService.addAutomaticZenRule(rule, mPkg, /* fromUser= */ false);
- verify(zenModeHelper).addAutomaticZenRule(eq(mPkg), eq(rule), anyInt(), any(), anyInt());
+ verify(zenModeHelper).addAutomaticZenRule(any(), eq(mPkg), eq(rule), anyInt(), any(),
+ anyInt());
}
private void addAutomaticZenRule_restrictedRuleTypeCannotBeUsedByRegularApps(
@@ -10555,7 +10591,7 @@
mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ true);
- verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ verify(zenModeHelper).addAutomaticZenRule(any(), eq("pkg"), eq(SOME_ZEN_RULE),
eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyString(), anyInt());
}
@@ -10567,7 +10603,7 @@
mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ false);
- verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ verify(zenModeHelper).addAutomaticZenRule(any(), eq("pkg"), eq(SOME_ZEN_RULE),
eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), anyInt());
}
@@ -10579,7 +10615,7 @@
mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ false);
- verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ verify(zenModeHelper).addAutomaticZenRule(any(), eq("pkg"), eq(SOME_ZEN_RULE),
eq(ZenModeConfig.ORIGIN_APP), anyString(), anyInt());
}
@@ -10601,7 +10637,7 @@
mBinderService.updateAutomaticZenRule("id", SOME_ZEN_RULE, /* fromUser= */ true);
- verify(zenModeHelper).updateAutomaticZenRule(eq("id"), eq(SOME_ZEN_RULE),
+ verify(zenModeHelper).updateAutomaticZenRule(any(), eq("id"), eq(SOME_ZEN_RULE),
eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyString(), anyInt());
}
@@ -10623,7 +10659,7 @@
mBinderService.removeAutomaticZenRule("id", /* fromUser= */ true);
- verify(zenModeHelper).removeAutomaticZenRule(eq("id"),
+ verify(zenModeHelper).removeAutomaticZenRule(any(), eq("id"),
eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyString(), anyInt());
}
@@ -10648,7 +10684,7 @@
SOURCE_USER_ACTION);
mBinderService.setAutomaticZenRuleState("id", withSourceUser);
- verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceUser),
+ verify(zenModeHelper).setAutomaticZenRuleState(any(), eq("id"), eq(withSourceUser),
eq(ZenModeConfig.ORIGIN_USER_IN_APP), anyInt());
}
@@ -10663,7 +10699,7 @@
SOURCE_CONTEXT);
mBinderService.setAutomaticZenRuleState("id", withSourceContext);
- verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ verify(zenModeHelper).setAutomaticZenRuleState(any(), eq("id"), eq(withSourceContext),
eq(ZenModeConfig.ORIGIN_APP), anyInt());
}
@@ -10678,7 +10714,7 @@
SOURCE_USER_ACTION);
mBinderService.setAutomaticZenRuleState("id", withSourceContext);
- verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ verify(zenModeHelper).setAutomaticZenRuleState(any(), eq("id"), eq(withSourceContext),
eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyInt());
}
@Test
@@ -10692,10 +10728,35 @@
SOURCE_CONTEXT);
mBinderService.setAutomaticZenRuleState("id", withSourceContext);
- verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ verify(zenModeHelper).setAutomaticZenRuleState(any(), eq("id"), eq(withSourceContext),
eq(ZenModeConfig.ORIGIN_SYSTEM), anyInt());
}
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_MULTIUSER)
+ public void getAutomaticZenRules_fromSystem_readsWithCurrentUser() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ // Representative used to verify getCallingZenUser().
+ mBinderService.getAutomaticZenRules();
+
+ verify(zenModeHelper).getAutomaticZenRules(eq(UserHandle.CURRENT));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_MULTIUSER)
+ public void getAutomaticZenRules_fromNormalPackage_readsWithBinderUser() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ // Representative used to verify getCallingZenUser().
+ mBinderService.getAutomaticZenRules();
+
+ verify(zenModeHelper).getAutomaticZenRules(eq(Binder.getCallingUserHandle()));
+ }
+
/** Prepares for a zen-related test that uses a mocked {@link ZenModeHelper}. */
private ZenModeHelper setUpMockZenTest() {
ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
@@ -15815,7 +15876,8 @@
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
mBinderService.setNotificationPolicy("package", policy, false);
- verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy));
+ verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(any(), eq("package"), anyInt(),
+ eq(policy));
}
@Test
@@ -15831,7 +15893,7 @@
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
mBinderService.setNotificationPolicy("package", policy, false);
- verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
+ verify(zenModeHelper).setNotificationPolicy(any(), eq(policy), anyInt(), anyInt());
}
@Test
@@ -15878,9 +15940,9 @@
mBinderService.setNotificationPolicy("package", policy, false);
if (canSetGlobalPolicy) {
- verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
+ verify(zenModeHelper).setNotificationPolicy(any(), eq(policy), anyInt(), anyInt());
} else {
- verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(anyString(), anyInt(),
+ verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(any(), anyString(), anyInt(),
eq(policy));
}
}
@@ -15898,7 +15960,7 @@
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
mBinderService.setNotificationPolicy("package", policy, false);
- verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
+ verify(zenModeHelper).setNotificationPolicy(any(), eq(policy), anyInt(), anyInt());
}
@Test
@@ -15913,7 +15975,7 @@
mBinderService.getNotificationPolicy("package");
- verify(zenHelper).getNotificationPolicyFromImplicitZenRule(eq("package"));
+ verify(zenHelper).getNotificationPolicyFromImplicitZenRule(any(), eq("package"));
}
@Test
@@ -15928,7 +15990,7 @@
mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
- verify(zenHelper).applyGlobalZenModeAsImplicitZenRule(eq("package"), anyInt(),
+ verify(zenHelper).applyGlobalZenModeAsImplicitZenRule(any(), eq("package"), anyInt(),
eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
}
@@ -15945,9 +16007,8 @@
mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
- verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
- eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), eq("package"),
- anyInt());
+ verify(zenModeHelper).setManualZenMode(any(), eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS),
+ eq(null), eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), eq("package"), anyInt());
}
@Test
@@ -15991,10 +16052,10 @@
mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
if (canSetGlobalPolicy) {
- verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
- eq(ZenModeConfig.ORIGIN_APP), anyString(), eq("package"), anyInt());
+ verify(zenModeHelper).setManualZenMode(any(), eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS),
+ eq(null), eq(ZenModeConfig.ORIGIN_APP), anyString(), eq("package"), anyInt());
} else {
- verify(zenModeHelper).applyGlobalZenModeAsImplicitZenRule(anyString(), anyInt(),
+ verify(zenModeHelper).applyGlobalZenModeAsImplicitZenRule(any(), anyString(), anyInt(),
eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
}
}
@@ -16013,8 +16074,8 @@
mBinderService.requestInterruptionFilterFromListener(mock(INotificationListener.class),
INTERRUPTION_FILTER_PRIORITY);
- verify(mService.mZenModeHelper).applyGlobalZenModeAsImplicitZenRule(eq("pkg"), eq(mUid),
- eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
+ verify(mService.mZenModeHelper).applyGlobalZenModeAsImplicitZenRule(any(), eq("pkg"),
+ eq(mUid), eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
}
@Test
@@ -16031,9 +16092,9 @@
mBinderService.requestInterruptionFilterFromListener(mock(INotificationListener.class),
INTERRUPTION_FILTER_PRIORITY);
- verify(mService.mZenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS),
- eq(null), eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(),
- eq("pkg"), eq(mUid));
+ verify(mService.mZenModeHelper).setManualZenMode(any(),
+ eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null), eq(ZenModeConfig.ORIGIN_SYSTEM),
+ anyString(), eq("pkg"), eq(mUid));
}
@Test
@@ -16111,8 +16172,8 @@
throws Exception {
setUpRealZenTest();
// Start with hasPriorityChannels=true, allowPriorityChannels=true ("default").
- mService.mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0, 0,
- Policy.policyState(true, true), 0),
+ mService.mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT,
+ new Policy(0, 0, 0, 0, Policy.policyState(true, true), 0),
ZenModeConfig.ORIGIN_SYSTEM, Process.SYSTEM_UID);
// The caller will supply states with "wrong" hasPriorityChannels.
@@ -16142,8 +16203,8 @@
throws Exception {
setUpRealZenTest();
// Start with hasPriorityChannels=true, allowPriorityChannels=true ("default").
- mService.mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0, 0,
- Policy.policyState(true, true), 0),
+ mService.mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT,
+ new Policy(0, 0, 0, 0, Policy.policyState(true, true), 0),
ZenModeConfig.ORIGIN_SYSTEM, Process.SYSTEM_UID);
mService.setCallerIsNormalPackage();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 1a1da0f..e1b478c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -359,7 +359,7 @@
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(),
anyString(), eq(null), anyString())).thenReturn(MODE_DEFAULT);
@@ -493,7 +493,7 @@
private void resetZenModeHelper() {
reset(mMockZenModeHelper);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
}
private void setUpPackageWithUid(String packageName, int uid) throws Exception {
@@ -2632,9 +2632,10 @@
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, never()).updateHasPriorityChannels(anyBoolean());
+ verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
} else {
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), any(), anyInt(),
+ anyInt());
}
resetZenModeHelper();
@@ -2646,9 +2647,11 @@
uid, false);
assertTrue(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(true));
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(true));
} else {
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
}
resetZenModeHelper();
@@ -2656,18 +2659,21 @@
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false);
assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, never()).updateHasPriorityChannels(anyBoolean());
+ verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
} else {
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), any(), anyInt(),
+ anyInt());
}
resetZenModeHelper();
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(false));
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(false));
} else {
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
}
resetZenModeHelper();
}
@@ -2685,9 +2691,10 @@
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, never()).updateHasPriorityChannels(anyBoolean());
+ verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
} else {
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), any(), anyInt(),
+ anyInt());
}
resetZenModeHelper();
@@ -2699,9 +2706,11 @@
assertTrue(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(true));
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(true));
} else {
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
}
resetZenModeHelper();
}
@@ -2719,9 +2728,10 @@
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, never()).updateHasPriorityChannels(anyBoolean());
+ verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
} else {
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), any(), anyInt(),
+ anyInt());
}
resetZenModeHelper();
@@ -2733,9 +2743,11 @@
uid, false);
assertTrue(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(true));
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(true));
} else {
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
}
resetZenModeHelper();
@@ -2743,18 +2755,21 @@
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false);
assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, never()).updateHasPriorityChannels(anyBoolean());
+ verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
} else {
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), any(), anyInt(),
+ anyInt());
}
resetZenModeHelper();
mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(false));
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(false));
} else {
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
}
resetZenModeHelper();
}
@@ -2767,7 +2782,7 @@
// start in a 'allowed to bypass dnd state'
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
mHelper.syncChannelsBypassingDnd();
// create notification channel that can bypass dnd, but app is blocked
@@ -2783,9 +2798,11 @@
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(false));
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(false));
} else {
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
}
resetZenModeHelper();
}
@@ -2798,7 +2815,7 @@
// start in a 'allowed to bypass dnd state'
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
mHelper.syncChannelsBypassingDnd();
// create notification channel that can bypass dnd, but app is blocked
@@ -2809,9 +2826,11 @@
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(false));
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(false));
} else {
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
}
resetZenModeHelper();
}
@@ -2824,7 +2843,7 @@
// start in a 'allowed to bypass dnd state'
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
mHelper.syncChannelsBypassingDnd();
// create notification channel that can bypass dnd, but app is blocked
@@ -2835,9 +2854,11 @@
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(false));
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(false));
} else {
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
}
resetZenModeHelper();
}
@@ -2855,9 +2876,10 @@
uid, false);
assertFalse(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, never()).updateHasPriorityChannels(anyBoolean());
+ verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
} else {
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), any(), anyInt(),
+ anyInt());
}
resetZenModeHelper();
@@ -2867,9 +2889,11 @@
mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true);
assertTrue(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(true));
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(true));
} else {
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
}
resetZenModeHelper();
@@ -2879,9 +2903,11 @@
mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true);
assertFalse(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(false));
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(false));
} else {
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
}
resetZenModeHelper();
}
@@ -2892,13 +2918,15 @@
// RankingHelper should change to false
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
mHelper.syncChannelsBypassingDnd();
assertFalse(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(false));
+ verify(mMockZenModeHelper, times(1)).updateHasPriorityChannels(eq(UserHandle.CURRENT),
+ eq(false));
} else {
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(eq(UserHandle.CURRENT),
+ any(), anyInt(), anyInt());
}
resetZenModeHelper();
}
@@ -2907,12 +2935,13 @@
public void testSetupNewZenModeHelper_cannotBypass() {
// start notification policy off with mAreChannelsBypassingDnd = false
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
assertFalse(mHelper.areChannelsBypassingDnd());
if (android.app.Flags.modesUi()) {
- verify(mMockZenModeHelper, never()).updateHasPriorityChannels(anyBoolean());
+ verify(mMockZenModeHelper, never()).updateHasPriorityChannels(any(), anyBoolean());
} else {
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), any(), anyInt(),
+ anyInt());
}
resetZenModeHelper();
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 5d4382a..f900346 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -155,7 +155,7 @@
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ when(mMockZenModeHelper.getNotificationPolicy(any())).thenReturn(mTestNotificationPolicy);
mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
mUsageStats, new String[] {ImportanceExtractor.class.getName()},
mock(IPlatformCompat.class), mGroupHelper);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 5709d88..bdf146f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -17,6 +17,8 @@
package com.android.server.notification;
import static android.app.AutomaticZenRule.TYPE_BEDTIME;
+import static android.app.Flags.FLAG_BACKUP_RESTORE_LOGGING;
+import static android.app.Flags.FLAG_MODES_API;
import static android.app.Flags.FLAG_MODES_UI;
import static android.app.Flags.modesUi;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
@@ -24,6 +26,8 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.suppressedEffectsToString;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES;
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_UNKNOWN;
@@ -52,17 +56,22 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.app.NotificationManager.Policy;
+import android.app.backup.BackupRestoreEventLogger;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Parcel;
+import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.FlagsParameterization;
@@ -135,7 +144,7 @@
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
return FlagsParameterization.allCombinationsOf(
- FLAG_MODES_UI);
+ FLAG_MODES_UI, FLAG_BACKUP_RESTORE_LOGGING);
}
public ZenModeConfigTest(FlagsParameterization flags) {
@@ -144,7 +153,6 @@
@Before
public final void setUp() {
- mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
MockitoAnnotations.initMocks(this);
mContext.setMockPackageManager(mPm);
}
@@ -515,6 +523,98 @@
}
@Test
+ @EnableFlags({FLAG_MODES_UI, FLAG_BACKUP_RESTORE_LOGGING})
+ public void testBackupRestore_fromPreModesUi() throws IOException, XmlPullParserException {
+ String xml = "<zen version=\"12\">\n"
+ + "<allow calls=\"true\" repeatCallers=\"true\" messages=\"true\""
+ + " reminders=\"false\" events=\"false\" callsFrom=\"2\" messagesFrom=\"2\""
+ + " alarms=\"true\" media=\"true\" system=\"false\" convos=\"true\" convosFrom=\"2\""
+ + " priorityChannelsAllowed=\"true\" />\n"
+ + "<disallow visualEffects=\"157\" />\n"
+ + "<manual enabled=\"true\" zen=\"1\" creationTime=\"0\" modified=\"false\" />\n"
+ + "<state areChannelsBypassingDnd=\"true\" />\n"
+ + "</zen>";
+
+ BackupRestoreEventLogger logger = mock(BackupRestoreEventLogger.class);
+ readConfigXml(new ByteArrayInputStream(xml.getBytes()), logger);
+
+ verify(logger).logItemsRestored(DATA_TYPE_ZEN_RULES, 1);
+ }
+
+ @Test
+ public void testBackupRestore() throws IOException, XmlPullParserException {
+ ZenModeConfig config = new ZenModeConfig();
+ ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ rule.configurationActivity = CONFIG_ACTIVITY;
+ rule.component = OWNER;
+ rule.conditionId = CONDITION_ID;
+ rule.condition = CONDITION;
+ rule.enabled = ENABLED;
+ rule.creationTime = 123;
+ rule.id = "id";
+ rule.zenMode = INTERRUPTION_FILTER;
+ rule.modified = true;
+ rule.name = NAME;
+ rule.setConditionOverride(OVERRIDE_DEACTIVATE);
+ rule.pkg = OWNER.getPackageName();
+ rule.zenPolicy = POLICY;
+
+ rule.allowManualInvocation = ALLOW_MANUAL;
+ rule.type = TYPE;
+ rule.userModifiedFields = 16;
+ rule.zenPolicyUserModifiedFields = 5;
+ rule.zenDeviceEffectsUserModifiedFields = 2;
+ rule.iconResName = ICON_RES_NAME;
+ rule.triggerDescription = TRIGGER_DESC;
+ rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
+ if (Flags.modesUi()) {
+ rule.disabledOrigin = ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
+ }
+ config.automaticRules.put(rule.id, rule);
+
+ BackupRestoreEventLogger logger = null;
+ if (Flags.backupRestoreLogging()) {
+ logger = mock(BackupRestoreEventLogger.class);
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeConfigXml(config, XML_VERSION_MODES_API, true, baos, logger);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig fromXml = readConfigXml(bais, logger);
+
+ ZenModeConfig.ZenRule ruleActual = fromXml.automaticRules.get(rule.id);
+ assertEquals(rule.pkg, ruleActual.pkg);
+ assertEquals(OVERRIDE_NONE, ruleActual.getConditionOverride());
+ assertEquals(rule.enabler, ruleActual.enabler);
+ assertEquals(rule.component, ruleActual.component);
+ assertEquals(rule.configurationActivity, ruleActual.configurationActivity);
+ assertEquals(rule.condition, ruleActual.condition);
+ assertEquals(rule.enabled, ruleActual.enabled);
+ assertEquals(rule.creationTime, ruleActual.creationTime);
+ assertEquals(rule.modified, ruleActual.modified);
+ assertEquals(rule.conditionId, ruleActual.conditionId);
+ assertEquals(rule.name, ruleActual.name);
+ assertEquals(rule.zenMode, ruleActual.zenMode);
+
+ assertEquals(rule.allowManualInvocation, ruleActual.allowManualInvocation);
+ assertEquals(rule.iconResName, ruleActual.iconResName);
+ assertEquals(rule.type, ruleActual.type);
+ assertEquals(rule.userModifiedFields, ruleActual.userModifiedFields);
+ assertEquals(rule.zenPolicyUserModifiedFields, ruleActual.zenPolicyUserModifiedFields);
+ assertEquals(rule.zenDeviceEffectsUserModifiedFields,
+ ruleActual.zenDeviceEffectsUserModifiedFields);
+ assertEquals(rule.triggerDescription, ruleActual.triggerDescription);
+ assertEquals(rule.zenPolicy, ruleActual.zenPolicy);
+ assertEquals(rule.deletionInstant, ruleActual.deletionInstant);
+ if (Flags.modesUi()) {
+ assertEquals(rule.disabledOrigin, ruleActual.disabledOrigin);
+ }
+ if (Flags.backupRestoreLogging()) {
+ verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_RULES, 2);
+ verify(logger).logItemsRestored(DATA_TYPE_ZEN_RULES, 2);
+ }
+ }
+
+ @Test
public void testWriteToParcel() {
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.configurationActivity = CONFIG_ACTIVITY;
@@ -1023,9 +1123,9 @@
// write out entire config xml
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos);
+ writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos, null);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
- ZenModeConfig fromXml = readConfigXml(bais);
+ ZenModeConfig fromXml = readConfigXml(bais, null);
// The result should be valid and contain a manual rule; the rule should have a non-null
@@ -1055,9 +1155,9 @@
// write out entire config xml
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos);
+ writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos, null);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
- ZenModeConfig fromXml = readConfigXml(bais);
+ ZenModeConfig fromXml = readConfigXml(bais, null);
// The result should have a manual rule; it should have a non-null ZenPolicy and a condition
// whose state is true. The conditionId and enabler data should also be preserved.
@@ -1084,9 +1184,9 @@
// write out entire config xml
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos);
+ writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos, null);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
- ZenModeConfig fromXml = readConfigXml(bais);
+ ZenModeConfig fromXml = readConfigXml(bais, null);
// The result should have a manual rule; it should not be changed from the previous rule.
assertThat(fromXml.manualRule).isEqualTo(config.manualRule);
@@ -1213,9 +1313,9 @@
config.manualRule.enabled = false;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- writeConfigXml(config, XML_VERSION_MODES_UI, /* forBackup= */ false, baos);
+ writeConfigXml(config, XML_VERSION_MODES_UI, /* forBackup= */ false, baos, null);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
- ZenModeConfig fromXml = readConfigXml(bais);
+ ZenModeConfig fromXml = readConfigXml(bais, null);
assertThat(fromXml.manualRule.enabled).isTrue();
}
@@ -1359,23 +1459,23 @@
}
private void writeConfigXml(ZenModeConfig config, Integer version, boolean forBackup,
- ByteArrayOutputStream os) throws IOException {
+ ByteArrayOutputStream os, BackupRestoreEventLogger logger) throws IOException {
String tag = ZEN_TAG;
TypedXmlSerializer out = Xml.newFastSerializer();
out.setOutput(new BufferedOutputStream(os), "utf-8");
out.startDocument(null, true);
out.startTag(null, tag);
- config.writeXml(out, version, forBackup);
+ config.writeXml(out, version, forBackup, logger);
out.endTag(null, tag);
out.endDocument();
}
- private ZenModeConfig readConfigXml(ByteArrayInputStream is)
+ private ZenModeConfig readConfigXml(ByteArrayInputStream is, BackupRestoreEventLogger logger)
throws XmlPullParserException, IOException {
TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(is), null);
parser.nextTag();
- return ZenModeConfig.readXml(parser);
+ return ZenModeConfig.readXml(parser, logger);
}
}
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 8b3ac2b..0019b3e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -20,7 +20,9 @@
import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
+import static android.app.Flags.FLAG_BACKUP_RESTORE_LOGGING;
import static android.app.Flags.FLAG_MODES_API;
+import static android.app.Flags.FLAG_MODES_MULTIUSER;
import static android.app.Flags.FLAG_MODES_UI;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
@@ -61,6 +63,7 @@
import static android.service.notification.ZenModeConfig.ORIGIN_APP;
import static android.service.notification.ZenModeConfig.ORIGIN_INIT;
import static android.service.notification.ZenModeConfig.ORIGIN_INIT_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_SYSTEM;
import static android.service.notification.ZenModeConfig.ORIGIN_UNKNOWN;
import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_APP;
import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
@@ -81,6 +84,8 @@
import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES;
import static com.android.server.notification.ZenModeEventLogger.ACTIVE_RULE_TYPE_MANUAL;
import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
@@ -102,6 +107,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.notNull;
@@ -116,15 +122,17 @@
import android.Manifest;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.compat.CompatChanges;
import android.content.ComponentName;
-import android.content.ContentResolver;
+import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -271,7 +279,6 @@
private TestableLooper mTestableLooper;
private final TestClock mTestClock = new TestClock();
private ZenModeHelper mZenModeHelper;
- private ContentResolver mContentResolver;
@Mock
DeviceEffectsApplier mDeviceEffectsApplier;
@Mock
@@ -282,8 +289,7 @@
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return FlagsParameterization.progressionOf(FLAG_MODES_API,
- FLAG_MODES_UI);
+ return FlagsParameterization.allCombinationsOf(FLAG_MODES_UI, FLAG_BACKUP_RESTORE_LOGGING);
}
public ZenModeHelperTest(FlagsParameterization flags) {
@@ -296,7 +302,6 @@
mTestableLooper = TestableLooper.get(this);
mContext.ensureTestableResources();
- mContentResolver = mContext.getContentResolver();
mResources = mock(Resources.class, withSettings()
.spiedInstance(mContext.getResources()));
mPkg = mContext.getPackageName();
@@ -314,11 +319,16 @@
mContext.addMockSystemService(AppOpsManager.class, mAppOps);
mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
+ mContext.addMockSystemService(Context.ALARM_SERVICE, mock(AlarmManager.class));
mConditionProviders = new ConditionProviders(mContext, new UserProfiles(),
AppGlobals.getPackageManager());
- mConditionProviders.addSystemProvider(new CountdownConditionProvider());
- mConditionProviders.addSystemProvider(new ScheduleConditionProvider());
+ CountdownConditionProvider countdown = spy(new CountdownConditionProvider());
+ ScheduleConditionProvider schedule = spy(new ScheduleConditionProvider());
+ doNothing().when(countdown).notifyConditions(any());
+ doNothing().when(schedule).notifyConditions(any());
+ mConditionProviders.addSystemProvider(countdown);
+ mConditionProviders.addSystemProvider(schedule);
mZenModeEventLogger = new ZenModeEventLoggerFake(mPackageManager);
mZenModeHelper = new ZenModeHelper(mContext, mTestableLooper.getLooper(), mTestClock,
mConditionProviders, mTestFlagResolver, mZenModeEventLogger);
@@ -377,7 +387,7 @@
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
serializer.startDocument(null, true);
- mZenModeHelper.writeXml(serializer, false, version, UserHandle.USER_ALL);
+ mZenModeHelper.writeXml(serializer, false, version, UserHandle.USER_ALL, null);
serializer.endDocument();
serializer.flush();
mZenModeHelper.setConfig(new ZenModeConfig(), null, ORIGIN_INIT, "writing xml",
@@ -385,13 +395,14 @@
return baos;
}
- private ByteArrayOutputStream writeXmlAndPurgeForUser(Integer version, int userId)
+ private ByteArrayOutputStream writeXmlAndPurgeForUser(Integer version, int userId,
+ boolean forBackup, BackupRestoreEventLogger logger)
throws Exception {
TypedXmlSerializer serializer = Xml.newFastSerializer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
serializer.startDocument(null, true);
- mZenModeHelper.writeXml(serializer, true, version, userId);
+ mZenModeHelper.writeXml(serializer, forBackup, version, userId, logger);
serializer.endDocument();
serializer.flush();
ZenModeConfig newConfig = new ZenModeConfig();
@@ -689,13 +700,12 @@
AutomaticZenRule azr = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_NONE)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azr, ZenModeConfig.ORIGIN_SYSTEM, "reason", SYSTEM_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azr, ORIGIN_SYSTEM, "reason", SYSTEM_UID);
// Enable rule
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
- new Condition(azr.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
+ new Condition(azr.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM,
SYSTEM_UID);
// Confirm that the consolidated policy doesn't allow anything
@@ -723,13 +733,12 @@
AutomaticZenRule azr = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azr, ZenModeConfig.ORIGIN_SYSTEM, "reason", SYSTEM_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azr, ORIGIN_SYSTEM, "reason", SYSTEM_UID);
// Enable rule
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
- new Condition(azr.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
+ new Condition(azr.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM,
SYSTEM_UID);
// Confirm that the consolidated policy allows only alarms and media and nothing else
@@ -756,7 +765,7 @@
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
// Set zen to priority-only with all notification sounds muted (so ringer will be muted)
Policy totalSilence = new Policy(0, 0, 0);
- mZenModeHelper.setNotificationPolicy(totalSilence, ORIGIN_APP, 1);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, totalSilence, ORIGIN_APP, 1);
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
// 2. verify ringer is unchanged
@@ -793,8 +802,8 @@
public void testRingerAffectedStreamsPriorityOnly() {
// in priority only mode:
// ringtone, notification and system streams are affected by ringer mode
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
- ORIGIN_APP, "test", "caller", 1);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ Uri.EMPTY, ORIGIN_APP, "test", "caller", 1);
ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerMuted =
mZenModeHelper.new RingerModeDelegate();
@@ -810,9 +819,10 @@
// even when ringer is muted (since all ringer sounds cannot bypass DND),
// system stream is still affected by ringer mode
- mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0), ORIGIN_APP, 1);
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
- ORIGIN_APP, "test", "caller", 1);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, new Policy(0, 0, 0), ORIGIN_APP,
+ 1);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ Uri.EMPTY, ORIGIN_APP, "test", "caller", 1);
ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerNotMuted =
mZenModeHelper.new RingerModeDelegate();
@@ -919,7 +929,7 @@
// apply zen off multiple times - verify ringer is not set to normal
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
for (int i = 0; i < 3; i++) {
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, Uri.EMPTY,
ORIGIN_APP, "test", "caller", 1);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
@@ -944,7 +954,7 @@
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
for (int i = 0; i < 3; i++) {
// if zen doesn't change, zen should not reapply itself to the ringer
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, Uri.EMPTY,
ORIGIN_APP, "test", "caller", 1);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
@@ -969,7 +979,7 @@
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
for (int i = 0; i < 3; i++) {
// if zen doesn't change, zen should not reapply itself to the ringer
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, Uri.EMPTY,
ORIGIN_APP, "test", "caller", 1);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
@@ -985,9 +995,8 @@
reset(mAudioManager);
// Turn manual zen mode on
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ORIGIN_APP,
- null, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_APP, null, "test", CUSTOM_PKG_UID);
// audio manager shouldn't do anything until the handler processes its messages
verify(mAudioManager, never()).updateRingerModeAffectedStreamsInternal();
@@ -1012,6 +1021,7 @@
// Turn manual zen mode on
mZenModeHelper.setManualZenMode(
+ UserHandle.CURRENT,
ZEN_MODE_IMPORTANT_INTERRUPTIONS,
null,
ORIGIN_APP,
@@ -1019,6 +1029,7 @@
"test",
CUSTOM_PKG_UID);
mZenModeHelper.setManualZenMode(
+ UserHandle.CURRENT,
ZEN_MODE_IMPORTANT_INTERRUPTIONS,
null,
ORIGIN_APP,
@@ -1042,19 +1053,22 @@
@Test
public void testParcelConfig() {
- mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT,
+ new Policy(PRIORITY_CATEGORY_EVENTS
| PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
| PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
PRIORITY_SENDERS_STARRED, 0, CONVERSATION_SENDERS_ANYONE),
ORIGIN_UNKNOWN,
1);
- mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldDimWallpaper(true)
- .setShouldDisplayGrayscale(true)
- .setShouldUseNightMode(true)
- .build(), ORIGIN_UNKNOWN, "test", 1);
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
- ORIGIN_UNKNOWN, "test", "me", 1);
+ mZenModeHelper.setManualZenRuleDeviceEffects(UserHandle.CURRENT,
+ new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
+ .setShouldUseNightMode(true)
+ .build(),
+ ORIGIN_UNKNOWN, "test", 1);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ Uri.EMPTY, ORIGIN_UNKNOWN, "test", "me", 1);
ZenModeConfig actual = mZenModeHelper.mConfig.copy();
@@ -1063,18 +1077,21 @@
@Test
public void testWriteXml() throws Exception {
- mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT,
+ new Policy(PRIORITY_CATEGORY_EVENTS
| PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
| PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
PRIORITY_SENDERS_STARRED, SUPPRESSED_EFFECT_BADGE,
CONVERSATION_SENDERS_ANYONE),
ORIGIN_UNKNOWN, 1);
- mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldDimWallpaper(true)
- .setShouldDisplayGrayscale(true)
- .build(), ORIGIN_UNKNOWN, "test", 1);
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
- ORIGIN_UNKNOWN, "test", "me", 1);
+ mZenModeHelper.setManualZenRuleDeviceEffects(UserHandle.CURRENT,
+ new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
+ .build(),
+ ORIGIN_UNKNOWN, "test", 1);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ Uri.EMPTY, ORIGIN_UNKNOWN, "test", "me", 1);
ZenModeConfig expected = mZenModeHelper.mConfig.copy();
if (Flags.modesUi()) {
@@ -1085,7 +1102,7 @@
ByteArrayOutputStream baos = writeXmlAndPurge(null);
TypedXmlPullParser parser = getParserForByteStream(baos);
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertEquals("Config mismatch: current vs expected: "
+ new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, expected), expected,
@@ -1094,9 +1111,9 @@
@Test
public void testProto() throws InvalidProtocolBufferException {
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM,
- null, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, null,
+ "test", CUSTOM_PKG_UID);
mZenModeHelper.mConfig.automaticRules = new ArrayMap<>(); // no automatic rules
@@ -1161,7 +1178,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
@@ -1319,8 +1336,8 @@
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
- mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, ORIGIN_APP, "test",
- CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, CUSTOM_RULE_ID, ORIGIN_APP,
+ "test", CUSTOM_PKG_UID);
assertTrue(-1
== mZenModeHelper.mRulesUidCache.getOrDefault(CUSTOM_PKG_NAME + "|" + 0, -1));
}
@@ -1348,9 +1365,8 @@
public void testProtoWithManualRule() throws Exception {
setupZenConfig();
mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules();
- mZenModeHelper.setManualZenMode(INTERRUPTION_FILTER_PRIORITY, Uri.EMPTY,
- ORIGIN_APP,
- "test", "me", 1);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, INTERRUPTION_FILTER_PRIORITY, Uri.EMPTY,
+ ORIGIN_APP, "test", "me", 1);
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
@@ -1370,6 +1386,10 @@
@Test
public void testWriteXml_onlyBackupsTargetUser() throws Exception {
+ BackupRestoreEventLogger logger = null;
+ if (android.app.Flags.backupRestoreLogging()) {
+ logger = mock(BackupRestoreEventLogger.class);
+ }
// Setup configs for user 10 and 11.
setupZenConfig();
ZenModeConfig config10 = mZenModeHelper.mConfig.copy();
@@ -1386,15 +1406,16 @@
SYSTEM_UID);
// Backup user 10 and reset values.
- ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, 10);
+ ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, 10, true, logger);
ZenModeConfig newConfig11 = new ZenModeConfig();
newConfig11.user = 11;
mZenModeHelper.mConfigs.put(11, newConfig11);
// Parse backup data.
TypedXmlPullParser parser = getParserForByteStream(baos);
- mZenModeHelper.readXml(parser, true, 10);
- mZenModeHelper.readXml(parser, true, 11);
+ mZenModeHelper.readXml(parser, true, 10, logger);
+ parser = getParserForByteStream(baos);
+ mZenModeHelper.readXml(parser, true, 11, logger);
ZenModeConfig actual = mZenModeHelper.mConfigs.get(10);
if (Flags.modesUi()) {
@@ -1408,39 +1429,72 @@
"Config mismatch: current vs expected: "
+ new ZenModeDiff.ConfigDiff(actual, config10), config10, actual);
assertNotEquals("Expected config mismatch", config11, mZenModeHelper.mConfigs.get(11));
+
+ if (android.app.Flags.backupRestoreLogging()) {
+ verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_CONFIG, 1);
+ // If this is modes_ui, this is manual + single default rule
+ // If not modes_ui, it's two default automatic rules + manual policy
+ verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_RULES, Flags.modesUi() ? 2 : 3);
+ verify(logger, never())
+ .logItemsBackupFailed(anyString(), anyInt(), anyString());
+
+ verify(logger, times(2)).logItemsRestored(DATA_TYPE_ZEN_RULES, Flags.modesUi() ? 2 : 3);
+ verify(logger, never())
+ .logItemsRestoreFailed(anyString(), anyInt(), anyString());
+ }
}
@Test
public void testReadXmlRestore_forSystemUser() throws Exception {
+ BackupRestoreEventLogger logger = null;
+ if (android.app.Flags.backupRestoreLogging()) {
+ logger = mock(BackupRestoreEventLogger.class);
+ }
setupZenConfig();
// one enabled automatic rule
mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules();
ZenModeConfig original = mZenModeHelper.mConfig.copy();
- ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, UserHandle.USER_SYSTEM);
+ ByteArrayOutputStream baos = writeXmlAndPurgeForUser(
+ null, UserHandle.USER_SYSTEM, true, logger);
TypedXmlPullParser parser = getParserForByteStream(baos);
- mZenModeHelper.readXml(parser, true, UserHandle.USER_SYSTEM);
+ mZenModeHelper.readXml(parser, true, UserHandle.USER_SYSTEM, logger);
assertEquals("Config mismatch: current vs original: "
+ new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, original),
original, mZenModeHelper.mConfig);
assertEquals(original.hashCode(), mZenModeHelper.mConfig.hashCode());
+
+ if (android.app.Flags.backupRestoreLogging()) {
+ verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_CONFIG, 1);
+ verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_RULES, 2);
+ verify(logger, never())
+ .logItemsBackupFailed(anyString(), anyInt(), anyString());
+ verify(logger).logItemsRestored(DATA_TYPE_ZEN_RULES, 2);
+ verify(logger, never())
+ .logItemsRestoreFailed(anyString(), anyInt(), anyString());
+ }
}
/** Restore should ignore the data's user id and restore for the target user. */
@Test
public void testReadXmlRestore_forNonSystemUser() throws Exception {
+ BackupRestoreEventLogger logger = null;
+ if (android.app.Flags.backupRestoreLogging()) {
+ logger = mock(BackupRestoreEventLogger.class);
+ }
// Setup config.
setupZenConfig();
mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules();
ZenModeConfig expected = mZenModeHelper.mConfig.copy();
// Backup data for user 0.
- ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, UserHandle.USER_SYSTEM);
+ ByteArrayOutputStream baos = writeXmlAndPurgeForUser(
+ null, UserHandle.USER_SYSTEM, true, logger);
// Restore data for user 10.
TypedXmlPullParser parser = getParserForByteStream(baos);
- mZenModeHelper.readXml(parser, true, 10);
+ mZenModeHelper.readXml(parser, true, 10, logger);
ZenModeConfig actual = mZenModeHelper.mConfigs.get(10);
expected.user = 10;
@@ -1454,17 +1508,21 @@
@Test
public void testReadXmlRestore_doesNotEnableManualRule() throws Exception {
+ BackupRestoreEventLogger logger = null;
+ if (android.app.Flags.backupRestoreLogging()) {
+ logger = mock(BackupRestoreEventLogger.class);
+ }
setupZenConfig();
// Turn on manual zen mode
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
ORIGIN_USER_IN_SYSTEMUI, "", "someCaller", SYSTEM_UID);
ZenModeConfig original = mZenModeHelper.mConfig.copy();
assertThat(original.isManualActive()).isTrue();
ByteArrayOutputStream baos = writeXmlAndPurge(null);
TypedXmlPullParser parser = getParserForByteStream(baos);
- mZenModeHelper.readXml(parser, true, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, true, UserHandle.USER_ALL, logger);
ZenModeConfig result = mZenModeHelper.getConfig();
assertThat(result.isManualActive()).isFalse();
@@ -1518,7 +1576,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
ZenModeConfig.ZenRule original = expected.automaticRules.get(ruleId);
ZenModeConfig.ZenRule current = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -1529,6 +1587,10 @@
@Test
public void testReadXmlRestoreWithZenPolicy_forSystemUser() throws Exception {
+ BackupRestoreEventLogger logger = null;
+ if (android.app.Flags.backupRestoreLogging()) {
+ logger = mock(BackupRestoreEventLogger.class);
+ }
final String ruleId = "customRule";
setupZenConfig();
@@ -1560,9 +1622,10 @@
SystemZenRules.maybeUpgradeRules(mContext, expected);
}
- ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, UserHandle.USER_SYSTEM);
+ ByteArrayOutputStream baos = writeXmlAndPurgeForUser(
+ null, UserHandle.USER_SYSTEM, true, logger);
TypedXmlPullParser parser = getParserForByteStream(baos);
- mZenModeHelper.readXml(parser, true, UserHandle.USER_SYSTEM);
+ mZenModeHelper.readXml(parser, true, UserHandle.USER_SYSTEM, logger);
ZenModeConfig.ZenRule original = expected.automaticRules.get(ruleId);
ZenModeConfig.ZenRule current = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -1574,7 +1637,7 @@
@Test
public void testReadXmlRulesNotOverridden() throws Exception {
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
// automatic zen rule is enabled on upgrade so rules should not be overriden to default
ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>();
@@ -1594,10 +1657,10 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertTrue(mZenModeHelper.mConfig.automaticRules.containsKey("customRule"));
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
}
@Test
@@ -1614,7 +1677,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertTrue(mZenModeHelper.mConfig.getZenPolicy().shouldShowAllVisualEffects());
@@ -1630,7 +1693,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertTrue(mZenModeHelper.mConfig.getZenPolicy().shouldShowAllVisualEffects());
}
@@ -1649,7 +1712,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertTrue(mZenModeHelper.mConfig.getZenPolicy().shouldShowAllVisualEffects());
}
@@ -1668,7 +1731,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertThat(mZenModeHelper.mConfig.getZenPolicy()
.isVisualEffectAllowed(VISUAL_EFFECT_FULL_SCREEN_INTENT, true)).isFalse();
@@ -1693,7 +1756,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertThat(mZenModeHelper.mConfig.getZenPolicy()
.isVisualEffectAllowed(VISUAL_EFFECT_PEEK, true)).isFalse();
@@ -1712,7 +1775,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertThat(mZenModeHelper.mConfig.getZenPolicy()
.isVisualEffectAllowed(VISUAL_EFFECT_FULL_SCREEN_INTENT, true)).isFalse();
@@ -1727,7 +1790,7 @@
@Test
public void testReadXmlResetDefaultRules() throws Exception {
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
// no enabled automatic zen rules and no default rules
// so rules should be overridden by default rules
@@ -1739,7 +1802,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -1748,13 +1811,13 @@
assertTrue(rules.containsKey(defaultId));
}
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
}
@Test
public void testReadXmlAllDisabledRulesResetDefaultRules() throws Exception {
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
// all automatic zen rules are disabled on upgrade (and default rules don't already exist)
// so rules should be overriden by default rules
@@ -1775,7 +1838,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -1785,14 +1848,14 @@
}
assertFalse(rules.containsKey("customRule"));
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
}
@Test
@DisableFlags(FLAG_MODES_UI) // modes_ui has only 1 default rule
public void testReadXmlOnlyOneDefaultRuleExists() throws Exception {
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
// all automatic zen rules are disabled on upgrade and only one default rule exists
// so rules should be overriden to the default rules
@@ -1829,7 +1892,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -1839,13 +1902,13 @@
}
assertThat(rules).doesNotContainKey("customRule");
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
}
@Test
public void testReadXmlDefaultRulesExist() throws Exception {
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
// Default rules exist so rules should not be overridden by defaults
ArrayMap<String, ZenModeConfig.ZenRule> automaticRules = new ArrayMap<>();
@@ -1899,7 +1962,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// check default rules
int expectedNumAutoRules = 1 + ZenModeConfig.getDefaultRuleIds().size(); // custom + default
@@ -1910,7 +1973,7 @@
}
assertThat(rules).containsKey("customRule");
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
@@ -1923,7 +1986,7 @@
// When reading XML for something that is already on the modes API system, make sure no
// rules' policies get changed.
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
// Shared for rules
ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRules = new ArrayMap<>();
@@ -1952,10 +2015,10 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// basic check: global config maintained
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
// Find our automatic rules.
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -1972,7 +2035,7 @@
// a custom policy matching the global config for any automatic rule with no specified
// policy.
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>();
ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
@@ -1991,10 +2054,10 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// basic check: global config maintained
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
// Find our automatic rule and check that it has a policy set now
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -2024,7 +2087,7 @@
// underspecified ZenPolicy, we fill in all of the gaps with things from the global config
// in order to maintain consistency of behavior.
setupZenConfig();
- Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
+ Policy originalPolicy = mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT);
ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>();
ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
@@ -2048,10 +2111,10 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// basic check: global config maintained
- assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
+ assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT));
// Find our automatic rule and check that it has a policy set now
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -2114,7 +2177,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
@@ -2166,7 +2229,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// Implicit rule was updated.
assertThat(mZenModeHelper.mConfig.automaticRules.get(implicitRuleBeforeModesUi.id))
@@ -2204,7 +2267,7 @@
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
// Both rules were untouched
assertThat(mZenModeHelper.mConfig.automaticRules.get(implicitRuleWithModesUi.id))
@@ -2345,8 +2408,8 @@
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
// We need the package name to be something that's not "android" so there aren't any
// existing rules under that package.
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
- "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkgname", zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
assertNotNull(id);
}
try {
@@ -2356,8 +2419,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
- "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkgname", zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
// yay
@@ -2377,8 +2440,8 @@
ZenModeConfig.toScheduleConditionId(si),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
- "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkgname", zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
assertNotNull(id);
}
try {
@@ -2388,8 +2451,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
- "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkgname", zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
// yay
@@ -2409,8 +2472,8 @@
ZenModeConfig.toScheduleConditionId(si),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
- "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkgname", zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
assertNotNull(id);
}
try {
@@ -2420,8 +2483,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
- "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkgname", zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
// yay
@@ -2436,8 +2499,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "android", zenRule,
+ ORIGIN_SYSTEM, "test", SYSTEM_UID);
assertTrue(id != null);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -2457,8 +2520,8 @@
new ComponentName("android", "ScheduleConditionProvider"),
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "android", zenRule,
+ ORIGIN_SYSTEM, "test", SYSTEM_UID);
assertTrue(id != null);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -2483,8 +2546,8 @@
new ComponentName("android", "ScheduleConditionProvider"),
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id1 = mZenModeHelper.addAutomaticZenRule("android", zenRule1,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id1 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "android", zenRule1,
+ ORIGIN_SYSTEM, "test", SYSTEM_UID);
// Zen rule with partially-filled policy: should get all of the filled fields set, and the
// rest filled with default state
@@ -2498,8 +2561,8 @@
.showFullScreenIntent(true)
.build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "android", zenRule2,
+ ORIGIN_SYSTEM, "test", SYSTEM_UID);
// rule 1 should exist
assertThat(id1).isNotNull();
@@ -2544,9 +2607,9 @@
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
- CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(zenRule.getConditionId(),
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, null, zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, zenRule.getConditionId(),
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
ORIGIN_APP,
CUSTOM_PKG_UID);
@@ -2564,8 +2627,8 @@
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
- CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, null, zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
AutomaticZenRule zenRule2 = new AutomaticZenRule("NEW",
null,
@@ -2574,7 +2637,8 @@
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- mZenModeHelper.updateAutomaticZenRule(id, zenRule2, ORIGIN_APP, "", CUSTOM_PKG_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, id, zenRule2, ORIGIN_APP, "",
+ CUSTOM_PKG_UID);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
assertEquals("NEW", ruleInConfig.name);
@@ -2589,15 +2653,16 @@
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
- CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, null, zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
assertTrue(id != null);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
assertTrue(ruleInConfig != null);
assertEquals(zenRule.getName(), ruleInConfig.name);
- mZenModeHelper.removeAutomaticZenRule(id, ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id, ORIGIN_APP, "test",
+ CUSTOM_PKG_UID);
assertNull(mZenModeHelper.mConfig.automaticRules.get(id));
}
@@ -2609,16 +2674,16 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
- CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, null, zenRule,
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
assertTrue(id != null);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
assertTrue(ruleInConfig != null);
assertEquals(zenRule.getName(), ruleInConfig.name);
- mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), ORIGIN_APP, "test",
- CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRules(UserHandle.CURRENT, mContext.getPackageName(),
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
assertNull(mZenModeHelper.mConfig.automaticRules.get(id));
}
@@ -2633,18 +2698,18 @@
new ComponentName(mPkg, "ScheduleConditionProvider"),
sharedUri,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mPkg, zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, zenRule,
+ ORIGIN_SYSTEM, "test", SYSTEM_UID);
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
new ComponentName(mPkg, "ScheduleConditionProvider"),
sharedUri,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(mPkg, zenRule2,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, zenRule2,
+ ORIGIN_SYSTEM, "test", SYSTEM_UID);
Condition condition = new Condition(sharedUri, "", STATE_TRUE);
- mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition,
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, sharedUri, condition,
+ ORIGIN_SYSTEM, SYSTEM_UID);
for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
if (rule.id.equals(id)) {
@@ -2658,8 +2723,8 @@
}
condition = new Condition(sharedUri, "", STATE_FALSE);
- mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition,
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, sharedUri, condition,
+ ORIGIN_SYSTEM, SYSTEM_UID);
for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
if (rule.id.equals(id)) {
@@ -2689,14 +2754,15 @@
.setShouldMaximizeDoze(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setDeviceEffects(zde)
.build(),
ORIGIN_APP, "reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(
new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
@@ -2722,14 +2788,15 @@
.setShouldMaximizeDoze(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setDeviceEffects(zde)
.build(),
- ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
}
@@ -2749,7 +2816,8 @@
.setShouldMaximizeDoze(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setDeviceEffects(zde)
@@ -2757,7 +2825,7 @@
ORIGIN_USER_IN_SYSTEMUI,
"reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
}
@@ -2769,26 +2837,27 @@
.setShouldDisableTapToWake(true)
.addExtraEffect("extra")
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", 0);
ZenDeviceEffects updateFromApp = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // Good
.setShouldMaximizeDoze(true) // Bad
.addExtraEffect("should be rejected") // Bad
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setDeviceEffects(updateFromApp)
.build(),
ORIGIN_APP, "reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(
new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // From update.
@@ -2803,24 +2872,25 @@
ZenDeviceEffects original = new ZenDeviceEffects.Builder()
.setShouldDisableTapToWake(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", 0);
ZenDeviceEffects updateFromSystem = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // Good
.setShouldMaximizeDoze(true) // Also good
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setDeviceEffects(updateFromSystem)
.build(),
- ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromSystem);
}
@@ -2830,12 +2900,13 @@
ZenDeviceEffects original = new ZenDeviceEffects.Builder()
.setShouldDisableTapToWake(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", 0);
ZenDeviceEffects updateFromUser = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true)
@@ -2844,13 +2915,13 @@
// even with this line removed, tap to wake would be set to false.
.setShouldDisableTapToWake(false)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setDeviceEffects(updateFromUser)
.build(),
ORIGIN_USER_IN_SYSTEMUI, "reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser);
}
@@ -2860,7 +2931,8 @@
public void updateAutomaticZenRule_nullPolicy_doesNothing() {
// Test that when updateAutomaticZenRule is called with a null policy, nothing changes
// about the existing policy.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setZenPolicy(new ZenPolicy.Builder()
@@ -2869,13 +2941,13 @@
.build(),
ORIGIN_APP, "reasons", 0);
- mZenModeHelper.updateAutomaticZenRule(ruleId,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
// no zen policy
.build(),
ORIGIN_APP, "reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
.isEqualTo(STATE_DISALLOW);
}
@@ -2886,7 +2958,8 @@
// Test that when updating an automatic zen rule with an existing policy, the newly set
// fields overwrite those from the previous policy, but unset fields in the new policy
// keep values from the previous one.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setZenPolicy(new ZenPolicy.Builder()
@@ -2895,9 +2968,9 @@
.allowReminders(true)
.build())
.build(),
- ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", 0);
- mZenModeHelper.updateAutomaticZenRule(ruleId,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setZenPolicy(new ZenPolicy.Builder()
.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
@@ -2905,7 +2978,7 @@
.build(),
ORIGIN_APP, "reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
.isEqualTo(STATE_ALLOW); // from update
assertThat(savedRule.getZenPolicy().getPriorityCallSenders())
@@ -2931,9 +3004,8 @@
AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
.setType(TYPE_BEDTIME)
.build();
- String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime,
- ORIGIN_APP,
- "reason", CUSTOM_PKG_UID);
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg",
+ bedtime, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(bedtimeRuleId);
}
@@ -2952,9 +3024,8 @@
AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
.setType(TYPE_BEDTIME)
.build();
- String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime,
- ORIGIN_APP,
- "reason", CUSTOM_PKG_UID);
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg",
+ bedtime, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID, bedtimeRuleId);
@@ -2974,9 +3045,8 @@
AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
.setType(TYPE_BEDTIME)
.build();
- String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime,
- ORIGIN_APP,
- "reason", CUSTOM_PKG_UID);
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg",
+ bedtime, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID, bedtimeRuleId);
@@ -2995,16 +3065,16 @@
AutomaticZenRule futureBedtime = new AutomaticZenRule.Builder("Bedtime (?)", CONDITION_ID)
.build();
- String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(mPkg, futureBedtime,
- ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg,
+ futureBedtime, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.keySet())
.containsExactly(sleepingRule.id, bedtimeRuleId);
AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime (!)", CONDITION_ID)
.setType(TYPE_BEDTIME)
.build();
- mZenModeHelper.updateAutomaticZenRule(bedtimeRuleId, bedtime, ORIGIN_APP, "reason",
- CUSTOM_PKG_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, bedtimeRuleId, bedtime,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(bedtimeRuleId);
}
@@ -3015,16 +3085,16 @@
setupZenConfig();
// note that caller=null because that's how it comes in from NMS.setZenMode
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// confirm that setting zen mode via setManualZenMode changed the zen mode correctly
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
assertEquals(true, mZenModeHelper.mConfig.manualRule.allowManualInvocation);
// and also that it works to turn it back off again
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
- "", null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, Global.ZEN_MODE_OFF, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode);
}
@@ -3039,22 +3109,21 @@
AutomaticZenRule activeRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String activeRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- activeRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE,
- ORIGIN_APP,
- CUSTOM_PKG_UID);
+ String activeRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), activeRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, activeRuleId,
+ CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
AutomaticZenRule inactiveRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- inactiveRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), inactiveRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
assertWithMessage("Failure for origin " + origin.name())
.that(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
// User turns DND off.
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, origin.value(),
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null, origin.value(),
"snoozing", "systemui", SYSTEM_UID);
assertWithMessage("Failure for origin " + origin.name())
.that(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
@@ -3078,21 +3147,20 @@
AutomaticZenRule activeRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String activeRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- activeRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE,
- ORIGIN_APP,
- CUSTOM_PKG_UID);
+ String activeRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), activeRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, activeRuleId,
+ CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
AutomaticZenRule inactiveRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- inactiveRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), inactiveRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
// User turns DND off.
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, origin.value(),
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null, origin.value(),
"snoozing", "systemui", SYSTEM_UID);
ZenModeConfig config = mZenModeHelper.mConfig;
if (origin == ZenChangeOrigin.ORIGIN_USER_IN_SYSTEMUI) {
@@ -3123,15 +3191,15 @@
setupZenConfig();
// note that caller=null because that's how it comes in from NMS.setZenMode
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// confirm that setting zen mode via setManualZenMode changed the zen mode correctly
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
// and also that it works to turn it back off again
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM, "",
- null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
}
@@ -3144,14 +3212,13 @@
// 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,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "",
- null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "", null, 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
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "", null,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null, ORIGIN_APP, "",
+ null, CUSTOM_PKG_UID);
// In total, this should be 2 loggable changes
assertEquals(2, mZenModeEventLogger.numLoggedChanges());
@@ -3220,22 +3287,21 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_APP, "test", CUSTOM_PKG_UID);
// Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
// Note that pre-modes_ui, this event serves as a test that automatic changes to an app's
// that look like they're coming from the system are attributed to the app, but when
// modes_ui is true, we opt to trust the provided change origin.
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Flags.modesUi() ? ORIGIN_APP : ZenModeConfig.ORIGIN_SYSTEM,
- CUSTOM_PKG_UID);
+ Flags.modesUi() ? ORIGIN_APP : ORIGIN_SYSTEM, CUSTOM_PKG_UID);
// Event 2: "User" turns off the automatic rule (sets it to not enabled)
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(id, zenRule,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "",
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, id, zenRule,
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "",
SYSTEM_UID);
AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
@@ -3244,18 +3310,19 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "test",
+ String systemId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), systemRule,
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "test",
SYSTEM_UID);
// Event 3: turn on the system rule
- mZenModeHelper.setAutomaticZenRuleState(systemId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, systemId,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ ORIGIN_SYSTEM, SYSTEM_UID);
// Event 4: "User" deletes the rule
- mZenModeHelper.removeAutomaticZenRule(systemId,
- Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "",
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, systemId,
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_SYSTEM, "",
SYSTEM_UID);
// In total, this represents 4 events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3311,7 +3378,7 @@
assertFalse(mZenModeEventLogger.getIsUserAction(2));
assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
assertThat(mZenModeEventLogger.getChangeOrigin(2)).isEqualTo(
- Flags.modesUi() ? ZenModeConfig.ORIGIN_SYSTEM : 0);
+ Flags.modesUi() ? ORIGIN_SYSTEM : 0);
// When the system rule is deleted, we consider this a user action that turns DND off
// (again)
@@ -3339,27 +3406,27 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, 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,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
// Event 2: App deactivates the rule automatically (it's 8 AM, bedtime schedule ends)
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
ORIGIN_APP, CUSTOM_PKG_UID);
// Event 3: App activates the rule automatically (it's now 11 PM, bedtime schedule starts)
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
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,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
@@ -3427,21 +3494,23 @@
setupZenConfig();
// First just turn zen mode on
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
ORIGIN_USER_IN_SYSTEMUI, "", null, SYSTEM_UID);
// Now change the policy slightly; want to confirm that this'll be reflected in the logs
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
- mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_ALARMS, 0, 0),
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT,
+ new Policy(PRIORITY_CATEGORY_ALARMS, 0, 0),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
// Turn zen mode off; we want to make sure policy changes do not get logged when zen mode
// is off.
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM, "",
- null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// Change the policy again
- mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0),
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT,
+ new Policy(PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
// Total events: we only expect ones for turning on, changing policy, and turning off
@@ -3485,8 +3554,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// Rule 2, same as rule 1
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -3495,8 +3564,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule2, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// Rule 3, has stricter settings than the default settings
ZenModeConfig ruleConfig = mZenModeHelper.mConfig.copy();
@@ -3507,28 +3576,27 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
ruleConfig.getZenPolicy(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id3 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule3, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// First: turn on rule 1
- mZenModeHelper.setAutomaticZenRuleState(id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
// Second: turn on rule 2
- mZenModeHelper.setAutomaticZenRuleState(id2,
- new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM,
+ SYSTEM_UID);
// Third: turn on rule 3
- mZenModeHelper.setAutomaticZenRuleState(id3,
- new Condition(zenRule3.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id3,
+ new Condition(zenRule3.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM,
+ SYSTEM_UID);
// Fourth: Turn *off* rule 2
- mZenModeHelper.setAutomaticZenRuleState(id2,
- new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_FALSE), ORIGIN_SYSTEM,
+ SYSTEM_UID);
// This should result in a total of four events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3612,8 +3680,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
manualRulePolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ORIGIN_APP, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_APP, "test", SYSTEM_UID);
// Rule 2, same as rule 1 but owned by the system
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -3622,41 +3690,38 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
manualRulePolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- ORIGIN_USER_IN_SYSTEMUI, "test", SYSTEM_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule2, ORIGIN_USER_IN_SYSTEMUI, "test", 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 associated with CUSTOM_PKG_NAME.
// When modes_ui is true: we expect the change origin to be the source of truth.
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Flags.modesUi() ? ORIGIN_APP : ZenModeConfig.ORIGIN_SYSTEM,
- SYSTEM_UID);
+ Flags.modesUi() ? ORIGIN_APP : ORIGIN_SYSTEM, SYSTEM_UID);
// Second: turn on rule 2. This is a system-owned rule and the UID should not be modified
// (nor even looked up; the mock PackageManager won't handle "android" as input).
- mZenModeHelper.setAutomaticZenRuleState(id2,
- new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM,
+ SYSTEM_UID);
// Disable rule 1. Because this looks like a user action, the UID should not be modified
// from the system-provided one unless modes_ui is true.
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(id, zenRule,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, id, zenRule,
ORIGIN_USER_IN_SYSTEMUI, "", SYSTEM_UID);
// Add a manual rule. Any manual rule changes should not get calling uids reassigned.
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ORIGIN_APP,
- "", null, CUSTOM_PKG_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_APP, "", null, CUSTOM_PKG_UID);
// Change rule 2's condition, but from some other UID. Since it doesn't look like it's from
// the system, we keep the UID info.
// Note that this probably shouldn't be able to occur in real scenarios.
- mZenModeHelper.setAutomaticZenRuleState(id2,
- new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
- ORIGIN_APP, 12345);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_FALSE), ORIGIN_APP, 12345);
// That was 5 events total
assertEquals(5, mZenModeEventLogger.numLoggedChanges());
@@ -3712,32 +3777,31 @@
// Turn on zen mode with a manual rule with an enabler set. This should *not* count
// as a user action, and *should* get its UID reassigned.
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ZenModeConfig.ORIGIN_SYSTEM, "", CUSTOM_PKG_NAME, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_SYSTEM, "", CUSTOM_PKG_NAME, SYSTEM_UID);
assertEquals(1, mZenModeEventLogger.numLoggedChanges());
// Now change apps bypassing to true
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
newConfig.areChannelsBypassingDnd = true;
- mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, newConfig.toNotificationPolicy(),
+ ORIGIN_SYSTEM, SYSTEM_UID);
assertEquals(2, mZenModeEventLogger.numLoggedChanges());
// and then back to false, all without changing anything else
newConfig.areChannelsBypassingDnd = false;
- mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, newConfig.toNotificationPolicy(),
+ ORIGIN_SYSTEM, SYSTEM_UID);
assertEquals(3, mZenModeEventLogger.numLoggedChanges());
// Turn off manual mode, call from a package: don't reset UID even though enabler is set
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "",
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null, ORIGIN_APP, "",
CUSTOM_PKG_NAME, 12345);
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
// And likewise when turning it back on again
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ORIGIN_APP,
- "", CUSTOM_PKG_NAME, 12345);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_APP, "", CUSTOM_PKG_NAME, 12345);
// These are 5 events in total.
assertEquals(5, mZenModeEventLogger.numLoggedChanges());
@@ -3782,8 +3846,8 @@
setupZenConfig();
// First just turn zen mode on
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// Now change only the channels part of the policy; want to confirm that this'll be
// reflected in the logs
@@ -3793,8 +3857,8 @@
oldPolicy.priorityMessageSenders, oldPolicy.suppressedVisualEffects,
STATE_PRIORITY_CHANNELS_BLOCKED,
oldPolicy.priorityConversationSenders);
- mZenModeHelper.setNotificationPolicy(newPolicy,
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, newPolicy, ORIGIN_SYSTEM,
+ SYSTEM_UID);
// Total events: one for turning on, one for changing policy
assertThat(mZenModeEventLogger.numLoggedChanges()).isEqualTo(2);
@@ -3834,16 +3898,16 @@
Uri.parse("condition"),
null,
NotificationManager.INTERRUPTION_FILTER_ALL, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_APP, "test", CUSTOM_PKG_UID);
// Event 1: App activates the rule automatically.
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
ORIGIN_APP, CUSTOM_PKG_UID);
// Event 2: App deactivates the rule automatically.
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
ORIGIN_APP, CUSTOM_PKG_UID);
@@ -3876,35 +3940,33 @@
.setInterruptionFilter(INTERRUPTION_FILTER_ALL)
.setType(TYPE_BEDTIME)
.build();
- String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(mPkg, bedtime,
- ORIGIN_APP,
- "reason", CUSTOM_PKG_UID);
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, bedtime,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
// Create immersive rule
AutomaticZenRule immersive = new AutomaticZenRule.Builder("Immersed", CONDITION_ID)
.setType(TYPE_IMMERSIVE)
.setZenPolicy(mZenModeHelper.mConfig.getZenPolicy()) // same as the manual rule
.build();
- String immersiveId = mZenModeHelper.addAutomaticZenRule(mPkg, immersive,
- ORIGIN_APP,
- "reason", CUSTOM_PKG_UID);
+ String immersiveId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, immersive,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
// Event 1: Activate bedtime rule. This doesn't turn on notification filtering
- mZenModeHelper.setAutomaticZenRuleState(bedtimeRuleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, bedtimeRuleId,
new Condition(bedtime.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
ORIGIN_APP, CUSTOM_PKG_UID);
// Event 2: turn on manual zen mode. Manual rule will have ACTIVE_RULE_TYPE_MANUAL
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// Event 3: Turn immersive on
- mZenModeHelper.setAutomaticZenRuleState(immersiveId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, immersiveId,
new Condition(immersive.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
ORIGIN_APP, CUSTOM_PKG_UID);
// Event 4: Turn off bedtime mode, leaving just manual + immersive
- mZenModeHelper.setAutomaticZenRuleState(bedtimeRuleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, bedtimeRuleId,
new Condition(bedtime.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
ORIGIN_APP, CUSTOM_PKG_UID);
@@ -3966,15 +4028,15 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ ORIGIN_SYSTEM, SYSTEM_UID);
- assertEquals(mZenModeHelper.getNotificationPolicy(),
+ assertEquals(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT),
mZenModeHelper.getConsolidatedNotificationPolicy());
// inspect the consolidated policy. Based on setupZenConfig() values.
@@ -4002,13 +4064,13 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null, // null policy
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule
- mZenModeHelper.setAutomaticZenRuleState(id,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ ORIGIN_SYSTEM, SYSTEM_UID);
// inspect the consolidated policy, which should match the device default settings.
assertThat(ZenAdapters.notificationPolicyToZenPolicy(mZenModeHelper.mConsolidatedPolicy))
@@ -4040,13 +4102,12 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule; this will update the consolidated policy
- mZenModeHelper.setAutomaticZenRuleState(id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
// since this is the only active rule, the consolidated policy should match the custom
// policy for every field specified, and take default values (from device default or
@@ -4085,13 +4146,12 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule; this will update the consolidated policy
- mZenModeHelper.setAutomaticZenRuleState(id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
// since this is the only active rule, the consolidated policy should match the custom
// policy for every field specified, and take default values (from either device default
@@ -4125,13 +4185,12 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 1
- mZenModeHelper.setAutomaticZenRuleState(id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
// custom policy for rule 2
ZenPolicy customPolicy = new ZenPolicy.Builder()
@@ -4149,13 +4208,13 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule2, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 2; this will update the consolidated policy
- mZenModeHelper.setAutomaticZenRuleState(id2,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ ORIGIN_SYSTEM, SYSTEM_UID);
// now both rules should be on, and the consolidated policy should reflect the most
// restrictive option of each of the two
@@ -4186,13 +4245,12 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 1
- mZenModeHelper.setAutomaticZenRuleState(id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
// custom policy for rule 2
ZenPolicy customPolicy = new ZenPolicy.Builder()
@@ -4210,13 +4268,13 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule2, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 2; this will update the consolidated policy
- mZenModeHelper.setAutomaticZenRuleState(id2,
- new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM,
+ SYSTEM_UID);
// now both rules should be on, and the consolidated policy should reflect the most
// restrictive option of each of the two
@@ -4252,13 +4310,12 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule; this will update the consolidated policy
- mZenModeHelper.setAutomaticZenRuleState(id,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
// confirm that channels make it through
assertTrue(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
@@ -4274,13 +4331,13 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
strictPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule2, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 2; this will update the consolidated policy
- mZenModeHelper.setAutomaticZenRuleState(id2,
- new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM,
+ SYSTEM_UID);
// rule 2 should override rule 1
assertFalse(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
@@ -4305,9 +4362,9 @@
.allowSystem(true)
.build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String rule1Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRuleWithPriority, ORIGIN_APP, "test", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(rule1Id,
+ String rule1Id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRuleWithPriority, ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, rule1Id,
new Condition(zenRuleWithPriority.getConditionId(), "", STATE_TRUE),
ORIGIN_APP, CUSTOM_PKG_UID);
@@ -4318,9 +4375,9 @@
Uri.parse("priority"),
new ZenPolicy.Builder().disallowAllSounds().build(),
NotificationManager.INTERRUPTION_FILTER_ALL, true);
- String rule2Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRuleWithAll, ORIGIN_APP, "test", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(rule2Id,
+ String rule2Id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRuleWithAll, ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, rule2Id,
new Condition(zenRuleWithPriority.getConditionId(), "", STATE_TRUE),
ORIGIN_APP, CUSTOM_PKG_UID);
@@ -4364,7 +4421,7 @@
rule.triggerDescription = TRIGGER_DESC;
mZenModeHelper.mConfig.automaticRules.put(rule.id, rule);
- AutomaticZenRule actual = mZenModeHelper.getAutomaticZenRule(rule.id);
+ AutomaticZenRule actual = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, rule.id);
assertEquals(NAME, actual.getName());
assertEquals(OWNER, actual.getOwner());
@@ -4400,8 +4457,8 @@
.setManualInvocationAllowed(ALLOW_MANUAL)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(OWNER.getPackageName(), azr,
- ORIGIN_APP, "add", CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ OWNER.getPackageName(), azr, ORIGIN_APP, "add", CUSTOM_PKG_UID);
ZenModeConfig.ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -4430,17 +4487,17 @@
AutomaticZenRule azrBase = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// Checks the name can be changed by the app because the user has not modified it.
AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
.setName("NewName")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP,
+ "reason", SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(rule.getName()).isEqualTo("NewName");
// The user modifies some other field in the rule, which makes the rule as a whole not
@@ -4448,35 +4505,35 @@
azrUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_USER_IN_SYSTEMUI, "reason",
- SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
// ...but the app can still modify the name, because the name itself hasn't been modified
// by the user.
azrUpdate = new AutomaticZenRule.Builder(rule)
.setName("NewAppName")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP,
+ "reason", SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(rule.getName()).isEqualTo("NewAppName");
// The user modifies the name.
azrUpdate = new AutomaticZenRule.Builder(rule)
.setName("UserProvidedName")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_USER_IN_SYSTEMUI, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(rule.getName()).isEqualTo("UserProvidedName");
// The app is no longer able to modify the name.
azrUpdate = new AutomaticZenRule.Builder(rule)
.setName("NewAppName")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP,
+ "reason", SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(rule.getName()).isEqualTo("UserProvidedName");
}
@@ -4490,9 +4547,9 @@
.setDeviceEffects(new ZenDeviceEffects.Builder().build())
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// Modifies the filter, icon, zen policy, and device effects
ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
@@ -4510,9 +4567,9 @@
.build();
// Update the rule with the AZR from origin user.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_USER_IN_SYSTEMUI, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// UPDATE_ORIGIN_USER should change the bitmask and change the values.
assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
@@ -4547,9 +4604,9 @@
.build())
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// Modifies the icon, zen policy and device effects
ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
@@ -4567,9 +4624,9 @@
.build();
// Update the rule with the AZR from origin systemUI.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ZenModeConfig.ORIGIN_SYSTEM,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_SYSTEM,
"reason", SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI should change the value but NOT update the bitmask.
assertThat(rule.getIconResId()).isEqualTo(ICON_RES_ID);
@@ -4597,9 +4654,9 @@
.build())
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
ZenPolicy policy = new ZenPolicy.Builder()
.allowReminders(true)
@@ -4615,8 +4672,8 @@
// Since the rule is not already user modified, UPDATE_ORIGIN_APP can modify the rule.
// The bitmask is not modified.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
- SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP,
+ "reason", SYSTEM_UID);
ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(storedRule.userModifiedFields).isEqualTo(0);
@@ -4630,8 +4687,8 @@
assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(0);
// Creates another rule, this time from user. This will have user modified bits set.
- String ruleIdUser = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
+ String ruleIdUser = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser);
int ruleModifiedFields = storedRule.userModifiedFields;
int rulePolicyModifiedFields = storedRule.zenPolicyUserModifiedFields;
@@ -4639,9 +4696,10 @@
// Zen rule update coming from the app again. This cannot fully update the rule, because
// the rule is already considered user modified.
- mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, ORIGIN_APP,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleIdUser, azrUpdate, ORIGIN_APP,
"reason", SYSTEM_UID);
- AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
+ AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ ruleIdUser);
// The app can only change the value if the rule is not already user modified,
// so the rule is not changed, and neither is the bitmask.
@@ -4670,9 +4728,9 @@
.setShouldDisplayGrayscale(true)
.build())
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// The values are modified but the bitmask is not.
assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
@@ -4692,8 +4750,8 @@
.setDeviceEffects(zde)
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
// Sets Device Effects to null
@@ -4702,9 +4760,9 @@
// Zen rule update coming from app, but since the rule isn't already
// user modified, it can be updated.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_APP, "reason",
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr, ORIGIN_APP, "reason",
SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// When AZR's ZenDeviceEffects is null, the updated rule's device effects are kept.
assertThat(rule.getDeviceEffects()).isEqualTo(zde);
@@ -4718,9 +4776,9 @@
.setZenPolicy(POLICY)
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
// Set zen policy to null
@@ -4729,9 +4787,9 @@
// Zen rule update coming from app, but since the rule isn't already
// user modified, it can be updated.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_APP, "reason",
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr, ORIGIN_APP, "reason",
SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// When AZR's ZenPolicy is null, we expect the updated rule's policy to be unchanged
// (equivalent to the provided policy, with additional fields filled in with defaults).
@@ -4750,9 +4808,9 @@
// .setDeviceEffects(new ZenDeviceEffects.Builder().build())
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// Create a fully populated ZenPolicy.
ZenPolicy policy = new ZenPolicy.Builder()
@@ -4780,9 +4838,9 @@
// Applies the update to the rule.
// Default config defined in getDefaultConfigParser() is used as the original rule.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_USER_IN_SYSTEMUI, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// New ZenPolicy differs from the default config
assertThat(rule.getZenPolicy()).isNotNull();
@@ -4811,9 +4869,9 @@
.setDeviceEffects(null)
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
@@ -4823,9 +4881,9 @@
.build();
// Applies the update to the rule.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_USER_IN_SYSTEMUI, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
// New ZenDeviceEffects is used; all fields considered set, since previously were null.
assertThat(rule.getDeviceEffects()).isNotNull();
@@ -4848,8 +4906,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ final String createdId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[1];
@@ -4865,7 +4923,7 @@
mZenModeHelper.addCallback(callback);
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, ZenModeConfig.ORIGIN_SYSTEM,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, createdId, zenRule, ORIGIN_SYSTEM,
"", SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
@@ -4883,8 +4941,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, false);
- final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ final String createdId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[1];
@@ -4900,7 +4958,7 @@
mZenModeHelper.addCallback(callback);
zenRule.setEnabled(true);
- mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, ZenModeConfig.ORIGIN_SYSTEM,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, createdId, zenRule, ORIGIN_SYSTEM,
"", SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
@@ -4919,8 +4977,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ final String createdId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[1];
@@ -4935,9 +4993,9 @@
};
mZenModeHelper.addCallback(callback);
- mZenModeHelper.setAutomaticZenRuleState(createdId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, createdId,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ ORIGIN_SYSTEM, SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
@@ -4959,8 +5017,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ final String createdId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[2];
@@ -4977,12 +5035,11 @@
};
mZenModeHelper.addCallback(callback);
- mZenModeHelper.setAutomaticZenRuleState(createdId,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, createdId,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
- null, "", SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, Global.ZEN_MODE_OFF, null,
+ ORIGIN_SYSTEM, null, "", SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
@@ -5004,8 +5061,8 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ final String createdId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[2];
@@ -5022,13 +5079,12 @@
};
mZenModeHelper.addCallback(callback);
- mZenModeHelper.setAutomaticZenRuleState(createdId,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, createdId,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
- mZenModeHelper.setAutomaticZenRuleState(createdId,
- new Condition(zenRule.getConditionId(), "", STATE_FALSE),
- ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, createdId,
+ new Condition(zenRule.getConditionId(), "", STATE_FALSE), ORIGIN_APP,
+ CUSTOM_PKG_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
@@ -5049,21 +5105,20 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
+ final String createdId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), zenRule, ORIGIN_SYSTEM, "test", SYSTEM_UID);
// Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
- mZenModeHelper.setAutomaticZenRuleState(createdId,
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, createdId,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE), ORIGIN_SYSTEM, SYSTEM_UID);
// Event 2: Snooze rule by turning off DND
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
- "", null, SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, Global.ZEN_MODE_OFF, null,
+ ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// Event 3: "User" turns off the automatic rule (sets it to not enabled)
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, ZenModeConfig.ORIGIN_SYSTEM,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, createdId, zenRule, ORIGIN_SYSTEM,
"", SYSTEM_UID);
assertEquals(OVERRIDE_NONE,
@@ -5078,17 +5133,17 @@
.setConfigurationActivity(new ComponentName(mPkg, "cls"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
- CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
AutomaticZenRule updateWithDiff = new AutomaticZenRule.Builder(rule)
.setTriggerDescription("Whenever")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, ORIGIN_APP, "reason",
- CUSTOM_PKG_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, updateWithDiff,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isNull();
@@ -5102,15 +5157,15 @@
.setConfigurationActivity(new ComponentName(mPkg, "cls"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
- CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
AutomaticZenRule updateUnchanged = new AutomaticZenRule.Builder(rule).build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, updateUnchanged, ORIGIN_APP, "reason",
- CUSTOM_PKG_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, updateUnchanged,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
@@ -5125,17 +5180,17 @@
.setConfigurationActivity(new ComponentName(mPkg, "cls"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
- CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
AutomaticZenRule updateWithDiff = new AutomaticZenRule.Builder(rule)
.setTriggerDescription("Whenever")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, ORIGIN_USER_IN_SYSTEMUI,
- "reason", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, updateWithDiff,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
@@ -5153,17 +5208,16 @@
.setConfigurationActivity(new ComponentName(mPkg, "cls"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId =
- mZenModeHelper.addAutomaticZenRule(
- mPkg, rule, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(
- ruleId, CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
AutomaticZenRule updateWithDiff =
new AutomaticZenRule.Builder(rule).setTriggerDescription("Whenever").build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, ORIGIN_USER_IN_SYSTEMUI,
- "reason", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, updateWithDiff,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
@@ -5178,16 +5232,16 @@
.setConfigurationActivity(new ComponentName(mPkg, "cls"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
- CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- mZenModeHelper.updateAutomaticZenRule(ruleId,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder(rule).setEnabled(false).build(),
ORIGIN_USER_IN_SYSTEMUI, "disable", SYSTEM_UID);
- mZenModeHelper.updateAutomaticZenRule(ruleId,
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder(rule).setEnabled(true).build(),
ORIGIN_USER_IN_SYSTEMUI, "enable", SYSTEM_UID);
@@ -5203,16 +5257,16 @@
AutomaticZenRule original = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName("android", "some.old.cps"))
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule("android", original,
- ZenModeConfig.ORIGIN_SYSTEM, "reason", SYSTEM_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "android", original,
+ ORIGIN_SYSTEM, "reason", SYSTEM_UID);
AutomaticZenRule update = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName("android", "brand.new.cps"))
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, update, ORIGIN_USER_IN_SYSTEMUI, "reason",
- SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, update,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
- AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(result).isNotNull();
assertThat(result.getOwner().getClassName()).isEqualTo("brand.new.cps");
}
@@ -5223,16 +5277,16 @@
AutomaticZenRule original = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName(mContext.getPackageName(), "old.third.party.cps"))
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), original,
- ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), original, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
AutomaticZenRule update = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName(mContext.getPackageName(), "new.third.party.cps"))
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, update, ORIGIN_USER_IN_SYSTEMUI, "reason",
- SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, update,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
- AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
assertThat(result).isNotNull();
assertThat(result.getOwner().getClassName()).isEqualTo("old.third.party.cps");
}
@@ -5247,14 +5301,14 @@
.setShouldSuppressAmbientDisplay(true)
.setShouldDimWallpaper(true)
.build());
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(any(), eq(ORIGIN_APP));
// Now delete the (currently active!) rule. For example, assume this is done from settings.
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_USER_IN_SYSTEMUI, "remove",
- SYSTEM_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_USER_IN_SYSTEMUI,
+ "remove", SYSTEM_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_USER_IN_SYSTEMUI));
@@ -5273,8 +5327,8 @@
String ruleId = addRuleWithEffects(effects);
verifyNoMoreInteractions(mDeviceEffectsApplier);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(eq(effects), eq(ORIGIN_APP));
@@ -5287,13 +5341,13 @@
ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
String ruleId = addRuleWithEffects(zde);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(eq(zde), eq(ORIGIN_APP));
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_FALSE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_APP));
@@ -5310,8 +5364,8 @@
.setShouldDisplayGrayscale(true)
.addExtraEffect("ONE")
.build());
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(
eq(new ZenDeviceEffects.Builder()
@@ -5326,8 +5380,8 @@
.setShouldDimWallpaper(true)
.addExtraEffect("TWO")
.build());
- mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, secondRuleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(
@@ -5350,15 +5404,15 @@
.addExtraEffect("extra_effect")
.build();
String ruleId = addRuleWithEffects(zde);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(eq(zde), eq(ORIGIN_APP));
// Now create and activate a second rule that doesn't add any more effects.
String secondRuleId = addRuleWithEffects(zde);
- mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, secondRuleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verifyNoMoreInteractions(mDeviceEffectsApplier);
@@ -5371,8 +5425,8 @@
String ruleId = addRuleWithEffects(zde);
verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
@@ -5412,8 +5466,8 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setDeviceEffects(effects)
.build();
- return mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ZenModeConfig.ORIGIN_SYSTEM, "reasons", SYSTEM_UID);
+ return mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(),
+ rule, ORIGIN_SYSTEM, "reasons", SYSTEM_UID);
}
@Test
@@ -5426,35 +5480,37 @@
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime())
+ .isEqualTo(1000);
// User customizes it.
AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
- "userUpdate", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "userUpdate", SYSTEM_UID);
// App deletes it.
mTestClock.advanceByMillis(1000);
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
// App adds it again.
mTestClock.advanceByMillis(1000);
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// Verify that the rule was restored:
// - id and creation time is the same as the original one.
// - ZenPolicy is the one that the user had set.
// - rule still has the user-modified fields.
- AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ newRuleId);
assertThat(finalRule.getCreationTime()).isEqualTo(1000); // And not 3000.
assertThat(newRuleId).isEqualTo(ruleId);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
@@ -5481,24 +5537,26 @@
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime())
+ .isEqualTo(1000);
// App deletes it.
mTestClock.advanceByMillis(1000);
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
// App adds it again.
mTestClock.advanceByMillis(1000);
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// Verify that the rule was recreated. This means id and creation time are new.
- AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ newRuleId);
assertThat(finalRule.getCreationTime()).isEqualTo(3000);
assertThat(newRuleId).isNotEqualTo(ruleId);
}
@@ -5513,9 +5571,10 @@
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime())
+ .isEqualTo(1000);
// User customizes it.
mTestClock.advanceByMillis(1000);
@@ -5523,24 +5582,26 @@
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
- "userUpdate", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "userUpdate", SYSTEM_UID);
// App deletes it.
mTestClock.advanceByMillis(1000);
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
// User creates it again (unusual case, but ok).
mTestClock.advanceByMillis(1000);
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_USER_IN_SYSTEMUI, "add it anew", SYSTEM_UID);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_USER_IN_SYSTEMUI, "add it anew",
+ SYSTEM_UID);
// Verify that the rule was recreated. This means id and creation time are new, and the rule
// matches the latest data supplied to addAZR.
- AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ newRuleId);
assertThat(finalRule.getCreationTime()).isEqualTo(4000);
assertThat(newRuleId).isNotEqualTo(ruleId);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
@@ -5561,9 +5622,10 @@
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime())
+ .isEqualTo(1000);
// User customizes it.
mTestClock.advanceByMillis(1000);
@@ -5571,23 +5633,24 @@
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
- "userUpdate", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "userUpdate", SYSTEM_UID);
// User deletes it.
mTestClock.advanceByMillis(1000);
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_USER_IN_SYSTEMUI, "delete it",
- SYSTEM_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_USER_IN_SYSTEMUI,
+ "delete it", SYSTEM_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
// App creates it again.
mTestClock.advanceByMillis(1000);
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// Verify that the rule was recreated. This means id and creation time are new.
- AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ newRuleId);
assertThat(finalRule.getCreationTime()).isEqualTo(4000);
assertThat(newRuleId).isNotEqualTo(ruleId);
}
@@ -5601,18 +5664,18 @@
.setOwner(new ComponentName("first", "owner"))
.setInterruptionFilter(INTERRUPTION_FILTER_ALL)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
// User customizes it.
AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
- "userUpdate", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "userUpdate", SYSTEM_UID);
// App deletes it. It's preserved for a possible restoration.
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
@@ -5622,12 +5685,14 @@
.setOwner(new ComponentName("second", "owner"))
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- readdingWithDifferentOwner, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), readdingWithDifferentOwner, ORIGIN_APP, "add it again",
+ CUSTOM_PKG_UID);
// Verify that the rule was NOT restored:
assertThat(newRuleId).isNotEqualTo(ruleId);
- AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ newRuleId);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
assertThat(finalRule.getOwner()).isEqualTo(new ComponentName("second", "owner"));
@@ -5643,23 +5708,23 @@
mZenModeHelper.mConfig.automaticRules.clear();
// Start with a bunch of customized rules where conditionUris are not unique.
- String id1 = mZenModeHelper.addAutomaticZenRule("pkg1",
+ String id1 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg1",
new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(),
ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
- String id2 = mZenModeHelper.addAutomaticZenRule("pkg1",
+ String id2 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg1",
new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(),
ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
- String id3 = mZenModeHelper.addAutomaticZenRule("pkg1",
+ String id3 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg1",
new AutomaticZenRule.Builder("Test3", Uri.parse("uri2")).build(),
ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
- String id4 = mZenModeHelper.addAutomaticZenRule("pkg2",
+ String id4 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg2",
new AutomaticZenRule.Builder("Test4", Uri.parse("uri1")).build(),
ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
- String id5 = mZenModeHelper.addAutomaticZenRule("pkg2",
+ String id5 = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, "pkg2",
new AutomaticZenRule.Builder("Test5", Uri.parse("uri1")).build(),
ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
@@ -5667,11 +5732,16 @@
zenRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
}
- mZenModeHelper.removeAutomaticZenRule(id1, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
- mZenModeHelper.removeAutomaticZenRule(id2, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
- mZenModeHelper.removeAutomaticZenRule(id3, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
- mZenModeHelper.removeAutomaticZenRule(id4, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
- mZenModeHelper.removeAutomaticZenRule(id5, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id1, ORIGIN_APP, "begone",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id2, ORIGIN_APP, "begone",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id3, ORIGIN_APP, "begone",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id4, ORIGIN_APP, "begone",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id5, ORIGIN_APP, "begone",
+ CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.deletedRules.keySet())
.containsExactly("pkg1|uri1", "pkg1|uri2", "pkg2|uri1");
@@ -5685,11 +5755,11 @@
public void removeAllZenRules_preservedForRestoring() {
mZenModeHelper.mConfig.automaticRules.clear();
- mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(),
new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(),
ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
- mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(),
new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(),
ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
@@ -5698,8 +5768,8 @@
zenRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
}
- mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), ORIGIN_APP,
- "begone", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRules(UserHandle.CURRENT, mContext.getPackageName(),
+ ORIGIN_APP, "begone", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(2);
}
@@ -5716,8 +5786,8 @@
mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg1Rule), pkg1Rule);
mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg2Rule), pkg2Rule);
- mZenModeHelper.removeAutomaticZenRules("pkg1",
- ZenModeConfig.ORIGIN_SYSTEM, "goodbye pkg1", SYSTEM_UID);
+ mZenModeHelper.removeAutomaticZenRules(UserHandle.CURRENT, "pkg1",
+ ORIGIN_SYSTEM, "goodbye pkg1", SYSTEM_UID);
// Preserved rules from pkg1 are gone; those from pkg2 are still there.
assertThat(mZenModeHelper.mConfig.deletedRules.values().stream().map(r -> r.pkg)
@@ -5733,36 +5803,37 @@
.setConditionId(CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
// User customizes it.
AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
- "userUpdate", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "userUpdate", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
// App activates it.
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
// App deletes it.
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
// App adds it again.
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// The rule is restored...
assertThat(newRuleId).isEqualTo(ruleId);
- AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ newRuleId);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
// ... but it is NOT active
@@ -5782,40 +5853,41 @@
.setConditionId(CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
// User customizes it.
AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
- "userUpdate", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdate,
+ ORIGIN_USER_IN_SYSTEMUI, "userUpdate", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
// App activates it.
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
// User snoozes it.
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null, ORIGIN_SYSTEM,
"snoozing", "systemui", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
// App deletes it.
- mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
// App adds it again.
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), rule, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// The rule is restored...
assertThat(newRuleId).isEqualTo(ruleId);
- AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+ AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ newRuleId);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
// ... but it is NEITHER active NOR snoozed.
@@ -5888,7 +5960,8 @@
@Test
@EnableFlags(FLAG_MODES_API)
public void getAutomaticZenRuleState_ownedRule_returnsRuleState() {
- String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setConfigurationActivity(
new ComponentName(mContext.getPackageName(), "Blah"))
@@ -5896,18 +5969,23 @@
ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
// Null condition -> STATE_FALSE
- assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_FALSE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id))
+ .isEqualTo(Condition.STATE_FALSE);
- mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_TRUE, ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_TRUE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id))
+ .isEqualTo(Condition.STATE_TRUE);
- mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_FALSE, ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, CONDITION_FALSE, ORIGIN_APP,
CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_FALSE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id))
+ .isEqualTo(Condition.STATE_FALSE);
- mZenModeHelper.removeAutomaticZenRule(id, ORIGIN_APP, "", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_UNKNOWN);
+ mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id, ORIGIN_APP, "",
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id))
+ .isEqualTo(Condition.STATE_UNKNOWN);
}
@Test
@@ -5922,8 +6000,8 @@
mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
- assertThat(mZenModeHelper.getAutomaticZenRuleState("systemRule")).isEqualTo(
- Condition.STATE_UNKNOWN);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, "systemRule"))
+ .isEqualTo(Condition.STATE_UNKNOWN);
}
@Test
@@ -5940,7 +6018,7 @@
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
// Should be ignored.
- mZenModeHelper.setAutomaticZenRuleState("otherRule",
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, "otherRule",
new Condition(otherRule.conditionId, "off", Condition.STATE_FALSE),
ORIGIN_APP, CUSTOM_PKG_UID);
@@ -5961,7 +6039,7 @@
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
// Should be ignored.
- mZenModeHelper.setAutomaticZenRuleState(otherRule.conditionId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, otherRule.conditionId,
new Condition(otherRule.conditionId, "off", Condition.STATE_FALSE),
ORIGIN_APP, CUSTOM_PKG_UID);
@@ -5972,7 +6050,8 @@
@EnableFlags(FLAG_MODES_API)
public void testCallbacks_policy() throws Exception {
setupZenConfig();
- assertThat(mZenModeHelper.getNotificationPolicy().allowReminders()).isTrue();
+ assertThat(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).allowReminders())
+ .isTrue();
SettableFuture<Policy> futurePolicy = SettableFuture.create();
mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@Override
@@ -5982,7 +6061,8 @@
});
Policy totalSilencePolicy = new Policy(0, 0, 0);
- mZenModeHelper.setNotificationPolicy(totalSilencePolicy, ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, totalSilencePolicy, ORIGIN_APP,
+ CUSTOM_PKG_UID);
Policy callbackPolicy = futurePolicy.get(1, TimeUnit.SECONDS);
assertThat(callbackPolicy.allowReminders()).isFalse();
@@ -6000,13 +6080,14 @@
}
});
- String totalSilenceRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String totalSilenceRuleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setOwner(OWNER)
.setInterruptionFilter(INTERRUPTION_FILTER_NONE)
.build(),
ORIGIN_APP, "reasons", 0);
- mZenModeHelper.setAutomaticZenRuleState(totalSilenceRuleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, totalSilenceRuleId,
new Condition(CONDITION_ID, "", STATE_TRUE), ORIGIN_APP, CUSTOM_PKG_UID);
Policy callbackPolicy = futureConsolidatedPolicy.get(1, TimeUnit.SECONDS);
@@ -6018,8 +6099,8 @@
public void applyGlobalZenModeAsImplicitZenRule_createsImplicitRuleAndActivatesIt() {
mZenModeHelper.mConfig.automaticRules.clear();
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules.values())
.comparingElementsUsing(IGNORE_METADATA)
@@ -6034,13 +6115,14 @@
public void applyGlobalZenModeAsImplicitZenRule_updatesImplicitRuleAndActivatesIt() {
mZenModeHelper.mConfig.automaticRules.clear();
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "test", "test", 0);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null, ORIGIN_APP, "test",
+ "test", 0);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_ALARMS);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_ALARMS);
assertThat(mZenModeHelper.mConfig.automaticRules.values())
.comparingElementsUsing(IGNORE_METADATA)
@@ -6057,22 +6139,22 @@
String pkg = mContext.getPackageName();
// From app, call "setInterruptionFilter" and create and implicit rule.
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
ZEN_MODE_IMPORTANT_INTERRUPTIONS);
String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
.isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
// From user, update that rule's interruption filter.
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
- "reason", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdateRule,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
// From app, call "setInterruptionFilter" again.
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
ZEN_MODE_NO_INTERRUPTIONS);
// The app's update was ignored, and the user's update is still current, and the current
@@ -6089,22 +6171,22 @@
String pkg = mContext.getPackageName();
// From app, call "setInterruptionFilter" and create and implicit rule.
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
ZEN_MODE_IMPORTANT_INTERRUPTIONS);
String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
.isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
// From user, update something in that rule, but not the interruption filter.
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setName("Renamed")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
- "reason", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdateRule,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
// From app, call "setInterruptionFilter" again.
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
ZEN_MODE_NO_INTERRUPTIONS);
// The app's update was accepted, and the current mode is the one that they wanted.
@@ -6117,13 +6199,13 @@
@EnableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_modeOff_deactivatesImplicitRule() {
mZenModeHelper.mConfig.automaticRules.clear();
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(mPkg, CUSTOM_PKG_UID,
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, mPkg, CUSTOM_PKG_UID,
ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state)
.isEqualTo(STATE_TRUE);
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(mPkg, CUSTOM_PKG_UID,
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, mPkg, CUSTOM_PKG_UID,
ZEN_MODE_OFF);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state)
@@ -6135,8 +6217,8 @@
public void applyGlobalZenModeAsImplicitZenRule_modeOffButNoPreviousRule_ignored() {
mZenModeHelper.mConfig.automaticRules.clear();
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_OFF);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_OFF);
assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
}
@@ -6146,18 +6228,19 @@
public void applyGlobalZenModeAsImplicitZenRule_update_unsnoozesRule() {
mZenModeHelper.mConfig.automaticRules.clear();
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride())
.isEqualTo(OVERRIDE_NONE);
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "test", "test", 0);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, null, ORIGIN_APP, "test",
+ "test", 0);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride())
.isEqualTo(OVERRIDE_DEACTIVATE);
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_ALARMS);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_ALARMS);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride())
.isEqualTo(OVERRIDE_NONE);
@@ -6166,14 +6249,57 @@
}
@Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void applyGlobalZenModeAsImplicitZenRule_again_refreshesRuleName() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("Do Not Disturb (" + CUSTOM_APP_LABEL + ")");
+ // "Break" the rule name to check that applying again restores it.
+ mZenModeHelper.mConfig.automaticRules.valueAt(0).name = "BOOM!";
+
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, ZEN_MODE_ALARMS);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("Do Not Disturb (" + CUSTOM_APP_LABEL + ")");
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void applyGlobalZenModeAsImplicitZenRule_again_doesNotChangeCustomizedRuleName() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("Do Not Disturb (" + CUSTOM_APP_LABEL + ")");
+ String ruleId = ZenModeConfig.implicitRuleId(mContext.getPackageName());
+
+ // User chooses a new name.
+ AutomaticZenRule azr = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ new AutomaticZenRule.Builder(azr).setName("User chose this").build(),
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
+
+ // App triggers the rule again.
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, ZEN_MODE_ALARMS);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("User chose this");
+ }
+
+ @Test
@DisableFlags(FLAG_MODES_API)
public void applyGlobalZenModeAsImplicitZenRule_flagOff_ignored() {
mZenModeHelper.mConfig.automaticRules.clear();
withoutWtfCrash(
- () -> mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME,
- CUSTOM_PKG_UID,
- ZEN_MODE_IMPORTANT_INTERRUPTIONS));
+ () -> mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT,
+ CUSTOM_PKG_NAME, CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS));
assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
}
@@ -6186,7 +6312,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(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, policy);
ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
.disallowAllSounds()
@@ -6210,14 +6337,15 @@
Policy original = 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,
- original);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, original);
// 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(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, updated);
ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
.disallowAllSounds()
@@ -6241,7 +6369,8 @@
// From app, call "setNotificationPolicy" and create and implicit rule.
Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
+ originalPolicy);
String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
// Store this for checking later.
@@ -6249,18 +6378,19 @@
mZenModeHelper.mConfig.getZenPolicy()).allowMedia(true).build();
// From user, update that rule's policy.
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
ZenPolicy userUpdateZenPolicy = new ZenPolicy.Builder().disallowAllSounds()
.allowAlarms(true).build();
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setZenPolicy(userUpdateZenPolicy)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
- "reason", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdateRule,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
// From app, call "setNotificationPolicy" again.
Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
+ appUpdatePolicy);
// The app's update was ignored, and the user's update is still current.
assertThat(mZenModeHelper.mConfig.automaticRules.values())
@@ -6281,7 +6411,8 @@
// From app, call "setNotificationPolicy" and create and implicit rule.
Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
+ originalPolicy);
String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
// Store this for checking later.
@@ -6289,16 +6420,17 @@
mZenModeHelper.mConfig.getZenPolicy()).allowMedia(true).build();
// From user, update something in that rule, but not the ZenPolicy.
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setName("Rule renamed, not touching policy")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
- "reason", SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, userUpdateRule,
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
// From app, call "setNotificationPolicy" again.
Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, pkg, CUSTOM_PKG_UID,
+ appUpdatePolicy);
// The app's update was applied.
ZenPolicy appsSecondZenPolicy = new ZenPolicy.Builder()
@@ -6311,13 +6443,57 @@
}
@Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void applyGlobalPolicyAsImplicitZenRule_again_refreshesRuleName() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, new Policy(0, 0, 0));
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("Do Not Disturb (" + CUSTOM_APP_LABEL + ")");
+ // "Break" the rule name to check that updating it again restores it.
+ mZenModeHelper.mConfig.automaticRules.valueAt(0).name = "BOOM!";
+
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, new Policy(0, 0, 0));
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("Do Not Disturb (" + CUSTOM_APP_LABEL + ")");
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void applyGlobalPolicyAsImplicitZenRule_again_doesNotChangeCustomizedRuleName() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, new Policy(0, 0, 0));
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("Do Not Disturb (" + CUSTOM_APP_LABEL + ")");
+ String ruleId = ZenModeConfig.implicitRuleId(mContext.getPackageName());
+
+ // User chooses a new name.
+ AutomaticZenRule azr = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ new AutomaticZenRule.Builder(azr).setName("User chose this").build(),
+ ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
+
+ // App updates the implicit rule again.
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(), CUSTOM_PKG_UID, new Policy(0, 0, 0));
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).name)
+ .isEqualTo("User chose this");
+ }
+
+ @Test
@DisableFlags(FLAG_MODES_API)
public void applyGlobalPolicyAsImplicitZenRule_flagOff_ignored() {
mZenModeHelper.mConfig.automaticRules.clear();
withoutWtfCrash(
- () -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME,
- CUSTOM_PKG_UID, new Policy(0, 0, 0)));
+ () -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT,
+ CUSTOM_PKG_NAME, CUSTOM_PKG_UID, new Policy(0, 0, 0)));
assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
}
@@ -6329,11 +6505,11 @@
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), STATE_FALSE,
CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- writtenPolicy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, writtenPolicy);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
- CUSTOM_PKG_NAME);
+ UserHandle.CURRENT, CUSTOM_PKG_NAME);
assertThat(readPolicy).isEqualTo(writtenPolicy);
}
@@ -6343,15 +6519,15 @@
@DisableFlags(FLAG_MODES_UI)
public void getNotificationPolicyFromImplicitZenRule_ruleWithoutPolicy_copiesGlobalPolicy() {
// Implicit rule will get the global policy at the time of rule creation.
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(UserHandle.CURRENT, CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
// If the policy then changes afterwards, it should inherit updates because user cannot
// edit the policy in the UI.
- mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_ALARMS, 0, 0),
- ORIGIN_APP, 1);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT,
+ new Policy(PRIORITY_CATEGORY_ALARMS, 0, 0), ORIGIN_APP, 1);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
- CUSTOM_PKG_NAME);
+ UserHandle.CURRENT, CUSTOM_PKG_NAME);
assertThat(readPolicy).isNotNull();
assertThat(readPolicy.allowCalls()).isFalse();
@@ -6362,10 +6538,11 @@
@EnableFlags(FLAG_MODES_API)
public void getNotificationPolicyFromImplicitZenRule_noImplicitRule_returnsGlobalPolicy() {
Policy policy = new Policy(PRIORITY_CATEGORY_CALLS, PRIORITY_SENDERS_STARRED, 0);
- mZenModeHelper.setNotificationPolicy(policy, ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, policy, ORIGIN_APP,
+ CUSTOM_PKG_UID);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
- CUSTOM_PKG_NAME);
+ UserHandle.CURRENT, CUSTOM_PKG_NAME);
assertThat(readPolicy).isNotNull();
assertThat(readPolicy.allowCalls()).isTrue();
@@ -6398,7 +6575,8 @@
ZEN_MODE_IMPORTANT_INTERRUPTIONS, previousManualZenPolicy);
Policy newManualPolicy = new Policy(PRIORITY_CATEGORY_EVENTS, 0, 0);
- mZenModeHelper.setNotificationPolicy(newManualPolicy, ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, newManualPolicy, ORIGIN_APP,
+ CUSTOM_PKG_UID);
ZenPolicy newManualZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(newManualPolicy);
// Only app rules with default or same-as-manual policies were updated.
@@ -6426,10 +6604,12 @@
when(mResources.getResourceName(resourceId)).thenReturn(veryLongResourceName);
when(mResources.getIdentifier(veryLongResourceName, null, null)).thenReturn(resourceId);
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
+ mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID).setIconResId(resourceId).build(),
ORIGIN_APP, "reason", CUSTOM_PKG_UID);
- AutomaticZenRule storedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule storedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
+ ruleId);
assertThat(storedRule.getIconResId()).isEqualTo(0);
}
@@ -6440,8 +6620,8 @@
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
.build();
- mZenModeHelper.setManualZenRuleDeviceEffects(effects, ORIGIN_USER_IN_SYSTEMUI, "settings",
- SYSTEM_UID);
+ mZenModeHelper.setManualZenRuleDeviceEffects(UserHandle.CURRENT, effects,
+ ORIGIN_USER_IN_SYSTEMUI, "settings", SYSTEM_UID);
assertThat(mZenModeHelper.getConfig().manualRule).isNotNull();
assertThat(mZenModeHelper.getConfig().isManualActive()).isFalse();
@@ -6451,14 +6631,14 @@
@Test
@EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
public void setManualZenRuleDeviceEffects_preexistingMode() {
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY, ORIGIN_USER_IN_SYSTEMUI,
- "create manual rule", "settings", SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(UserHandle.CURRENT, ZEN_MODE_OFF, Uri.EMPTY,
+ ORIGIN_USER_IN_SYSTEMUI, "create manual rule", "settings", SYSTEM_UID);
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
.build();
- mZenModeHelper.setManualZenRuleDeviceEffects(effects, ORIGIN_USER_IN_SYSTEMUI, "settings",
- SYSTEM_UID);
+ mZenModeHelper.setManualZenRuleDeviceEffects(UserHandle.CURRENT, effects,
+ ORIGIN_USER_IN_SYSTEMUI, "settings", SYSTEM_UID);
assertThat(mZenModeHelper.getConfig().manualRule).isNotNull();
assertThat(mZenModeHelper.getConfig().isManualActive()).isFalse();
@@ -6473,7 +6653,7 @@
.setEnabled(false)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsDisabled,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, startsDisabled,
ORIGIN_APP,
"new", CUSTOM_PKG_UID);
@@ -6488,7 +6668,7 @@
.setOwner(new ComponentName(mPkg, "SomeProvider"))
.setEnabled(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, startsEnabled,
ORIGIN_APP,
"new", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
@@ -6497,8 +6677,8 @@
AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled)
.setEnabled(false)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, ORIGIN_USER_IN_SYSTEMUI, "off",
- SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, nowDisabled,
+ ORIGIN_USER_IN_SYSTEMUI, "off", SYSTEM_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
ORIGIN_USER_IN_SYSTEMUI);
@@ -6511,14 +6691,14 @@
.setOwner(new ComponentName(mPkg, "SomeProvider"))
.setEnabled(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, startsEnabled,
ORIGIN_APP,
"new", CUSTOM_PKG_UID);
AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled)
.setEnabled(false)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, ORIGIN_USER_IN_SYSTEMUI, "off",
- SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, nowDisabled,
+ ORIGIN_USER_IN_SYSTEMUI, "off", SYSTEM_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
ORIGIN_USER_IN_SYSTEMUI);
@@ -6526,8 +6706,8 @@
AutomaticZenRule nowRenamed = new AutomaticZenRule.Builder(nowDisabled)
.setName("Fancy pants rule")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowRenamed, ORIGIN_APP, "update",
- CUSTOM_PKG_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, nowRenamed, ORIGIN_APP,
+ "update", CUSTOM_PKG_UID);
// Identity of the disabler is preserved.
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
@@ -6541,14 +6721,14 @@
.setOwner(new ComponentName(mPkg, "SomeProvider"))
.setEnabled(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, startsEnabled,
ORIGIN_APP,
"new", CUSTOM_PKG_UID);
AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled)
.setEnabled(false)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, ORIGIN_USER_IN_SYSTEMUI, "off",
- SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, nowDisabled,
+ ORIGIN_USER_IN_SYSTEMUI, "off", SYSTEM_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
ORIGIN_USER_IN_SYSTEMUI);
@@ -6556,8 +6736,8 @@
AutomaticZenRule nowEnabled = new AutomaticZenRule.Builder(nowDisabled)
.setEnabled(true)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowEnabled, ORIGIN_APP, "on",
- CUSTOM_PKG_UID);
+ mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, nowEnabled, ORIGIN_APP,
+ "on", CUSTOM_PKG_UID);
// Identity of the disabler was cleared.
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
@@ -6570,10 +6750,10 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
@@ -6588,13 +6768,13 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
Condition autoOn = new Condition(rule.getConditionId(), "auto-on", STATE_TRUE,
SOURCE_CONTEXT);
ZenRule zenRule;
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6602,7 +6782,7 @@
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
assertThat(zenRule.condition).isNull();
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6611,7 +6791,8 @@
assertThat(zenRule.condition).isNull();
// Bonus check: app has resumed control over the rule and can now turn it on.
- mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOn, ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, autoOn, ORIGIN_APP,
+ CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isTrue();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
@@ -6624,21 +6805,22 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
Condition autoOn = new Condition(rule.getConditionId(), "auto-on", STATE_TRUE,
SOURCE_CONTEXT);
Condition autoOff = new Condition(rule.getConditionId(), "auto-off", STATE_FALSE,
SOURCE_CONTEXT);
ZenRule zenRule;
- mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOn, ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, autoOn, ORIGIN_APP,
+ CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isTrue();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
assertThat(zenRule.condition).isEqualTo(autoOn);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6646,7 +6828,7 @@
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
assertThat(zenRule.condition).isEqualTo(autoOn);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6655,7 +6837,8 @@
assertThat(zenRule.condition).isEqualTo(autoOn);
// Bonus check: app has resumed control over the rule and can now turn it off.
- mZenModeHelper.setAutomaticZenRuleState(ruleId, autoOff, ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, autoOff, ORIGIN_APP,
+ CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isFalse();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
@@ -6668,10 +6851,10 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
ZenRule zenRuleOn = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6679,7 +6862,7 @@
assertThat(zenRuleOn.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
assertThat(zenRuleOn.condition).isNotNull();
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
ZenRule zenRuleOff = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6694,31 +6877,31 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
ZenRule zenRule;
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isTrue();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isTrue();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isTrue();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6731,39 +6914,39 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
ZenRule zenRule;
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isTrue();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isFalse();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isFalse();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.isActive()).isFalse();
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -6777,18 +6960,18 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
// User manually turns on rule from SysUI / Settings...
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on-from-sysui", STATE_TRUE,
SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
assertThat(getZenRule(ruleId).isActive()).isTrue();
assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
// ... and they can turn it off manually from inside the app.
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-off-from-app", STATE_FALSE,
SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
assertThat(getZenRule(ruleId).isActive()).isFalse();
@@ -6801,25 +6984,25 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
// Rule is activated due to its schedule.
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-on-from-app", STATE_TRUE,
SOURCE_SCHEDULE), ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(getZenRule(ruleId).isActive()).isTrue();
assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
// User manually turns off rule from SysUI / Settings...
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-off-from-sysui", STATE_FALSE,
SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
assertThat(getZenRule(ruleId).isActive()).isFalse();
assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
// ... and they can turn it on manually from inside the app.
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on-from-app", STATE_TRUE,
SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
assertThat(getZenRule(ruleId).isActive()).isTrue();
@@ -6832,19 +7015,19 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
// Rule is manually activated by the user in the app.
// This turns the rule on, but is NOT an override...
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on-from-app", STATE_TRUE,
SOURCE_USER_ACTION), ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
assertThat(getZenRule(ruleId).isActive()).isTrue();
assertThat(getZenRule(ruleId).getConditionOverride()).isEqualTo(OVERRIDE_NONE);
// ... so the app can turn it off when its schedule is over.
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-off-from-app", STATE_FALSE,
SOURCE_SCHEDULE), ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(getZenRule(ruleId).isActive()).isFalse();
@@ -6909,12 +7092,13 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
+ .isEqualTo(STATE_TRUE);
ZenRule zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
assertThat(zenRule.condition).isNull();
@@ -6925,15 +7109,17 @@
// Now simulate a reboot -> reload the configuration after purging.
TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
if (Flags.modesUi()) {
- assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
+ .isEqualTo(STATE_TRUE);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
assertThat(zenRule.condition).isNull();
} else {
- assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
+ .isEqualTo(STATE_FALSE);
}
}
@@ -6944,15 +7130,16 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, rule,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT),
ORIGIN_APP, CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId,
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "snooze", STATE_FALSE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_FALSE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
+ .isEqualTo(STATE_FALSE);
ZenRule zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
assertThat(zenRule.condition).isNotNull();
@@ -6963,9 +7150,10 @@
// Now simulate a reboot -> reload the configuration after purging.
TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(ruleId)).isEqualTo(STATE_TRUE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
+ .isEqualTo(STATE_TRUE);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
assertThat(zenRule.condition).isNotNull();
@@ -6978,8 +7166,8 @@
AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond"))
.setPackage(mPkg)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, azr, ORIGIN_APP, "adding",
- CUSTOM_PKG_UID);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mPkg, azr,
+ ORIGIN_APP, "adding", CUSTOM_PKG_UID);
ZenRule zenRule = checkNotNull(mZenModeHelper.mConfig.automaticRules.get(ruleId));
@@ -7001,7 +7189,7 @@
ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI);
TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertThat(mZenModeHelper.mConfig.automaticRules).doesNotContainKey(
ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
@@ -7021,7 +7209,7 @@
ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI);
TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
- mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
assertThat(mZenModeHelper.mConfig.automaticRules).containsKey(
ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
@@ -7039,15 +7227,62 @@
.allowPriorityChannels(false)
.build();
- mZenModeHelper.updateHasPriorityChannels(true);
- assertThat(mZenModeHelper.getNotificationPolicy().hasPriorityChannels()).isTrue();
+ mZenModeHelper.updateHasPriorityChannels(UserHandle.CURRENT, true);
+ assertThat(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).hasPriorityChannels())
+ .isTrue();
// getNotificationPolicy() gets its policy from the manual rule; channels not permitted
- assertThat(mZenModeHelper.getNotificationPolicy().allowPriorityChannels()).isFalse();
+ assertThat(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).allowPriorityChannels())
+ .isFalse();
- mZenModeHelper.updateHasPriorityChannels(false);
- assertThat(mZenModeHelper.getNotificationPolicy().hasPriorityChannels()).isFalse();
- assertThat(mZenModeHelper.getNotificationPolicy().allowPriorityChannels()).isFalse();
+ mZenModeHelper.updateHasPriorityChannels(UserHandle.CURRENT, false);
+ assertThat(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).hasPriorityChannels())
+ .isFalse();
+ assertThat(mZenModeHelper.getNotificationPolicy(UserHandle.CURRENT).allowPriorityChannels())
+ .isFalse();
+ }
+
+ @Test
+ @EnableFlags(FLAG_MODES_MULTIUSER)
+ public void setManualZenMode_fromCurrentUser_updatesCurrentConfig() {
+ // Initialize default configurations (default rules) for both users.
+ mZenModeHelper.onUserSwitched(1);
+ mZenModeHelper.onUserSwitched(2);
+ UserHandle currentUser = UserHandle.of(2);
+ ZenModeHelper.Callback callback = mock(ZenModeHelper.Callback.class);
+ mZenModeHelper.addCallback(callback);
+
+ mZenModeHelper.setManualZenMode(currentUser, ZEN_MODE_ALARMS, null, ORIGIN_APP, "reason",
+ mPkg, CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.mConfig.isManualActive()).isTrue();
+ assertThat(mZenModeHelper.mConfigs.get(1).isManualActive()).isFalse();
+
+ // And we sent the broadcast announcing the change.
+ mTestableLooper.processAllMessages();
+ verify(callback).onZenModeChanged();
+ }
+
+ @Test
+ @EnableFlags(FLAG_MODES_MULTIUSER)
+ public void setInterruptionFilter_fromNonCurrentUser_updatesNonCurrentConfig() {
+ // Initialize default configurations (default rules) for both users.
+ // Afterwards, 2 is current, and 1 is background.
+ mZenModeHelper.onUserSwitched(1);
+ mZenModeHelper.onUserSwitched(2);
+ UserHandle backgroundUser = UserHandle.of(1);
+ ZenModeHelper.Callback callback = mock(ZenModeHelper.Callback.class);
+ mZenModeHelper.addCallback(callback);
+
+ mZenModeHelper.setManualZenMode(backgroundUser, ZEN_MODE_ALARMS, null, ORIGIN_APP, "reason",
+ mPkg, CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.mConfig.isManualActive()).isFalse();
+ assertThat(mZenModeHelper.mConfigs.get(1).isManualActive()).isTrue();
+
+ // And no broadcasts is sent for "background" changes (they were not evaluated).
+ mTestableLooper.processAllMessages();
+ verify(callback, never()).onZenModeChanged();
}
private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
@@ -7100,9 +7335,10 @@
rule.zenMode = zenMode;
rule.zenPolicy = policy;
rule.pkg = ownerPkg;
- rule.name = CUSTOM_APP_LABEL;
- if (!Flags.modesUi()) {
- rule.iconResName = ICON_RES_NAME;
+ if (Flags.modesUi()) {
+ rule.name = mContext.getString(R.string.zen_mode_implicit_name, CUSTOM_APP_LABEL);
+ } else {
+ rule.name = CUSTOM_APP_LABEL;
}
rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description,
CUSTOM_APP_LABEL);
@@ -7122,7 +7358,7 @@
SUPPRESSED_EFFECT_BADGE,
0,
CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.setNotificationPolicy(customPolicy, ORIGIN_UNKNOWN, 1);
+ mZenModeHelper.setNotificationPolicy(UserHandle.CURRENT, customPolicy, ORIGIN_UNKNOWN, 1);
if (!Flags.modesUi()) {
mZenModeHelper.mConfig.manualRule = null;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
index 44b69f1..c9c31df 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java
@@ -39,6 +39,7 @@
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.DisplayShape;
+import android.view.FrameRateCategoryRate;
import android.view.RoundedCorner;
import android.view.RoundedCorners;
import android.view.SurfaceControl.RefreshRateRange;
@@ -235,6 +236,9 @@
} else if (type.isArray() && type.getComponentType().equals(Display.Mode.class)) {
field.set(first, new Display.Mode[]{new Display.Mode(100, 200, 300)});
field.set(second, new Display.Mode[]{new Display.Mode(10, 20, 30)});
+ } else if (type.equals(FrameRateCategoryRate.class)) {
+ field.set(first, new FrameRateCategoryRate(16666667, 11111111));
+ field.set(second, new FrameRateCategoryRate(11111111, 8333333));
} else {
throw new IllegalArgumentException("Field " + field
+ " is not supported by this test, please add implementation of setting "
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 4ccbc32..66d75f7 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -907,29 +907,6 @@
}
/**
- * Ensure the caller (or self, if not processing an IPC) has
- * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} or
- * {@link android.Manifest.permission#READ_PHONE_NUMBERS}.
- *
- * @throws SecurityException if the caller does not have the required permission/privileges
- */
- @RequiresPermission(anyOf = {
- android.Manifest.permission.READ_PHONE_NUMBERS,
- android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
- })
- public static boolean checkCallingOrSelfReadPrivilegedPhoneStatePermissionOrReadPhoneNumber(
- Context context, int subId, String callingPackage, @Nullable String callingFeatureId,
- String message) {
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- return false;
- }
- return (context.checkCallingOrSelfPermission(
- Manifest.permission.READ_PRIVILEGED_PHONE_STATE) == PERMISSION_GRANTED
- || checkCallingOrSelfReadPhoneNumber(context, subId, callingPackage,
- callingFeatureId, message));
- }
-
- /**
* @return true if the specified {@code uid} is for a system or phone process, no matter if runs
* as system user or not.
*/
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index fad59f8b..6f2c862 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -133,6 +133,7 @@
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.uicc.IccUtils;
import com.android.internal.telephony.util.TelephonyUtils;
import com.android.telephony.Rlog;
@@ -6282,14 +6283,17 @@
* The contents of the file is a <b>Ip Multimedia Service Private User Identity</b> of the user
* as defined in the section 4.2.2 of 3GPP TS 131 103.
*
- * @return IMPI (IMS private user identity) of type string.
+ * @return IMPI (IMS private user identity) of type string or null if the IMPI isn't present
+ * on the ISIM.
* @throws IllegalStateException in case the ISIM has’t been loaded
* @throws SecurityException if the caller does not have the required permission/privileges
* @hide
*/
- @NonNull
+ @FlaggedApi(Flags.FLAG_SUPPORT_ISIM_RECORD)
+ @SystemApi
@RequiresPermission(android.Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ @Nullable
public String getImsPrivateUserIdentity() {
try {
IPhoneSubInfo info = getSubscriberInfoService();
@@ -6370,6 +6374,9 @@
* The contents of the file are <b>Ip Multimedia Service Public User Identities</b> of the user
* as defined in the section 4.2.4 of 3GPP TS 131 103. It contains one or more records.
*
+ * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission or carrier
+ * privileges.
+ *
* @return List of public user identities of type android.net.Uri or empty list if
* EF_IMPU is not available.
* @throws IllegalStateException in case the ISIM hasn’t been loaded
@@ -6378,18 +6385,18 @@
* {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
*/
- @NonNull
- @RequiresPermission(anyOf = {android.Manifest.permission.READ_PHONE_NUMBERS,
- android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE})
+ @FlaggedApi(Flags.FLAG_SUPPORT_ISIM_RECORD)
+ @SystemApi
+ @RequiresPermission(value = Manifest.permission.READ_PRIVILEGED_PHONE_STATE, conditional = true)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ @NonNull
public List<Uri> getImsPublicUserIdentities() {
try {
IPhoneSubInfo info = getSubscriberInfoService();
if (info == null) {
throw new RuntimeException("IMPU error: Subscriber Info is null");
}
- return info.getImsPublicUserIdentities(getSubId(), getOpPackageName(),
- getAttributionTag());
+ return info.getImsPublicUserIdentities(getSubId(), getOpPackageName());
} catch (IllegalArgumentException | NullPointerException ex) {
Rlog.e(TAG, "getImsPublicUserIdentities Exception = " + ex);
} catch (RemoteException ex) {
@@ -8684,7 +8691,10 @@
* @return an array of PCSCF strings with one PCSCF per string, or null if
* not present or not loaded
* @hide
+ * @deprecated use {@link #getImsPcscfAddresses()} instead.
*/
+ @Deprecated
+ @FlaggedApi(Flags.FLAG_SUPPORT_ISIM_RECORD)
@UnsupportedAppUsage
public String[] getIsimPcscf() {
try {
@@ -8701,6 +8711,40 @@
}
}
+ /**
+ * Returns the IMS Proxy Call Session Control Function(P-CSCF) that were loaded from the ISIM.
+ *
+ * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission or carrier
+ * privileges.
+ *
+ * @return List of P-CSCF address strings or empty list if not available.
+ * @throws IllegalStateException in case the ISIM hasn’t been loaded
+ * @throws SecurityException if the caller does not have the required permission/privilege
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_ISIM_RECORD)
+ @SystemApi
+ @RequiresPermission(value = Manifest.permission.READ_PRIVILEGED_PHONE_STATE, conditional = true)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ @NonNull
+ public List<String> getImsPcscfAddresses() {
+ try {
+ IPhoneSubInfo info = getSubscriberInfoService();
+ if (info == null) {
+ throw new RuntimeException("P-CSCF error: Subscriber Info is null");
+ }
+ return info.getImsPcscfAddresses(getSubId(), getOpPackageName());
+ } catch (IllegalArgumentException | NullPointerException ex) {
+ Rlog.e(TAG, "getImsPcscfAddresses Exception = " + ex);
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "getImsPcscfAddresses Exception = " + ex);
+ ex.rethrowAsRuntimeException();
+ }
+ return Collections.EMPTY_LIST;
+ }
+
/** UICC application type is unknown or not specified */
public static final int APPTYPE_UNKNOWN = PhoneConstants.APPTYPE_UNKNOWN;
/** UICC application type is SIM */
@@ -8934,8 +8978,10 @@
* @throws UnsupportedOperationException If the device does not have
* {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
* @hide
+ * @deprecated Use {@link #getSimServiceTable(int, Executor, OutcomeReceiver)} instead.
*/
-
+ @Deprecated
+ @FlaggedApi(Flags.FLAG_SUPPORT_ISIM_RECORD)
@Nullable
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -8963,6 +9009,55 @@
}
/**
+ * Fetches the sim service table from the EFUST/EFIST based on the application type
+ * {@link #APPTYPE_USIM} or {@link #APPTYPE_ISIM}.
+ * The USIM service table EF is described in as per Section 4.2.8 of 3GPP TS 31.102.
+ * The ISIM service table EF is described in as per Section 4.2.7 of 3GPP TS 31.103.
+ *
+ * @param appType of type int of either {@link #APPTYPE_USIM} or {@link #APPTYPE_ISIM}.
+ * @param executor executor to run the callback on.
+ * @param callback callback object to which the result will be delivered.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORT_ISIM_RECORD)
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ public void getSimServiceTable(@UiccAppType int appType,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<byte[], Exception> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ IPhoneSubInfo info = getSubscriberInfoService();
+ if (info == null) {
+ executor.execute(() -> callback.onError(
+ new RuntimeException("getSimServiceTable: Subscriber Info is null")));
+ return;
+ }
+
+ try {
+ String serviceTable;
+ if (appType == APPTYPE_ISIM) {
+ serviceTable = info.getIsimIst(getSubId());
+ } else if ((appType == APPTYPE_USIM)) {
+ serviceTable = info.getSimServiceTable(getSubId(), APPTYPE_USIM);
+ } else {
+ serviceTable = null;
+ }
+
+ if (serviceTable == null) {
+ executor.execute(() -> callback.onResult(new byte[0]));
+ } else {
+ byte[] simServiceTable = IccUtils.hexStringToBytes(serviceTable);
+ executor.execute(() -> callback.onResult(simServiceTable));
+ }
+ } catch (Exception ex) {
+ executor.execute(() -> callback.onError(ex));
+ }
+ }
+
+ /**
* Resets the {@link android.telephony.ims.ImsService} associated with the specified sim slot.
* Used by diagnostic apps to force the IMS stack to be disabled and re-enabled in an effort to
* recover from scenarios where the {@link android.telephony.ims.ImsService} gets in to a bad
diff --git a/telephony/java/android/telephony/ims/ImsCallSessionListener.java b/telephony/java/android/telephony/ims/ImsCallSessionListener.java
index 5946535..00d4bd0 100644
--- a/telephony/java/android/telephony/ims/ImsCallSessionListener.java
+++ b/telephony/java/android/telephony/ims/ImsCallSessionListener.java
@@ -16,6 +16,7 @@
package android.telephony.ims;
+import android.annotation.FlaggedApi;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -31,6 +32,7 @@
import android.util.Log;
import com.android.ims.internal.IImsCallSession;
+import com.android.internal.telephony.flags.Flags;
import java.util.ArrayList;
import java.util.Objects;
@@ -802,8 +804,8 @@
/**
* Notifies the result of transfer request.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
public void callSessionTransferred() {
try {
mListener.callSessionTransferred();
@@ -813,13 +815,13 @@
}
/**
- * Notifies the result of transfer request.
+ * Notifies the result of the transfer request failure.
*
* @param reasonInfo {@link ImsReasonInfo} containing a reason for the
* session transfer failure
- * @hide
*/
- public void callSessionTransferFailed(ImsReasonInfo reasonInfo) {
+ @FlaggedApi(Flags.FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ public void callSessionTransferFailed(@NonNull ImsReasonInfo reasonInfo) {
try {
mListener.callSessionTransferFailed(reasonInfo);
} catch (RemoteException e) {
@@ -839,8 +841,8 @@
* @param bitsPerSecond This value is the bitrate requested by the other party UE through
* RTP CMR, RTCPAPP or TMMBR, and ImsStack converts this value to the MAC bitrate
* (defined in TS36.321, range: 0 ~ 8000 kbit/s).
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
public final void callSessionSendAnbrQuery(@MediaStreamType int mediaType,
@MediaStreamDirection int direction, @IntRange(from = 0) int bitsPerSecond) {
Log.d(TAG, "callSessionSendAnbrQuery in imscallsessonListener");
diff --git a/telephony/java/android/telephony/ims/feature/ConnectionFailureInfo.java b/telephony/java/android/telephony/ims/feature/ConnectionFailureInfo.java
index 88d9aae..81cddb7 100644
--- a/telephony/java/android/telephony/ims/feature/ConnectionFailureInfo.java
+++ b/telephony/java/android/telephony/ims/feature/ConnectionFailureInfo.java
@@ -16,12 +16,16 @@
package android.telephony.ims.feature;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseArray;
+import com.android.internal.telephony.flags.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -30,6 +34,8 @@
*
* @hide
*/
+@FlaggedApi(Flags.FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+@SystemApi
public final class ConnectionFailureInfo implements Parcelable {
/** @hide */
@@ -67,7 +73,7 @@
public static final int REASON_RRC_TIMEOUT = 6;
/** Device currently not in service */
public static final int REASON_NO_SERVICE = 7;
- /** The PDN is no more active */
+ /** The PDN is no longer active */
public static final int REASON_PDN_NOT_AVAILABLE = 8;
/** Radio resource is busy with another subscription */
public static final int REASON_RF_BUSY = 9;
@@ -135,6 +141,8 @@
/**
* @return the cause code from the network or modem specific to the failure.
+ * See 3GPP TS 24.401 Annex A (Cause values for EPS mobility management) and
+ * 3GPP TS 24.501 Annex A (Cause values for 5GS mobility management).
*/
public int getCauseCode() {
return mCauseCode;
diff --git a/telephony/java/android/telephony/ims/feature/ImsTrafficSessionCallback.java b/telephony/java/android/telephony/ims/feature/ImsTrafficSessionCallback.java
index 245ee15..0029d49 100644
--- a/telephony/java/android/telephony/ims/feature/ImsTrafficSessionCallback.java
+++ b/telephony/java/android/telephony/ims/feature/ImsTrafficSessionCallback.java
@@ -16,20 +16,26 @@
package android.telephony.ims.feature;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.telephony.flags.Flags;
/**
* A callback class used to receive the result of {@link MmTelFeature#startImsTrafficSession}.
* @hide
*/
+@FlaggedApi(Flags.FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+@SystemApi
public interface ImsTrafficSessionCallback {
/** The modem is ready to process the IMS traffic. */
void onReady();
/**
- * Notifies that any IMS traffic is not sent to network due to any failure
- * on cellular networks. IMS service shall call {@link MmTelFeature#stopImsTrafficSession()}
+ * Notifies that any IMS traffic can not be sent to the network due to the provided cellular
+ * network failure. IMS service shall call {@link MmTelFeature#stopImsTrafficSession()}
* when receiving this callback.
*
* @param info The information of the failure.
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 3f02ae9..c6b11d7 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -16,6 +16,8 @@
package android.telephony.ims.feature;
+import static com.android.internal.telephony.flags.Flags.FLAG_SUPPORT_IMS_MMTEL_INTERFACE;
+
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -964,6 +966,8 @@
*
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public static final int EPS_FALLBACK_REASON_NO_NETWORK_TRIGGER = 1;
/**
@@ -976,6 +980,8 @@
*
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public static final int EPS_FALLBACK_REASON_NO_NETWORK_RESPONSE = 2;
/** @hide */
@@ -1003,36 +1009,50 @@
* Emergency call
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public static final int IMS_TRAFFIC_TYPE_EMERGENCY = 0;
/**
* Emergency SMS
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public static final int IMS_TRAFFIC_TYPE_EMERGENCY_SMS = 1;
/**
* Voice call
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public static final int IMS_TRAFFIC_TYPE_VOICE = 2;
/**
* Video call
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public static final int IMS_TRAFFIC_TYPE_VIDEO = 3;
/**
* SMS over IMS
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public static final int IMS_TRAFFIC_TYPE_SMS = 4;
/**
* IMS registration and subscription for reg event package (signaling)
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public static final int IMS_TRAFFIC_TYPE_REGISTRATION = 5;
/**
* Ut/XCAP (XML Configuration Access Protocol)
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public static final int IMS_TRAFFIC_TYPE_UT_XCAP = 6;
/** @hide */
@@ -1046,11 +1066,15 @@
* Indicates that the traffic is an incoming traffic.
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public static final int IMS_TRAFFIC_DIRECTION_INCOMING = 0;
/**
* Indicates that the traffic is an outgoing traffic.
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public static final int IMS_TRAFFIC_DIRECTION_OUTGOING = 1;
private IImsMmTelListener mListener;
@@ -1291,9 +1315,11 @@
/**
* Triggers the EPS fallback procedure.
*
- * @param reason specifies the reason that causes EPS fallback.
+ * @param reason specifies the reason that EPS fallback was triggered.
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public final void triggerEpsFallback(@EpsFallbackReason int reason) {
IImsMmTelListener listener = getListener();
if (listener == null) {
@@ -1344,6 +1370,8 @@
*
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public final void startImsTrafficSession(@ImsTrafficType int trafficType,
@AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType,
@ImsTrafficDirection int trafficDirection,
@@ -1387,6 +1415,8 @@
*
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public final void modifyImsTrafficSession(
@AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType,
@NonNull ImsTrafficSessionCallback callback) {
@@ -1417,6 +1447,8 @@
*
* @hide
*/
+ @FlaggedApi(FLAG_SUPPORT_IMS_MMTEL_INTERFACE)
+ @SystemApi
public final void stopImsTrafficSession(@NonNull ImsTrafficSessionCallback callback) {
IImsMmTelListener listener = getListener();
if (listener == null) {
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index c78dbe7..e332d0f 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -68,7 +68,6 @@
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SATELLITE)
@SystemApi
-@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public final class SatelliteManager {
private static final String TAG = "SatelliteManager";
diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
index b4d93fd..974cc14 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -208,10 +208,9 @@
/**
* Fetches the ISIM public user identities (EF_IMPU) from UICC based on subId
*/
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" +
- "anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE})")
- List<Uri> getImsPublicUserIdentities(int subId, String callingPackage,
- String callingFeatureId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)")
+ List<Uri> getImsPublicUserIdentities(int subId, String callingPackage);
/**
* Returns the IMS Service Table (IST) that was loaded from the ISIM.
@@ -227,6 +226,20 @@
String[] getIsimPcscf(int subId);
/**
+ * Fetches IMS Proxy Call Session Control Function(P-CSCF) based on the subscription.
+ *
+ * @param subId subscriptionId
+ * @param callingPackage package name of the caller
+ * @return List of IMS Proxy Call Session Control Function strings.
+ * @throws IllegalArgumentException if the subscriptionId is not valid
+ * @throws IllegalStateException in case the ISIM hasn’t been loaded.
+ * @throws SecurityException if the caller does not have the required permission
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)")
+ List<String> getImsPcscfAddresses(int subId, String callingPackage);
+
+ /**
* Returns the response of the SIM application on the UICC to authentication
* challenge/response algorithm. The data string and challenge response are
* Base64 encoded Strings.
diff --git a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
new file mode 100644
index 0000000..01c56b7
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.input
+
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputManager
+import android.hardware.input.KeyGestureEvent
+import android.platform.test.annotations.Presubmit
+import android.view.KeyEvent
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Tests for custom keyboard glyph map configuration.
+ *
+ * Build/Install/Run:
+ * atest InputTests:CustomInputGestureManagerTests
+ */
+@Presubmit
+class InputGestureManagerTests {
+
+ companion object {
+ const val USER_ID = 1
+ }
+
+ private lateinit var inputGestureManager: InputGestureManager
+
+ @Before
+ fun setup() {
+ inputGestureManager = InputGestureManager()
+ }
+
+ @Test
+ fun addRemoveCustomGesture() {
+ val customGesture = InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ val result = inputGestureManager.addCustomInputGesture(USER_ID, customGesture)
+ assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS, result)
+ assertEquals(
+ listOf(customGesture),
+ inputGestureManager.getCustomInputGestures(USER_ID)
+ )
+
+ inputGestureManager.removeCustomInputGesture(USER_ID, customGesture)
+ assertEquals(
+ listOf<InputGestureData>(),
+ inputGestureManager.getCustomInputGestures(USER_ID)
+ )
+ }
+
+ @Test
+ fun removeNonExistentGesture() {
+ val customGesture = InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ val result = inputGestureManager.removeCustomInputGesture(USER_ID, customGesture)
+ assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST, result)
+ assertEquals(
+ listOf<InputGestureData>(),
+ inputGestureManager.getCustomInputGestures(USER_ID)
+ )
+ }
+
+ @Test
+ fun addAlreadyExistentGesture() {
+ val customGesture = InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ inputGestureManager.addCustomInputGesture(USER_ID, customGesture)
+ val customGesture2 = InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build()
+ val result = inputGestureManager.addCustomInputGesture(USER_ID, customGesture2)
+ assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS, result)
+ assertEquals(
+ listOf(customGesture),
+ inputGestureManager.getCustomInputGestures(USER_ID)
+ )
+ }
+
+ @Test
+ fun addRemoveAllExistentGestures() {
+ val customGesture = InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ inputGestureManager.addCustomInputGesture(USER_ID, customGesture)
+ val customGesture2 = InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_DEL,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build()
+ inputGestureManager.addCustomInputGesture(USER_ID, customGesture2)
+
+ assertEquals(
+ listOf(customGesture, customGesture2),
+ inputGestureManager.getCustomInputGestures(USER_ID)
+ )
+
+ inputGestureManager.removeAllCustomInputGestures(USER_ID)
+ assertEquals(
+ listOf<InputGestureData>(),
+ inputGestureManager.getCustomInputGestures(USER_ID)
+ )
+ }
+}
\ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/InputShellCommandTest.java b/tests/Input/src/com/android/server/input/InputShellCommandTest.java
index 11f4633..a236244 100644
--- a/tests/Input/src/com/android/server/input/InputShellCommandTest.java
+++ b/tests/Input/src/com/android/server/input/InputShellCommandTest.java
@@ -133,6 +133,21 @@
assertThat(mInputEventInjector.mInjectedEvents).isEmpty();
}
+ @Test
+ public void testSwipeCommandEventFrequency() {
+ int[] durations = {100, 300, 500};
+ for (int durationMillis: durations) {
+ mInputEventInjector.mInjectedEvents.clear();
+ runCommand(String.format("swipe 200 800 200 200 %d", durationMillis));
+
+ // Add 2 events for ACTION_DOWN and ACTION_UP.
+ final int maxEventNum =
+ (int) Math.ceil(InputShellCommand.SWIPE_EVENT_HZ_DEFAULT
+ * (float) durationMillis / 1000) + 2;
+ assertThat(mInputEventInjector.mInjectedEvents.size()).isAtMost(maxEventNum);
+ }
+ }
+
private InputEvent getSingleInjectedInputEvent() {
assertThat(mInputEventInjector.mInjectedEvents).hasSize(1);
return mInputEventInjector.mInjectedEvents.get(0);
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 7e0bbc4..3828a71 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -70,6 +70,7 @@
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.Uri;
+import android.net.vcn.Flags;
import android.net.vcn.IVcnStatusCallback;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
import android.net.vcn.VcnConfig;
@@ -84,6 +85,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.test.TestLooper;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -102,6 +104,7 @@
import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -119,6 +122,8 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class VcnManagementServiceTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final String CONTEXT_ATTRIBUTION_TAG = "VCN";
private static final String TEST_PACKAGE_NAME =
VcnManagementServiceTest.class.getPackage().getName();
@@ -288,6 +293,8 @@
doReturn(Collections.singleton(TRANSPORT_WIFI))
.when(mMockDeps)
.getRestrictedTransports(any(), any(), any());
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_FIX_CONFIG_GARBAGE_COLLECTION);
}
@@ -438,6 +445,14 @@
return subIds;
}).when(snapshot).getAllSubIdsInGroup(any());
+ doAnswer(invocation -> {
+ final Set<ParcelUuid> subGroups = new ArraySet<>();
+ for (Entry<Integer, ParcelUuid> entry : subIdToGroupMap.entrySet()) {
+ subGroups.add(entry.getValue());
+ }
+ return subGroups;
+ }).when(snapshot).getAllSubscriptionGroups();
+
return snapshot;
}
@@ -1483,6 +1498,28 @@
}
@Test
+ public void testGarbageCollectionKeepConfigUntilNewSnapshot() throws Exception {
+ setupActiveSubscription(TEST_UUID_2);
+ startAndGetVcnInstance(TEST_UUID_2);
+
+ // Report loss of subscription from mSubMgr
+ doReturn(Collections.emptyList()).when(mSubMgr).getSubscriptionsInGroup(any());
+ triggerSubscriptionTrackerCbAndGetSnapshot(
+ TEST_UUID_2,
+ Collections.singleton(TEST_UUID_2),
+ Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_2));
+
+ assertTrue(mVcnMgmtSvc.getConfigs().containsKey(TEST_UUID_2));
+
+ // Report loss of subscription from snapshot
+ triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet());
+
+ mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+ mTestLooper.dispatchAll();
+ assertFalse(mVcnMgmtSvc.getConfigs().containsKey(TEST_UUID_2));
+ }
+
+ @Test
public void testVcnCarrierConfigChangeUpdatesPolicyListener() throws Exception {
setupActiveSubscription(TEST_UUID_2);
diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp
index 514651e..449d93d 100644
--- a/tools/aapt2/cmd/Command.cpp
+++ b/tools/aapt2/cmd/Command.cpp
@@ -213,15 +213,28 @@
bool match = false;
for (Flag& flag : flags_) {
- if (arg == flag.name) {
+ // Allow both "--arg value" and "--arg=value" syntax.
+ if (arg.starts_with(flag.name) &&
+ (arg.size() == flag.name.size() || (flag.num_args > 0 && arg[flag.name.size()] == '='))) {
if (flag.num_args > 0) {
- i++;
- if (i >= args.size()) {
- *out_error << flag.name << " missing argument.\n\n";
- Usage(out_error);
- return false;
+ if (arg.size() == flag.name.size()) {
+ i++;
+ if (i >= args.size()) {
+ *out_error << flag.name << " missing argument.\n\n";
+ Usage(out_error);
+ return 1;
+ }
+ arg = args[i];
+ } else {
+ arg.remove_prefix(flag.name.size() + 1);
+ // Disallow empty arguments after '='.
+ if (arg.empty()) {
+ *out_error << flag.name << " has empty argument.\n\n";
+ Usage(out_error);
+ return 1;
+ }
}
- flag.action(args[i]);
+ flag.action(arg);
} else {
flag.action({});
}
diff --git a/tools/aapt2/cmd/Command_test.cpp b/tools/aapt2/cmd/Command_test.cpp
index 7aa1aa01..20d87e0 100644
--- a/tools/aapt2/cmd/Command_test.cpp
+++ b/tools/aapt2/cmd/Command_test.cpp
@@ -19,6 +19,7 @@
#include "test/Test.h"
using ::testing::Eq;
+using namespace std::literals;
namespace aapt {
@@ -94,4 +95,27 @@
}
#endif
+TEST(CommandTest, OptionsWithValues) {
+ TestCommand command;
+ std::string flag;
+ command.AddRequiredFlag("--flag", "", &flag);
+
+ ASSERT_EQ(0, command.Execute({"--flag"s, "1"s}, &std::cerr));
+ EXPECT_STREQ("1", flag.c_str());
+
+ ASSERT_EQ(0, command.Execute({"--flag=1"s}, &std::cerr));
+ EXPECT_STREQ("1", flag.c_str());
+
+ ASSERT_EQ(0, command.Execute({"--flag"s, "=2"s}, &std::cerr));
+ EXPECT_STREQ("=2", flag.c_str());
+
+ ASSERT_EQ(0, command.Execute({"--flag"s, "--flag"s}, &std::cerr));
+ EXPECT_STREQ("--flag", flag.c_str());
+
+ EXPECT_NE(0, command.Execute({"--flag"s}, &std::cerr));
+ EXPECT_NE(0, command.Execute({"--flag="s}, &std::cerr));
+ EXPECT_NE(0, command.Execute({"--flag1=2"s}, &std::cerr));
+ EXPECT_NE(0, command.Execute({"--flag1"s, "2"s}, &std::cerr));
+}
+
} // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 52372fa..a5e18d35 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -605,8 +605,9 @@
}
// Write the crunched PNG.
- if (!android::WritePng(image.get(), nine_patch.get(), &crunched_png_buffer_out, {},
- &source_diag, context->IsVerbose())) {
+ if (!android::WritePng(image.get(), nine_patch.get(), &crunched_png_buffer_out,
+ {.compression_level = options.png_compression_level_int}, &source_diag,
+ context->IsVerbose())) {
return false;
}
@@ -924,6 +925,19 @@
}
}
+ if (!options_.png_compression_level) {
+ options_.png_compression_level_int = 9;
+ } else {
+ if (options_.png_compression_level->size() != 1 ||
+ options_.png_compression_level->front() < '0' ||
+ options_.png_compression_level->front() > '9') {
+ context.GetDiagnostics()->Error(
+ android::DiagMessage() << "PNG compression level should be a number in [0..9] range");
+ return 1;
+ }
+ options_.png_compression_level_int = options_.png_compression_level->front() - '0';
+ }
+
return Compile(&context, file_collection.get(), archive_writer.get(), options_);
}
diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h
index 70c8791..e244546 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -47,6 +47,8 @@
bool verbose = false;
std::optional<std::string> product_;
FeatureFlagValues feature_flag_values;
+ std::optional<std::string> png_compression_level;
+ int png_compression_level_int = 9;
};
/** Parses flags and compiles resources to be used in linking. */
@@ -65,6 +67,9 @@
AddOptionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales "
"(en-XA and ar-XB)", &options_.pseudolocalize);
AddOptionalSwitch("--no-crunch", "Disables PNG processing", &options_.no_png_crunch);
+ AddOptionalFlag("--png-compression-level",
+ "Set the zlib compression level for crunched PNG images, [0-9], 9 by default.",
+ &options_.png_compression_level);
AddOptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
&options_.legacy_mode);
AddOptionalSwitch("--preserve-visibility-of-styleables",