diff options
241 files changed, 6648 insertions, 1612 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 31214cbb7066..696c3178a4f4 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -3919,6 +3919,7 @@ public class DeviceIdleController extends SystemService if (locationManager != null && locationManager.getProvider(LocationManager.FUSED_PROVIDER) != null) { + mHasFusedLocation = true; locationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER, mLocationRequest, AppSchedulingModuleThread.getExecutor(), diff --git a/core/api/current.txt b/core/api/current.txt index 4f5e519e85d9..987b5c61219b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -7701,7 +7701,7 @@ package android.app { method @Nullable public android.app.WallpaperColors getWallpaperColors(int); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.os.ParcelFileDescriptor getWallpaperFile(int); method public int getWallpaperId(int); - method public android.app.WallpaperInfo getWallpaperInfo(); + method @RequiresPermission(value="QUERY_ALL_PACKAGES", conditional=true) public android.app.WallpaperInfo getWallpaperInfo(); method @Nullable public android.app.WallpaperInfo getWallpaperInfo(int); method public boolean hasResourceWallpaper(@RawRes int); method public boolean isSetWallpaperAllowed(); @@ -18800,14 +18800,14 @@ package android.hardware.biometrics { } @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem { - ctor public PromptContentItemBulletedText(@NonNull CharSequence); + 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 { - ctor public PromptContentItemPlainText(@NonNull CharSequence); + 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; @@ -18818,7 +18818,7 @@ package android.hardware.biometrics { @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptVerticalListContentView implements android.os.Parcelable android.hardware.biometrics.PromptContentView { method public int describeContents(); - method @Nullable public CharSequence getDescription(); + method @Nullable public String getDescription(); method @NonNull public java.util.List<android.hardware.biometrics.PromptContentItem> getListItems(); method public static int getMaxEachItemCharacterNumber(); method public static int getMaxItemCount(); @@ -18831,7 +18831,7 @@ package android.hardware.biometrics { method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem); method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem, int); method @NonNull public android.hardware.biometrics.PromptVerticalListContentView build(); - method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder setDescription(@NonNull CharSequence); + method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder setDescription(@NonNull String); } } @@ -22196,11 +22196,10 @@ package android.media { @FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecController implements java.lang.AutoCloseable { method @FlaggedApi("android.media.audio.loudness_configurator_api") public boolean addMediaCodec(@NonNull android.media.MediaCodec); - method public void close(); + method @FlaggedApi("android.media.audio.loudness_configurator_api") public void close(); method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int); method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int, @NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener); method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.MediaCodec); - method @FlaggedApi("android.media.audio.loudness_configurator_api") public void release(); method @FlaggedApi("android.media.audio.loudness_configurator_api") public void removeMediaCodec(@NonNull android.media.MediaCodec); } @@ -41704,10 +41703,10 @@ package android.telecom { method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); method @NonNull public android.os.ParcelUuid getCallId(); method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); + method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void requestMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); method public void sendEvent(@NonNull String, @NonNull android.os.Bundle); method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); - method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void setMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); method public void startCallStreaming(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0b17e03c147d..5920c6b97bc5 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -22,7 +22,7 @@ package android { field public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE = "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"; field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES"; field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS"; - field public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE"; + field @FlaggedApi("android.app.smartspace.flags.access_smartspace") public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE"; field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER"; field public static final String ACCESS_TUNED_INFO = "android.permission.ACCESS_TUNED_INFO"; field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER"; @@ -17159,7 +17159,7 @@ package android.telephony.satellite { 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> getSatelliteAttachRestrictionReasonsForCarrier(int); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@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 provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @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 registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback) throws android.telephony.satellite.SatelliteManager.SatelliteException; + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteModemStateCallback); @@ -17226,6 +17226,7 @@ package android.telephony.satellite { field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ERROR = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23; // 0x17 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6 diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e288b42f7ec7..1bdbd4c50634 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -6792,6 +6792,7 @@ public final class ActivityThread extends ClientTransactionHandler } } if (killApp) { + // Keep in sync with "perhaps it was removed" case below. mPackages.remove(packages[i]); mResourcePackages.remove(packages[i]); } @@ -6859,6 +6860,12 @@ public final class ActivityThread extends ClientTransactionHandler } } catch (RemoteException e) { } + } else { + // No package, perhaps it was removed? + Slog.e(TAG, "Package [" + packages[i] + "] reported as REPLACED," + + " but missing application info. Assuming REMOVED."); + mPackages.remove(packages[i]); + mResourcePackages.remove(packages[i]); } } } diff --git a/core/java/android/app/HomeVisibilityListener.java b/core/java/android/app/HomeVisibilityListener.java index 1f5f2e4c8237..5dd7ab0f99fa 100644 --- a/core/java/android/app/HomeVisibilityListener.java +++ b/core/java/android/app/HomeVisibilityListener.java @@ -69,6 +69,11 @@ public abstract class HomeVisibilityListener { public HomeVisibilityListener() { mObserver = new android.app.IProcessObserver.Stub() { @Override + public void onProcessStarted(int pid, int processUid, int packageUid, + String packageName, String processName) { + } + + @Override public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) { refreshHomeVisibility(); } diff --git a/core/java/android/app/IProcessObserver.aidl b/core/java/android/app/IProcessObserver.aidl index 7be3620f317b..5c5e72cf9d6f 100644 --- a/core/java/android/app/IProcessObserver.aidl +++ b/core/java/android/app/IProcessObserver.aidl @@ -18,6 +18,17 @@ package android.app; /** {@hide} */ oneway interface IProcessObserver { + /** + * Invoked when an app process starts up. + * + * @param pid The pid of the process. + * @param processUid The UID associated with the process. + * @param packageUid The UID associated with the package. + * @param packageName The name of the package. + * @param processName The name of the process. + */ + void onProcessStarted(int pid, int processUid, int packageUid, + @utf8InCpp String packageName, @utf8InCpp String processName); void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities); void onForegroundServicesChanged(int pid, int uid, int serviceTypes); void onProcessDied(int pid, int uid); diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 36b03c1b1f48..0116ca24ec97 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -1899,15 +1899,22 @@ public class WallpaperManager { /** * Returns the information about the home screen wallpaper if its current wallpaper is a live - * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null. + * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the + * caller doesn't have the appropriate permissions, this returns {@code null}. * * <p> - * In order to use this, apps should declare a {@code <queries>} tag with the action - * {@code "android.service.wallpaper.WallpaperService"}. Otherwise, + * Before Android U, this method requires the + * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission. + * </p> + * + * <p> + * Starting from Android U, in order to use this, apps should declare a {@code <queries>} tag + * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise, * this method will return {@code null} if the caller doesn't otherwise have * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package. * </p> */ + @RequiresPermission(value = "QUERY_ALL_PACKAGES", conditional = true) public WallpaperInfo getWallpaperInfo() { return getWallpaperInfoForUser(mContext.getUserId()); } @@ -1924,19 +1931,14 @@ public class WallpaperManager { } /** - * Returns the information about the home screen wallpaper if its current wallpaper is a live - * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the + * Returns the information about the designated wallpaper if its current wallpaper is a live + * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if * the caller doesn't have the appropriate permissions, this returns {@code null}. * * <p> - * Before Android U, this method requires the - * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission. - * </p> - * - * <p> - * Starting from Android U, In order to use this, apps should declare a {@code <queries>} tag - * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise, - * this method will return {@code null} if the caller doesn't otherwise have + * In order to use this, apps should declare a {@code <queries>} tag with the action + * {@code "android.service.wallpaper.WallpaperService"}. Otherwise, this method will return + * {@code null} if the caller doesn't otherwise have * <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package. * </p> * @@ -1952,7 +1954,7 @@ public class WallpaperManager { /** * Returns the information about the designated wallpaper if its current wallpaper is a live * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the - * the caller doesn't have the appropriate permissions, this returns {@code null}. + * caller doesn't have the appropriate permissions, this returns {@code null}. * * <p> * In order to use this, apps should declare a {@code <queries>} tag diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl index 325aa28fde08..83e18ec05599 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl @@ -133,4 +133,10 @@ interface IVirtualDeviceManager { * device. */ boolean isVirtualDeviceOwnedMirrorDisplay(int displayId); + + /** + * Returns all current persistent device IDs, including the ones for which no virtual device + * exists, as long as one may have existed or can be created. + */ + List<String> getAllPersistentDeviceIds(); } diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index e4e9fbaf2c55..903875bef1fc 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -153,3 +153,11 @@ flag { bug: "291135724" is_fixed_read_only: true } + +flag { + name: "fix_system_apps_first_install_time" + namespace: "package_manager_service" + description: "Feature flag to fix the first-install timestamps for system apps." + bug: "321258605" + is_fixed_read_only: true +} diff --git a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl index 73ac333cfd89..d51e62e709c2 100644 --- a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl +++ b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl @@ -33,4 +33,20 @@ oneway interface AuthenticationStateListener { * Defines behavior in response to authentication stopping */ void onAuthenticationStopped(); + + /** + * Defines behavior in response to a successful authentication + * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested + * authentication + * @param userId The user Id for the requested authentication + */ + void onAuthenticationSucceeded(int requestReason, int userId); + + /** + * Defines behavior in response to a failed authentication + * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested + * authentication + * @param userId The user Id for the requested authentication + */ + void onAuthenticationFailed(int requestReason, int userId); } diff --git a/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java index c5e5a8076747..25e5cca485d2 100644 --- a/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java +++ b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java @@ -28,14 +28,14 @@ import android.os.Parcelable; */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) public final class PromptContentItemBulletedText implements PromptContentItemParcelable { - private final CharSequence mText; + private final String mText; /** * A list item with bulleted text shown on {@link PromptVerticalListContentView}. * * @param text The text of this list item. */ - public PromptContentItemBulletedText(@NonNull CharSequence text) { + public PromptContentItemBulletedText(@NonNull String text) { mText = text; } @@ -43,7 +43,7 @@ public final class PromptContentItemBulletedText implements PromptContentItemPar * @hide */ @NonNull - public CharSequence getText() { + public String getText() { return mText; } @@ -60,7 +60,7 @@ public final class PromptContentItemBulletedText implements PromptContentItemPar */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeCharSequence(mText); + dest.writeString(mText); } /** @@ -70,7 +70,7 @@ public final class PromptContentItemBulletedText implements PromptContentItemPar public static final Creator<PromptContentItemBulletedText> CREATOR = new Creator<>() { @Override public PromptContentItemBulletedText createFromParcel(Parcel in) { - return new PromptContentItemBulletedText(in.readCharSequence()); + return new PromptContentItemBulletedText(in.readString()); } @Override diff --git a/core/java/android/hardware/biometrics/PromptContentItemPlainText.java b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java index 6434c5975c12..7919256f9c6d 100644 --- a/core/java/android/hardware/biometrics/PromptContentItemPlainText.java +++ b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java @@ -28,14 +28,14 @@ import android.os.Parcelable; */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) public final class PromptContentItemPlainText implements PromptContentItemParcelable { - private final CharSequence mText; + private final String mText; /** * A list item with plain text shown on {@link PromptVerticalListContentView}. * * @param text The text of this list item. */ - public PromptContentItemPlainText(@NonNull CharSequence text) { + public PromptContentItemPlainText(@NonNull String text) { mText = text; } @@ -43,7 +43,7 @@ public final class PromptContentItemPlainText implements PromptContentItemParcel * @hide */ @NonNull - public CharSequence getText() { + public String getText() { return mText; } @@ -60,7 +60,7 @@ public final class PromptContentItemPlainText implements PromptContentItemParcel */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeCharSequence(mText); + dest.writeString(mText); } /** @@ -70,7 +70,7 @@ public final class PromptContentItemPlainText implements PromptContentItemParcel public static final Creator<PromptContentItemPlainText> CREATOR = new Creator<>() { @Override public PromptContentItemPlainText createFromParcel(Parcel in) { - return new PromptContentItemPlainText(in.readCharSequence()); + return new PromptContentItemPlainText(in.readString()); } @Override diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java index f3e62907d845..38d32dc73ccb 100644 --- a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java +++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java @@ -52,11 +52,11 @@ public final class PromptVerticalListContentView implements PromptContentViewPar private static final int MAX_ITEM_NUMBER = 20; private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640; private final List<PromptContentItemParcelable> mContentList; - private final CharSequence mDescription; + private final String mDescription; private PromptVerticalListContentView( @NonNull List<PromptContentItemParcelable> contentList, - @NonNull CharSequence description) { + @NonNull String description) { mContentList = contentList; mDescription = description; } @@ -65,7 +65,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar mContentList = in.readArrayList( PromptContentItemParcelable.class.getClassLoader(), PromptContentItemParcelable.class); - mDescription = in.readCharSequence(); + mDescription = in.readString(); } /** @@ -84,12 +84,12 @@ public final class PromptVerticalListContentView implements PromptContentViewPar /** * Gets the description for the content view, as set by - * {@link PromptVerticalListContentView.Builder#setDescription(CharSequence)}. + * {@link PromptVerticalListContentView.Builder#setDescription(String)}. * * @return The description for the content view, or null if the content view has no description. */ @Nullable - public CharSequence getDescription() { + public String getDescription() { return mDescription; } @@ -118,7 +118,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar @Override public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) { dest.writeList(mContentList); - dest.writeCharSequence(mDescription); + dest.writeString(mDescription); } /** @@ -143,7 +143,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar */ public static final class Builder { private final List<PromptContentItemParcelable> mContentList = new ArrayList<>(); - private CharSequence mDescription; + private String mDescription; /** * Optional: Sets a description that will be shown on the content view. @@ -152,7 +152,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar * @return This builder. */ @NonNull - public Builder setDescription(@NonNull CharSequence description) { + public Builder setDescription(@NonNull String description) { mDescription = description; return this; } diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index e267e6b22f9d..8e234fa11866 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -15,6 +15,7 @@ */ package android.hardware.face; +import android.hardware.biometrics.AuthenticationStateListener; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IBiometricStateListener; @@ -181,6 +182,14 @@ interface IFaceService { // authenticators. The callback is automatically removed after it's invoked. void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback); + // Registers AuthenticationStateListener. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + void registerAuthenticationStateListener(AuthenticationStateListener listener); + + // Unregisters AuthenticationStateListener. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + void unregisterAuthenticationStateListener(AuthenticationStateListener listener); + // Registers BiometricStateListener. void registerBiometricStateListener(IBiometricStateListener listener); diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java index 8644d9103dcb..f65b713f8967 100644 --- a/core/java/android/metrics/LogMaker.java +++ b/core/java/android/metrics/LogMaker.java @@ -32,6 +32,7 @@ import java.util.Arrays; * @hide */ @SystemApi +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class LogMaker { private static final String TAG = "LogBuilder"; diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 58717179d64d..3977bdf413d9 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -28,6 +28,7 @@ import android.app.ActivityThread; import android.app.Application; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import android.sysprop.DeviceProperties; import android.sysprop.SocProperties; import android.sysprop.TelephonyProperties; @@ -47,6 +48,7 @@ import java.util.stream.Collectors; /** * Information about the current build, extracted from system properties. */ +@RavenwoodKeepWholeClass public class Build { private static final String TAG = "Build"; @@ -307,7 +309,7 @@ public class Build { * compatibility. */ final String[] abiList; - if (VMRuntime.getRuntime().is64Bit()) { + if (android.os.Process.is64Bit()) { abiList = SUPPORTED_64_BIT_ABIS; } else { abiList = SUPPORTED_32_BIT_ABIS; diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index aa283a2d019b..a818919d184e 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -20,6 +20,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; +import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass; import android.util.Log; import android.util.MutableInt; @@ -36,6 +38,8 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; /** * Gives access to the system properties store. The system properties @@ -51,6 +55,8 @@ import java.util.HashMap; * {@hide} */ @SystemApi +@RavenwoodKeepWholeClass +@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.SystemProperties_host") public class SystemProperties { private static final String TAG = "SystemProperties"; private static final boolean TRACK_KEY_ACCESS = false; @@ -94,6 +100,31 @@ public class SystemProperties { } } + /** @hide */ + public static void init$ravenwood(Map<String, String> values, + Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate) { + native_init$ravenwood(values, keyReadablePredicate, keyWritablePredicate, + SystemProperties::callChangeCallbacks); + synchronized (sChangeCallbacks) { + sChangeCallbacks.clear(); + } + } + + /** @hide */ + public static void reset$ravenwood() { + native_reset$ravenwood(); + synchronized (sChangeCallbacks) { + sChangeCallbacks.clear(); + } + } + + // These native methods are currently only implemented by Ravenwood, as it's the only + // mechanism we have to jump to our RavenwoodNativeSubstitutionClass + private static native void native_init$ravenwood(Map<String, String> values, + Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate, + Runnable changeCallback); + private static native void native_reset$ravenwood(); + // The one-argument version of native_get used to be a regular native function. Nowadays, // we use the two-argument form of native_get all the time, but we can't just delete the // one-argument overload: apps use it via reflection, as the UnsupportedAppUsage annotation diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 5d7e04d4ed26..c0b490929c0a 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -36,6 +36,7 @@ import dalvik.annotation.optimization.FastNative; * href="{@docRoot}tools/debugging/systrace.html">Analyzing Display and Performance * with Systrace</a>. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class Trace { /* * Writes trace events to the kernel trace buffer. These trace events can be @@ -123,10 +124,26 @@ public final class Trace { @UnsupportedAppUsage @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace private static native long nativeGetEnabledTags(); + @android.ravenwood.annotation.RavenwoodReplace private static native void nativeSetAppTracingAllowed(boolean allowed); + @android.ravenwood.annotation.RavenwoodReplace private static native void nativeSetTracingEnabled(boolean allowed); + private static long nativeGetEnabledTags$ravenwood() { + // Tracing currently completely disabled under Ravenwood + return 0; + } + + private static void nativeSetAppTracingAllowed$ravenwood(boolean allowed) { + // Tracing currently completely disabled under Ravenwood + } + + private static void nativeSetTracingEnabled$ravenwood(boolean allowed) { + // Tracing currently completely disabled under Ravenwood + } + @FastNative private static native void nativeTraceCounter(long tag, String name, long value); @FastNative diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 76e0c259b38f..bbda0684f1d8 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -271,7 +271,6 @@ public abstract class WallpaperService extends Service { boolean mDrawingAllowed; boolean mOffsetsChanged; boolean mFixedSizeAllowed; - boolean mShouldDim; // Whether the wallpaper should be dimmed by default (when no additional dimming is applied) // based on its color hints boolean mShouldDimByDefault; @@ -348,9 +347,11 @@ public abstract class WallpaperService extends Service { private Display mDisplay; private Context mDisplayContext; private int mDisplayState; - private float mWallpaperDimAmount = 0.05f; + + private float mCustomDimAmount = 0f; + private float mWallpaperDimAmount = 0f; private float mPreviousWallpaperDimAmount = mWallpaperDimAmount; - private float mDefaultDimAmount = mWallpaperDimAmount; + private float mDefaultDimAmount = 0.05f; SurfaceControl mSurfaceControl = new SurfaceControl(); SurfaceControl mBbqSurfaceControl; @@ -986,11 +987,8 @@ public abstract class WallpaperService extends Service { mShouldDimByDefault = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0 && (colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) == 0); - // If default dimming value changes and no additional dimming is applied - if (mShouldDimByDefault != mShouldDim && mWallpaperDimAmount == 0f) { - mShouldDim = mShouldDimByDefault; - updateSurfaceDimming(); - } + // Recompute dim in case it changed compared to the previous WallpaperService + updateWallpaperDimming(mCustomDimAmount); } /** @@ -999,28 +997,21 @@ public abstract class WallpaperService extends Service { * @param dimAmount Float amount between [0.0, 1.0] to dim the wallpaper. */ private void updateWallpaperDimming(float dimAmount) { - if (dimAmount == mWallpaperDimAmount) { - return; - } + mCustomDimAmount = Math.min(1f, dimAmount); - // Custom dim amount cannot be less than the default dim amount. - mWallpaperDimAmount = Math.max(mDefaultDimAmount, dimAmount); - // If dim amount is 0f (additional dimming is removed), then the wallpaper should dim - // based on its default wallpaper color hints. - mShouldDim = dimAmount != 0f || mShouldDimByDefault; - updateSurfaceDimming(); - } + // If default dim is enabled, the actual dim amount is at least the default dim amount + mWallpaperDimAmount = (!mShouldDimByDefault) ? mCustomDimAmount + : Math.max(mDefaultDimAmount, mCustomDimAmount); - private void updateSurfaceDimming() { - if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) { + if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null + || mWallpaperDimAmount == mPreviousWallpaperDimAmount) { return; } SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction(); // TODO: apply the dimming to preview as well once surface transparency works in // preview mode. - if ((!isPreview() && mShouldDim) - || mPreviousWallpaperDimAmount != mWallpaperDimAmount) { + if (!isPreview()) { Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount); // Animate dimming to gradually change the wallpaper alpha from the previous @@ -1545,8 +1536,6 @@ public abstract class WallpaperService extends Service { .createWindowContext(TYPE_WALLPAPER, null /* options */); mDefaultDimAmount = mDisplayContext.getResources().getFloat( com.android.internal.R.dimen.config_wallpaperDimAmount); - mWallpaperDimAmount = mDefaultDimAmount; - mPreviousWallpaperDimAmount = mWallpaperDimAmount; mDisplayState = mDisplay.getCommittedState(); mMergedConfiguration.setOverrideConfiguration( mDisplayContext.getResources().getConfiguration()); diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 17a3a12d3b79..7f1e037e92d4 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -34,11 +34,11 @@ import static android.view.InsetsController.DEBUG; import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; import static android.view.InsetsController.LayoutInsetsDuringAnimation; import static android.view.InsetsSource.ID_IME; -import static android.view.InsetsState.ISIDE_BOTTOM; -import static android.view.InsetsState.ISIDE_FLOATING; -import static android.view.InsetsState.ISIDE_LEFT; -import static android.view.InsetsState.ISIDE_RIGHT; -import static android.view.InsetsState.ISIDE_TOP; +import static android.view.InsetsSource.SIDE_BOTTOM; +import static android.view.InsetsSource.SIDE_NONE; +import static android.view.InsetsSource.SIDE_LEFT; +import static android.view.InsetsSource.SIDE_RIGHT; +import static android.view.InsetsSource.SIDE_TOP; import static android.view.WindowInsets.Type.ime; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -60,7 +60,7 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.util.SparseSetArray; import android.util.proto.ProtoOutputStream; -import android.view.InsetsState.InternalInsetsSide; +import android.view.InsetsSource.InternalInsetsSide; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; @@ -142,7 +142,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro if (mHasZeroInsetsIme) { // IME has shownInsets of ZERO, and can't map to a side by default. // Map zero insets IME to bottom, making it a special case of bottom insets. - idSideMap.put(ID_IME, ISIDE_BOTTOM); + idSideMap.put(ID_IME, SIDE_BOTTOM); } buildSideControlsMap(idSideMap, mSideControlsMap, controls); } else { @@ -286,10 +286,10 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro } final Insets offset = Insets.subtract(mShownInsets, mPendingInsets); final ArrayList<SurfaceParams> params = new ArrayList<>(); - updateLeashesForSide(ISIDE_LEFT, offset.left, params, outState, mPendingAlpha); - updateLeashesForSide(ISIDE_TOP, offset.top, params, outState, mPendingAlpha); - updateLeashesForSide(ISIDE_RIGHT, offset.right, params, outState, mPendingAlpha); - updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha); + updateLeashesForSide(SIDE_LEFT, offset.left, params, outState, mPendingAlpha); + updateLeashesForSide(SIDE_TOP, offset.top, params, outState, mPendingAlpha); + updateLeashesForSide(SIDE_RIGHT, offset.right, params, outState, mPendingAlpha); + updateLeashesForSide(SIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha); mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()])); mCurrentInsets = mPendingInsets; @@ -499,19 +499,19 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro final float surfaceOffset = mTranslator != null ? mTranslator.translateLengthInAppWindowToScreen(offset) : offset; switch (side) { - case ISIDE_LEFT: + case SIDE_LEFT: m.postTranslate(-surfaceOffset, 0); frame.offset(-offset, 0); break; - case ISIDE_TOP: + case SIDE_TOP: m.postTranslate(0, -surfaceOffset); frame.offset(0, -offset); break; - case ISIDE_RIGHT: + case SIDE_RIGHT: m.postTranslate(surfaceOffset, 0); frame.offset(offset, 0); break; - case ISIDE_BOTTOM: + case SIDE_BOTTOM: m.postTranslate(0, surfaceOffset); frame.offset(0, offset); break; @@ -543,9 +543,10 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro // control may be null if it got revoked. continue; } - @InternalInsetsSide int side = InsetsState.getInsetSide(control.getInsetsHint()); - if (side == ISIDE_FLOATING && control.getType() == WindowInsets.Type.ime()) { - side = ISIDE_BOTTOM; + @InternalInsetsSide int side = InsetsSource.getInsetSide(control.getInsetsHint()); + if (side == SIDE_NONE && control.getType() == WindowInsets.Type.ime()) { + // IME might not provide insets when it is fullscreen or floating. + side = SIDE_BOTTOM; } sideControlsMap.add(side, control); } diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index 0927d4519b9c..86ab21348e4f 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -46,6 +46,24 @@ import java.util.StringJoiner; */ public class InsetsSource implements Parcelable { + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "SIDE_", value = { + SIDE_NONE, + SIDE_LEFT, + SIDE_TOP, + SIDE_RIGHT, + SIDE_BOTTOM, + SIDE_UNKNOWN + }) + public @interface InternalInsetsSide {} + + static final int SIDE_NONE = 0; + static final int SIDE_LEFT = 1; + static final int SIDE_TOP = 2; + static final int SIDE_RIGHT = 3; + static final int SIDE_BOTTOM = 4; + static final int SIDE_UNKNOWN = 5; + /** The insets source ID of IME */ public static final int ID_IME = createId(null, 0, ime()); @@ -101,6 +119,12 @@ public class InsetsSource implements Parcelable { private boolean mVisible; + /** + * Used to decide which side of the relative frame should receive insets when the frame fully + * covers the relative frame. + */ + private @InternalInsetsSide int mSideHint = SIDE_NONE; + private final Rect mTmpFrame = new Rect(); public InsetsSource(int id, @InsetsType int type) { @@ -119,6 +143,7 @@ public class InsetsSource implements Parcelable { ? new Rect(other.mVisibleFrame) : null; mFlags = other.mFlags; + mSideHint = other.mSideHint; } public void set(InsetsSource other) { @@ -128,6 +153,7 @@ public class InsetsSource implements Parcelable { ? new Rect(other.mVisibleFrame) : null; mFlags = other.mFlags; + mSideHint = other.mSideHint; } public InsetsSource setFrame(int left, int top, int right, int bottom) { @@ -160,6 +186,18 @@ public class InsetsSource implements Parcelable { return this; } + /** + * Updates the side hint which is used to decide which side of the relative frame should receive + * insets when the frame fully covers the relative frame. + * + * @param bounds A rectangle which contains the frame. It will be used to calculate the hint. + */ + public InsetsSource updateSideHint(Rect bounds) { + mSideHint = getInsetSide( + calculateInsets(bounds, mFrame, true /* ignoreVisibility */)); + return this; + } + public int getId() { return mId; } @@ -236,8 +274,21 @@ public class InsetsSource implements Parcelable { return Insets.of(0, 0, 0, mTmpFrame.height()); } - // Intersecting at top/bottom - if (mTmpFrame.width() == relativeFrame.width()) { + if (mTmpFrame.equals(relativeFrame)) { + // Covering all sides + switch (mSideHint) { + default: + case SIDE_LEFT: + return Insets.of(mTmpFrame.width(), 0, 0, 0); + case SIDE_TOP: + return Insets.of(0, mTmpFrame.height(), 0, 0); + case SIDE_RIGHT: + return Insets.of(0, 0, mTmpFrame.width(), 0); + case SIDE_BOTTOM: + return Insets.of(0, 0, 0, mTmpFrame.height()); + } + } else if (mTmpFrame.width() == relativeFrame.width()) { + // Intersecting at top/bottom if (mTmpFrame.top == relativeFrame.top) { return Insets.of(0, mTmpFrame.height(), 0, 0); } else if (mTmpFrame.bottom == relativeFrame.bottom) { @@ -249,9 +300,8 @@ public class InsetsSource implements Parcelable { if (mTmpFrame.top == 0) { return Insets.of(0, mTmpFrame.height(), 0, 0); } - } - // Intersecting at left/right - else if (mTmpFrame.height() == relativeFrame.height()) { + } else if (mTmpFrame.height() == relativeFrame.height()) { + // Intersecting at left/right if (mTmpFrame.left == relativeFrame.left) { return Insets.of(mTmpFrame.width(), 0, 0, 0); } else if (mTmpFrame.right == relativeFrame.right) { @@ -283,6 +333,46 @@ public class InsetsSource implements Parcelable { } /** + * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b + * is set in order that this method returns a meaningful result. + */ + static @InternalInsetsSide int getInsetSide(Insets insets) { + if (Insets.NONE.equals(insets)) { + return SIDE_NONE; + } + if (insets.left != 0) { + return SIDE_LEFT; + } + if (insets.top != 0) { + return SIDE_TOP; + } + if (insets.right != 0) { + return SIDE_RIGHT; + } + if (insets.bottom != 0) { + return SIDE_BOTTOM; + } + return SIDE_UNKNOWN; + } + + static String sideToString(@InternalInsetsSide int side) { + switch (side) { + case SIDE_NONE: + return "NONE"; + case SIDE_LEFT: + return "LEFT"; + case SIDE_TOP: + return "TOP"; + case SIDE_RIGHT: + return "RIGHT"; + case SIDE_BOTTOM: + return "BOTTOM"; + default: + return "UNKNOWN:" + side; + } + } + + /** * Creates an identifier of an {@link InsetsSource}. * * @param owner An object owned by the owner. Only the owner can modify its own sources. @@ -331,7 +421,7 @@ public class InsetsSource implements Parcelable { } public static String flagsToString(@Flags int flags) { - final StringJoiner joiner = new StringJoiner(" "); + final StringJoiner joiner = new StringJoiner("|"); if ((flags & FLAG_SUPPRESS_SCRIM) != 0) { joiner.add("SUPPRESS_SCRIM"); } @@ -371,6 +461,7 @@ public class InsetsSource implements Parcelable { } pw.print(" visible="); pw.print(mVisible); pw.print(" flags="); pw.print(flagsToString(mFlags)); + pw.print(" sideHint="); pw.print(sideToString(mSideHint)); pw.println(); } @@ -393,6 +484,7 @@ public class InsetsSource implements Parcelable { if (mType != that.mType) return false; if (mVisible != that.mVisible) return false; if (mFlags != that.mFlags) return false; + if (mSideHint != that.mSideHint) return false; if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true; if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false; return mFrame.equals(that.mFrame); @@ -400,7 +492,7 @@ public class InsetsSource implements Parcelable { @Override public int hashCode() { - return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags); + return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags, mSideHint); } public InsetsSource(Parcel in) { @@ -414,6 +506,7 @@ public class InsetsSource implements Parcelable { } mVisible = in.readBoolean(); mFlags = in.readInt(); + mSideHint = in.readInt(); } @Override @@ -434,6 +527,7 @@ public class InsetsSource implements Parcelable { } dest.writeBoolean(mVisible); dest.writeInt(mFlags); + dest.writeInt(mSideHint); } @Override @@ -442,7 +536,8 @@ public class InsetsSource implements Parcelable { + " mType=" + WindowInsets.Type.toString(mType) + " mFrame=" + mFrame.toShortString() + " mVisible=" + mVisible - + " mFlags=[" + flagsToString(mFlags) + "]" + + " mFlags=" + flagsToString(mFlags) + + " mSideHint=" + sideToString(mSideHint) + "}"; } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 59e0932ecd80..c88da9e2eb4f 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -37,7 +37,6 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.WindowConfiguration.ActivityType; @@ -48,6 +47,7 @@ import android.os.Parcelable; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.proto.ProtoOutputStream; +import android.view.InsetsSource.InternalInsetsSide; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; @@ -55,8 +55,6 @@ import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.StringJoiner; @@ -66,23 +64,6 @@ import java.util.StringJoiner; */ public class InsetsState implements Parcelable { - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = "ISIDE", value = { - ISIDE_LEFT, - ISIDE_TOP, - ISIDE_RIGHT, - ISIDE_BOTTOM, - ISIDE_FLOATING, - ISIDE_UNKNOWN - }) - public @interface InternalInsetsSide {} - static final int ISIDE_LEFT = 0; - static final int ISIDE_TOP = 1; - static final int ISIDE_RIGHT = 2; - static final int ISIDE_BOTTOM = 3; - static final int ISIDE_FLOATING = 4; - static final int ISIDE_UNKNOWN = 5; - private final SparseArray<InsetsSource> mSources; /** @@ -398,37 +379,14 @@ public class InsetsState implements Parcelable { } if (idSideMap != null) { - @InternalInsetsSide int insetSide = getInsetSide(insets); - if (insetSide != ISIDE_UNKNOWN) { + @InternalInsetsSide int insetSide = InsetsSource.getInsetSide(insets); + if (insetSide != InsetsSource.SIDE_UNKNOWN) { idSideMap.put(source.getId(), insetSide); } } } /** - * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b - * is set in order that this method returns a meaningful result. - */ - static @InternalInsetsSide int getInsetSide(Insets insets) { - if (Insets.NONE.equals(insets)) { - return ISIDE_FLOATING; - } - if (insets.left != 0) { - return ISIDE_LEFT; - } - if (insets.top != 0) { - return ISIDE_TOP; - } - if (insets.right != 0) { - return ISIDE_RIGHT; - } - if (insets.bottom != 0) { - return ISIDE_BOTTOM; - } - return ISIDE_UNKNOWN; - } - - /** * Gets the source mapped from the ID, or creates one if no such mapping has been made. */ public InsetsSource getOrCreateSource(int id, int type) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3c36227eda0a..7bc832ef9e3f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -11991,7 +11991,7 @@ public final class ViewRootImpl implements ViewParent, Runnable timeoutRunnable = () -> Log.e(mTag, "Failed to submit the sync transaction after 4s. Likely to ANR " + "soon"); - mHandler.postDelayed(timeoutRunnable, 4L * Build.HW_TIMEOUT_MULTIPLIER); + mHandler.postDelayed(timeoutRunnable, 4000L * Build.HW_TIMEOUT_MULTIPLIER); transaction.addTransactionCommittedListener(mSimpleExecutor, () -> mHandler.removeCallbacks(timeoutRunnable)); surfaceSyncGroup.addTransaction(transaction); diff --git a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java index bf1d31c8496d..fbb66d1485dd 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java @@ -149,9 +149,12 @@ public final class MainContentCaptureSessionV2 extends ContentCaptureSession { * * Because it is not guaranteed that the events will be enqueued from a single thread, the * implementation must be thread-safe to prevent unexpected behaviour. + * + * @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @NonNull - private final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue; + public final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue; /** * List of events held to be sent to the {@link ContentCaptureService} as a batch. @@ -908,7 +911,7 @@ public final class MainContentCaptureSessionV2 extends ContentCaptureSession { * clear the buffer events then starting sending out current event. */ private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) { - if (forceFlush) { + if (forceFlush || mEventProcessQueue.size() >= mManager.mOptions.maxBufferSize - 1) { // The buffer events are cleared in the same thread first to prevent new events // being added during the time of context switch. This would disrupt the sequence // of events. diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig index 9f9b7b4b68a9..1dd99baf8d2a 100644 --- a/core/java/android/view/flags/view_flags.aconfig +++ b/core/java/android/view/flags/view_flags.aconfig @@ -8,6 +8,15 @@ flag { } flag { + name: "enable_surface_native_alloc_registration_ro" + namespace: "toolkit" + description: "Feature flag for registering surfaces with the VM for faster" + " cleanup. Fixed readonly version." + bug: "306193257" + is_fixed_read_only: true +} + +flag { name: "enable_use_measure_cache_during_force_layout" namespace: "toolkit" description: "Enables using the measure cache during a view force layout from the second " diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java index 5dbf328b6c0a..93297e64c621 100644 --- a/core/java/android/window/TaskFragmentCreationParams.java +++ b/core/java/android/window/TaskFragmentCreationParams.java @@ -94,11 +94,18 @@ public final class TaskFragmentCreationParams implements Parcelable { @Nullable private final IBinder mPairedActivityToken; + /** + * If {@code true}, transitions are allowed even if the TaskFragment is empty. If + * {@code false}, transitions will wait until the TaskFragment becomes non-empty or other + * conditions are met. Default to {@code false}. + */ + private final boolean mAllowTransitionWhenEmpty; + private TaskFragmentCreationParams( @NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect initialRelativeBounds, @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken, - @Nullable IBinder pairedActivityToken) { + @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty) { if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) { throw new IllegalArgumentException("pairedPrimaryFragmentToken and" + " pairedActivityToken should not be set at the same time."); @@ -110,6 +117,7 @@ public final class TaskFragmentCreationParams implements Parcelable { mWindowingMode = windowingMode; mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken; mPairedActivityToken = pairedActivityToken; + mAllowTransitionWhenEmpty = allowTransitionWhenEmpty; } @NonNull @@ -155,6 +163,11 @@ public final class TaskFragmentCreationParams implements Parcelable { return mPairedActivityToken; } + /** @hide */ + public boolean getAllowTransitionWhenEmpty() { + return mAllowTransitionWhenEmpty; + } + private TaskFragmentCreationParams(Parcel in) { mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in); mFragmentToken = in.readStrongBinder(); @@ -163,6 +176,7 @@ public final class TaskFragmentCreationParams implements Parcelable { mWindowingMode = in.readInt(); mPairedPrimaryFragmentToken = in.readStrongBinder(); mPairedActivityToken = in.readStrongBinder(); + mAllowTransitionWhenEmpty = in.readBoolean(); } /** @hide */ @@ -175,6 +189,7 @@ public final class TaskFragmentCreationParams implements Parcelable { dest.writeInt(mWindowingMode); dest.writeStrongBinder(mPairedPrimaryFragmentToken); dest.writeStrongBinder(mPairedActivityToken); + dest.writeBoolean(mAllowTransitionWhenEmpty); } @NonNull @@ -201,6 +216,7 @@ public final class TaskFragmentCreationParams implements Parcelable { + " windowingMode=" + mWindowingMode + " pairedFragmentToken=" + mPairedPrimaryFragmentToken + " pairedActivityToken=" + mPairedActivityToken + + " allowTransitionWhenEmpty=" + mAllowTransitionWhenEmpty + "}"; } @@ -234,6 +250,8 @@ public final class TaskFragmentCreationParams implements Parcelable { @Nullable private IBinder mPairedActivityToken; + private boolean mAllowTransitionWhenEmpty; + public Builder(@NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) { mOrganizer = organizer; @@ -298,12 +316,26 @@ public final class TaskFragmentCreationParams implements Parcelable { return this; } + /** + * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true}, + * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions + * will wait until the TaskFragment becomes non-empty or other conditions are met. Default + * to {@code false}. + * + * @hide + */ + @NonNull + public Builder setAllowTransitionWhenEmpty(boolean allowTransitionWhenEmpty) { + mAllowTransitionWhenEmpty = allowTransitionWhenEmpty; + return this; + } + /** Constructs the options to create TaskFragment with. */ @NonNull public TaskFragmentCreationParams build() { return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken, mInitialRelativeBounds, mWindowingMode, mPairedPrimaryFragmentToken, - mPairedActivityToken); + mPairedActivityToken, mAllowTransitionWhenEmpty); } } } diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index cbf6367b3d60..edfbea4f51a4 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -50,3 +50,11 @@ flag { description: "Whether we should allow hiding the size compat restart button" bug: "318840081" } + +flag { + name: "configurable_font_scale_default" + namespace: "large_screen_experiences_app_compat" + description: "Whether the font_scale is read from a device dependent configuration file" + bug: "319808237" + is_fixed_read_only: true +} diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index eeea17bf39dd..90ca95a7fbab 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -71,7 +71,7 @@ public class SystemUiSystemPropertiesFlags { "persist.debug.sysui.notification.notif_cooldown_t1", 60000); /** Value used by polite notif. feature */ public static final Flag NOTIF_COOLDOWN_T2 = devFlag( - "persist.debug.sysui.notification.notif_cooldown_t2", 5000); + "persist.debug.sysui.notification.notif_cooldown_t2", 10000); /** Value used by polite notif. feature */ public static final Flag NOTIF_VOLUME1 = devFlag( "persist.debug.sysui.notification.notif_volume1", 30); @@ -81,6 +81,10 @@ public class SystemUiSystemPropertiesFlags { public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag( "persist.debug.sysui.notification.notif_cooldown_counter_reset", 10); + /** Value used by polite notif. feature */ + public static final Flag NOTIF_AVALANCHE_TIMEOUT = devFlag( + "persist.debug.sysui.notification.notif_avalanche_timeout", 120_000); + /** b/303716154: For debugging only: use short bitmap duration. */ public static final Flag DEBUG_SHORT_BITMAP_DURATION = devFlag( "persist.sysui.notification.debug_short_bitmap_duration"); diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java index e58f4f06daea..88aa89aaf48b 100644 --- a/core/java/com/android/internal/logging/MetricsLogger.java +++ b/core/java/com/android/internal/logging/MetricsLogger.java @@ -34,6 +34,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class MetricsLogger { // define metric categories in frameworks/base/proto/src/metrics_constants.proto. // mirror changes in native version at system/core/libmetricslogger/metrics_logger.cpp diff --git a/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java b/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java index 6786427b5d86..df8bf313d06f 100644 --- a/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java +++ b/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java @@ -12,6 +12,7 @@ import java.util.Queue; * * @hide. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class FakeMetricsLogger extends MetricsLogger { private Queue<LogMaker> logs = new LinkedList<>(); diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java index e303890c245a..6787ddc5d64d 100644 --- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java +++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java @@ -27,6 +27,7 @@ import java.util.List; * * @hide. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class UiEventLoggerFake implements UiEventLogger { /** * Immutable data class used to record fake log events. diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 757978b71a01..b5b3a48dacb7 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -333,11 +333,17 @@ public class LockPatternUtils { @UnsupportedAppUsage public LockPatternUtils(Context context) { + this(context, null); + } + + @VisibleForTesting + public LockPatternUtils(Context context, ILockSettings lockSettings) { mContext = context; mContentResolver = context.getContentResolver(); Looper looper = Looper.myLooper(); mHandler = looper != null ? new Handler(looper) : null; + mLockSettingsService = lockSettings; } @UnsupportedAppUsage diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index c06f5f75514f..e07acac52f2c 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -21,6 +21,8 @@ import android.annotation.Px; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.os.Build; +import android.os.Trace; import android.util.AttributeSet; import android.view.RemotableViewMethod; import android.view.View; @@ -45,6 +47,8 @@ public class MessagingLinearLayout extends ViewGroup { private int mMaxDisplayedLines = Integer.MAX_VALUE; + private static final boolean TRACE_ONMEASURE = Build.isDebuggable(); + public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -67,6 +71,10 @@ public class MessagingLinearLayout extends ViewGroup { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (TRACE_ONMEASURE) { + Trace.beginSection("MessagingLinearLayout#onMeasure"); + trackMeasureSpecs(widthMeasureSpec, heightMeasureSpec); + } // This is essentially a bottom-up linear layout that only adds children that fit entirely // up to a maximum height. int targetHeight = MeasureSpec.getSize(heightMeasureSpec); @@ -177,6 +185,9 @@ public class MessagingLinearLayout extends ViewGroup { resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth), widthMeasureSpec), Math.max(getSuggestedMinimumHeight(), totalHeight)); + if (TRACE_ONMEASURE) { + Trace.endSection(); + } } @Override @@ -240,6 +251,25 @@ public class MessagingLinearLayout extends ViewGroup { } } + private void trackMeasureSpecs(int widthMeasureSpec, int heightMeasureSpec) { + if (!TRACE_ONMEASURE) { + return; + } + + final int availableWidth = MeasureSpec.getSize(widthMeasureSpec); + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int availableHeight = MeasureSpec.getSize(heightMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + Trace.setCounter("MessagingLinearLayout#onMeasure_widthMeasureSpecSize", + availableWidth); + Trace.setCounter("MessagingLinearLayout#onMeasure_widthMeasureSpecMode", + widthMode); + Trace.setCounter("MessagingLinearLayout#onMeasure_heightMeasureSpecSize", + availableHeight); + Trace.setCounter("MessagingLinearLayout#onMeasure_heightMeasureSpecMode", + heightMode); + } + @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 7af69f2dff08..6a640a5ab23b 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -28,6 +28,7 @@ #include <meminfo/sysmeminfo.h> #include <processgroup/processgroup.h> #include <processgroup/sched_policy.h> +#include <android-base/logging.h> #include <android-base/unique_fd.h> #include <algorithm> @@ -232,6 +233,31 @@ void android_os_Process_setThreadGroupAndCpuset(JNIEnv* env, jobject clazz, int } } +// Look up the user ID of a process in /proc/${pid}/status. The Uid: line is present in +// /proc/${pid}/status since at least kernel v2.5. +static int uid_from_pid(int pid) +{ + int uid = -1; + std::array<char, 64> path; + int res = snprintf(path.data(), path.size(), "/proc/%d/status", pid); + if (res < 0 || res >= static_cast<int>(path.size())) { + DCHECK(false); + return uid; + } + FILE* f = fopen(path.data(), "r"); + if (!f) { + return uid; + } + char line[256]; + while (fgets(line, sizeof(line), f)) { + if (sscanf(line, "Uid: %d", &uid) == 1) { + break; + } + } + fclose(f); + return uid; +} + void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp) { ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp); @@ -275,7 +301,12 @@ void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jin } } - if (!SetProcessProfilesCached(0, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)})) + const int uid = uid_from_pid(pid); + if (uid < 0) { + signalExceptionForGroupError(env, ESRCH, pid); + return; + } + if (!SetProcessProfilesCached(uid, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)})) signalExceptionForGroupError(env, errno ? errno : EPERM, pid); } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 25b2aaf3d737..98f409a334dc 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -343,7 +343,9 @@ public: const std::vector<SurfaceControlStats>& /*stats*/) { JNIEnv* env = getenv(); // Adding a strong reference for java SyncFence - presentFence->incStrong(0); + if (presentFence) { + presentFence->incStrong(0); + } jobject stats = env->NewObject(gTransactionStatsClassInfo.clazz, gTransactionStatsClassInfo.ctor, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 100259ef72b9..070e2cb0dc5e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7065,6 +7065,7 @@ android:protectionLevel="signature" /> <!-- @SystemApi Allows an application to access the smartspace service as a client. + @FlaggedApi(android.app.smartspace.flags.Flags.FLAG_ACCESS_SMARTSPACE) @hide <p>Not for use by third-party applications.</p> --> <permission android:name="android.permission.ACCESS_SMARTSPACE" android:protectionLevel="signature|privileged|development" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index d1a90aea835c..440630287b7c 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -212,8 +212,8 @@ android_ravenwood_test { "src/android/database/CursorWindowTest.java", "src/android/os/**/*.java", "src/android/util/**/*.java", + "src/com/android/internal/logging/**/*.java", "src/com/android/internal/os/**/*.java", - "src/com/android/internal/os/LongArrayMultiStateCounterTest.java", "src/com/android/internal/util/**/*.java", "src/com/android/internal/power/EnergyConsumerStatsTest.java", diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java index 2d3e12331e23..2a718ff2f4aa 100644 --- a/core/tests/coretests/src/android/os/BuildTest.java +++ b/core/tests/coretests/src/android/os/BuildTest.java @@ -20,7 +20,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.ravenwood.RavenwoodRule; @@ -71,7 +70,6 @@ public class BuildTest { */ @Test @SmallTest - @IgnoreUnderRavenwood(blockedBy = Build.class) public void testBuildFields() throws Exception { assertNotEmpty("ID", Build.ID); assertNotEmpty("DISPLAY", Build.DISPLAY); diff --git a/core/tests/coretests/src/android/os/TraceTest.java b/core/tests/coretests/src/android/os/TraceTest.java index 593833ec96a7..b2c005f8a4f4 100644 --- a/core/tests/coretests/src/android/os/TraceTest.java +++ b/core/tests/coretests/src/android/os/TraceTest.java @@ -34,7 +34,6 @@ import org.junit.runner.RunWith; * while tracing on the emulator and then run traceview to view the trace. */ @RunWith(AndroidJUnit4.class) -@IgnoreUnderRavenwood(blockedBy = Trace.class) public class TraceTest { private static final String TAG = "TraceTest"; @@ -46,7 +45,51 @@ public class TraceTest { private int gMethodCalls = 0; @Test + public void testEnableDisable() { + // Currently only verifying that we can invoke without crashing + Trace.setTracingEnabled(true, 0); + Trace.setTracingEnabled(false, 0); + + Trace.setAppTracingAllowed(true); + Trace.setAppTracingAllowed(false); + } + + @Test + public void testBeginEnd() { + // Currently only verifying that we can invoke without crashing + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42); + Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42); + + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, TAG, 42); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42); + + Trace.beginSection(TAG); + Trace.endSection(); + + Trace.beginAsyncSection(TAG, 42); + Trace.endAsyncSection(TAG, 42); + } + + @Test + public void testCounter() { + // Currently only verifying that we can invoke without crashing + Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42); + Trace.setCounter(TAG, 42); + } + + @Test + public void testInstant() { + // Currently only verifying that we can invoke without crashing + Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG); + Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, TAG); + } + + @Test public void testNullStrings() { + // Currently only verifying that we can invoke without crashing Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, 42); Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, null); @@ -62,6 +105,7 @@ public class TraceTest { @Test @SmallTest + @IgnoreUnderRavenwood(blockedBy = Debug.class) public void testNativeTracingFromJava() { long start = System.currentTimeMillis(); @@ -82,6 +126,7 @@ public class TraceTest { // This should not run in the automated suite. @Suppress + @IgnoreUnderRavenwood(blockedBy = Debug.class) public void disableTestNativeTracingFromC() { long start = System.currentTimeMillis(); @@ -97,6 +142,7 @@ public class TraceTest { @Test @LargeTest @Suppress // Failing. + @IgnoreUnderRavenwood(blockedBy = Debug.class) public void testMethodTracing() { long start = System.currentTimeMillis(); diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index 906d84ec96b6..672875a19f9f 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -20,8 +20,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; import static android.view.InsetsSource.ID_IME; -import static android.view.InsetsState.ISIDE_BOTTOM; -import static android.view.InsetsState.ISIDE_TOP; +import static android.view.InsetsSource.SIDE_BOTTOM; +import static android.view.InsetsSource.SIDE_TOP; import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT; import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT; import static android.view.RoundedCorner.POSITION_TOP_LEFT; @@ -106,8 +106,8 @@ public class InsetsStateTest { typeSideMap); assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all())); - assertEquals(ISIDE_TOP, typeSideMap.get(ID_STATUS_BAR)); - assertEquals(ISIDE_BOTTOM, typeSideMap.get(ID_IME)); + assertEquals(SIDE_TOP, typeSideMap.get(ID_STATUS_BAR)); + assertEquals(SIDE_BOTTOM, typeSideMap.get(ID_IME)); assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars())); assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(ime())); } diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java index f0f3a9683353..00751288ad62 100644 --- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java +++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java @@ -433,6 +433,72 @@ public class MainContentCaptureSessionV2Test { assertThat(session.mEvents).isEmpty(); } + @Test + public void notifyViewAppearedBelowMaximumBufferSize() throws RemoteException { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSessionV2 session = createSession(options); + session.mDirectServiceInterface = mMockContentCaptureDirectManager; + + session.onSessionStarted(0x2, null); + for (int i = 0; i < BUFFER_SIZE - 1; i++) { + View view = prepareView(session); + session.notifyViewAppeared(session.newViewStructure(view)); + } + mTestableLooper.processAllMessages(); + + verify(mMockContentCaptureDirectManager, times(0)) + .sendEvents(any(), anyInt(), any()); + assertThat(session.mEvents).isNull(); + assertThat(session.mEventProcessQueue).hasSize(BUFFER_SIZE - 1); + } + + @Test + public void notifyViewAppearedExactAsMaximumBufferSize() throws RemoteException { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSessionV2 session = createSession(options); + session.mDirectServiceInterface = mMockContentCaptureDirectManager; + + session.onSessionStarted(0x2, null); + for (int i = 0; i < BUFFER_SIZE; i++) { + View view = prepareView(session); + session.notifyViewAppeared(session.newViewStructure(view)); + } + mTestableLooper.processAllMessages(); + + verify(mMockContentCaptureDirectManager, times(1)) + .sendEvents(any(), anyInt(), any()); + assertThat(session.mEvents).isEmpty(); + assertThat(session.mEventProcessQueue).isEmpty(); + } + + @Test + public void notifyViewAppearedAboveMaximumBufferSize() throws RemoteException { + ContentCaptureOptions options = + createOptions( + /* enableContentCaptureReceiver= */ true, + /* enableContentProtectionReceiver= */ true); + MainContentCaptureSessionV2 session = createSession(options); + session.mDirectServiceInterface = mMockContentCaptureDirectManager; + + session.onSessionStarted(0x2, null); + for (int i = 0; i < BUFFER_SIZE * 2 + 1; i++) { + View view = prepareView(session); + session.notifyViewAppeared(session.newViewStructure(view)); + } + mTestableLooper.processAllMessages(); + + verify(mMockContentCaptureDirectManager, times(2)) + .sendEvents(any(), anyInt(), any()); + assertThat(session.mEvents).isEmpty(); + assertThat(session.mEventProcessQueue).hasSize(1); + } + /** Simulates the regular content capture events sequence. */ private void notifyContentCaptureEvents(final MainContentCaptureSessionV2 session) { final ArrayList<Object> events = new ArrayList<>( diff --git a/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java new file mode 100644 index 000000000000..7054cc0f24b4 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java @@ -0,0 +1,67 @@ +/* + * 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.internal.logging; + +import static com.google.common.truth.Truth.assertThat; + +import android.metrics.LogMaker; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.internal.logging.testing.FakeMetricsLogger; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class MetricsLoggerTest { + private FakeMetricsLogger mLogger; + + private static final int TEST_ACTION = 42; + + @Before + public void setUp() throws Exception { + mLogger = new FakeMetricsLogger(); + } + + @Test + public void testEmpty() throws Exception { + assertThat(mLogger.getLogs().size()).isEqualTo(0); + } + + @Test + public void testAction() throws Exception { + mLogger.action(TEST_ACTION); + assertThat(mLogger.getLogs().size()).isEqualTo(1); + final LogMaker event = mLogger.getLogs().peek(); + assertThat(event.getType()).isEqualTo(MetricsProto.MetricsEvent.TYPE_ACTION); + assertThat(event.getCategory()).isEqualTo(TEST_ACTION); + } + + @Test + public void testVisible() throws Exception { + // Limited testing to confirm we don't crash + mLogger.visible(TEST_ACTION); + mLogger.hidden(TEST_ACTION); + mLogger.visibility(TEST_ACTION, true); + mLogger.visibility(TEST_ACTION, false); + } +} diff --git a/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java new file mode 100644 index 000000000000..7840f7177278 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java @@ -0,0 +1,80 @@ +/* + * 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.internal.logging; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.logging.testing.UiEventLoggerFake; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class UiEventLoggerTest { + private UiEventLoggerFake mLogger; + + private static final int TEST_EVENT_ID = 42; + private static final int TEST_INSTANCE_ID = 21; + + private enum MyUiEventEnum implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "Example event") + TEST_EVENT(TEST_EVENT_ID); + + private final int mId; + + MyUiEventEnum(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } + + private InstanceId TEST_INSTANCE = InstanceId.fakeInstanceId(TEST_INSTANCE_ID); + + @Before + public void setUp() throws Exception { + mLogger = new UiEventLoggerFake(); + } + + @Test + public void testEmpty() throws Exception { + assertThat(mLogger.numLogs()).isEqualTo(0); + } + + @Test + public void testSimple() throws Exception { + mLogger.log(MyUiEventEnum.TEST_EVENT); + assertThat(mLogger.numLogs()).isEqualTo(1); + assertThat(mLogger.eventId(0)).isEqualTo(TEST_EVENT_ID); + } + + @Test + public void testWithInstance() throws Exception { + mLogger.log(MyUiEventEnum.TEST_EVENT, TEST_INSTANCE); + assertThat(mLogger.numLogs()).isEqualTo(1); + assertThat(mLogger.eventId(0)).isEqualTo(TEST_EVENT_ID); + assertThat(mLogger.get(0).instanceId.getId()).isEqualTo(TEST_INSTANCE_ID); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java index ec4c563e469e..06d888b59166 100644 --- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java +++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java @@ -18,10 +18,14 @@ package com.android.internal.os; import static com.google.common.truth.Truth.assertThat; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,6 +37,9 @@ import java.nio.file.Files; @RunWith(AndroidJUnit4.class) @SmallTest public class MonotonicClockTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private final MockClock mClock = new MockClock(); private File mFile; @@ -70,6 +77,7 @@ public class MonotonicClockTest { } @Test + @IgnoreUnderRavenwood(reason = "b/321832617") public void corruptedFile() throws IOException { // Create an invalid binary XML file to cause IOException: "Unexpected magic number" try (FileWriter w = new FileWriter(mFile)) { diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java index 1a668f7bdc8f..b90480a1e794 100644 --- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java @@ -16,20 +16,279 @@ package com.android.internal.widget; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; + +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.app.trust.TrustManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.UserInfo; +import android.os.Looper; +import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; +import android.provider.Settings; +import android.test.mock.MockContentResolver; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.test.FakeSettingsProvider; + +import com.google.android.collect.Lists; + +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +import java.nio.charset.StandardCharsets; +import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest +@IgnoreUnderRavenwood(blockedBy = LockPatternUtils.class) public class LockPatternUtilsTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + private ILockSettings mLockSettings; + private static final int USER_ID = 1; + private static final int DEMO_USER_ID = 5; + + private LockPatternUtils mLockPatternUtils; + + private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode) + throws Exception { + mLockSettings = mock(ILockSettings.class); + final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + + final MockContentResolver cr = new MockContentResolver(context); + cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + when(context.getContentResolver()).thenReturn(cr); + Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode); + + when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn( + isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD + : LockPatternUtils.CREDENTIAL_TYPE_NONE); + when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED, + DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED); + when(mLockSettings.hasSecureLockScreen()).thenReturn(true); + mLockPatternUtils = new LockPatternUtils(context, mLockSettings); + + final UserInfo userInfo = mock(UserInfo.class); + when(userInfo.isDemo()).thenReturn(isDemoUser); + final UserManager um = mock(UserManager.class); + when(um.getUserInfo(DEMO_USER_ID)).thenReturn(userInfo); + when(context.getSystemService(Context.USER_SERVICE)).thenReturn(um); + } + + @Test + public void isUserInLockDown() throws Exception { + configureTest(true, false, 2); + + // GIVEN strong auth not required + when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED); + + // THEN user isn't in lockdown + assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID)); + + // GIVEN lockdown + when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn( + STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + + // THEN user is in lockdown + assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID)); + + // GIVEN lockdown and lockout + when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn( + STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT); + + // THEN user is in lockdown + assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID)); + } + + @Test + public void isLockScreenDisabled_isDemoUser_true() throws Exception { + configureTest(false, true, 2); + assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID)); + } + + @Test + public void isLockScreenDisabled_isSecureAndDemoUser_false() throws Exception { + configureTest(true, true, 2); + assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID)); + } + + @Test + public void isLockScreenDisabled_isNotDemoUser_false() throws Exception { + configureTest(false, false, 2); + assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID)); + } + + @Test + public void isLockScreenDisabled_isNotInDemoMode_false() throws Exception { + configureTest(false, true, 0); + assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID)); + } + + @Test + public void testAddWeakEscrowToken() throws RemoteException { + ILockSettings ils = createTestLockSettings(); + byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8); + int testUserId = 10; + IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener(); + mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener); + verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener)); + } + + @Test + public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException { + ILockSettings ils = createTestLockSettings(); + IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener(); + mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener); + verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener)); + } + + @Test + public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException { + ILockSettings ils = createTestLockSettings(); + IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener(); + mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener); + verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener)); + } + + @Test + public void testRemoveAutoEscrowToken() throws RemoteException { + ILockSettings ils = createTestLockSettings(); + int testUserId = 10; + long testHandle = 100L; + mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId); + verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId)); + } + + @Test + public void testIsAutoEscrowTokenActive() throws RemoteException { + ILockSettings ils = createTestLockSettings(); + int testUserId = 10; + long testHandle = 100L; + mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId); + verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId)); + } + + @Test + public void testIsAutoEscrowTokenValid() throws RemoteException { + ILockSettings ils = createTestLockSettings(); + int testUserId = 10; + byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8); + long testHandle = 100L; + mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId); + verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId)); + } + + @Test + public void testSetEnabledTrustAgents() throws RemoteException { + int testUserId = 10; + ILockSettings ils = createTestLockSettings(); + ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class); + doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt()); + List<ComponentName> enabledTrustAgents = Lists.newArrayList( + ComponentName.unflattenFromString("com.android/.TrustAgent"), + ComponentName.unflattenFromString("com.test/.TestAgent")); + + mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId); + + assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent"); + } + + @Test + public void testGetEnabledTrustAgents() throws RemoteException { + int testUserId = 10; + ILockSettings ils = createTestLockSettings(); + when(ils.getString(anyString(), any(), anyInt())).thenReturn( + "com.android/.TrustAgent,com.test/.TestAgent"); + + List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId); + + assertThat(trustAgents).containsExactly( + ComponentName.unflattenFromString("com.android/.TrustAgent"), + ComponentName.unflattenFromString("com.test/.TestAgent")); + } + + @Test + public void testSetKnownTrustAgents() throws RemoteException { + int testUserId = 10; + ILockSettings ils = createTestLockSettings(); + ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class); + doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt()); + List<ComponentName> knownTrustAgents = Lists.newArrayList( + ComponentName.unflattenFromString("com.android/.TrustAgent"), + ComponentName.unflattenFromString("com.test/.TestAgent")); + + mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId); + + assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent"); + } + + @Test + public void testGetKnownTrustAgents() throws RemoteException { + int testUserId = 10; + ILockSettings ils = createTestLockSettings(); + when(ils.getString(anyString(), any(), anyInt())).thenReturn( + "com.android/.TrustAgent,com.test/.TestAgent"); + + List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId); + + assertThat(trustAgents).containsExactly( + ComponentName.unflattenFromString("com.android/.TrustAgent"), + ComponentName.unflattenFromString("com.test/.TestAgent")); + } + + @Test + public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue() + throws RemoteException { + TestStrongAuthTracker tracker = createStrongAuthTracker(); + tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED); + + assertTrue(tracker.isBiometricAllowedForUser( + /* isStrongBiometric = */ true, + DEMO_USER_ID)); + } + + @Test + public void isBiometricAllowedForUser_afterLockout_returnsFalse() + throws RemoteException { + TestStrongAuthTracker tracker = createStrongAuthTracker(); + tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT); + + assertFalse(tracker.isBiometricAllowedForUser( + /* isStrongBiometric = */ true, + DEMO_USER_ID)); + } @Test public void testUserFrp_isNotRegularUser() throws Exception { @@ -56,4 +315,49 @@ public class LockPatternUtilsTest { assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_REPAIR_MODE); assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_REPAIR_MODE); } + + private TestStrongAuthTracker createStrongAuthTracker() { + final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext()); + return new TestStrongAuthTracker(context, Looper.getMainLooper()); + } + + private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker { + + TestStrongAuthTracker(Context context, Looper looper) { + super(context, looper); + } + + public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) { + handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID); + } + } + + private ILockSettings createTestLockSettings() { + final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + + final TrustManager trustManager = mock(TrustManager.class); + when(context.getSystemService(Context.TRUST_SERVICE)).thenReturn(trustManager); + + final ILockSettings ils = mock(ILockSettings.class); + mLockPatternUtils = new LockPatternUtils(context, ils); + return ils; + } + + private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() { + return new IWeakEscrowTokenActivatedListener.Stub() { + @Override + public void onWeakEscrowTokenActivated(long handle, int userId) { + // Do nothing. + } + }; + } + + private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() { + return new IWeakEscrowTokenRemovedListener.Stub() { + @Override + public void onWeakEscrowTokenRemoved(long handle, int userId) { + // Do nothing. + } + }; + } } diff --git a/core/tests/systemproperties/Android.bp b/core/tests/systemproperties/Android.bp index 765ca3e5110a..21aa3c44044b 100644 --- a/core/tests/systemproperties/Android.bp +++ b/core/tests/systemproperties/Android.bp @@ -15,6 +15,9 @@ android_test { static_libs: [ "android-common", "frameworks-core-util-lib", + "androidx.test.rules", + "androidx.test.ext.junit", + "ravenwood-junit", ], libs: [ "android.test.runner", @@ -23,3 +26,22 @@ android_test { platform_apis: true, certificate: "platform", } + +android_ravenwood_test { + name: "FrameworksCoreSystemPropertiesTestsRavenwood", + static_libs: [ + "android-common", + "frameworks-core-util-lib", + "androidx.test.rules", + "androidx.test.ext.junit", + "ravenwood-junit", + ], + libs: [ + "android.test.runner", + "android.test.base", + ], + srcs: [ + "src/**/*.java", + ], + auto_gen_config: true, +} diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java index 67783bff9299..ea65de088c07 100644 --- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java +++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java @@ -16,19 +16,36 @@ package android.os; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.platform.test.ravenwood.RavenwoodRule; import android.test.suitebuilder.annotation.SmallTest; -import junit.framework.TestCase; +import org.junit.Rule; +import org.junit.Test; import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -public class SystemPropertiesTest extends TestCase { +public class SystemPropertiesTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setSystemPropertyMutable(KEY, null) + .setSystemPropertyMutable(UNSET_KEY, null) + .setSystemPropertyMutable(PERSIST_KEY, null) + .build(); + private static final String KEY = "sys.testkey"; private static final String UNSET_KEY = "Aiw7woh6ie4toh7W"; private static final String PERSIST_KEY = "persist.sys.testkey"; + @Test @SmallTest public void testStressPersistPropertyConsistency() throws Exception { for (int i = 0; i < 100; ++i) { @@ -38,6 +55,7 @@ public class SystemPropertiesTest extends TestCase { } } + @Test @SmallTest public void testStressMemoryPropertyConsistency() throws Exception { for (int i = 0; i < 100; ++i) { @@ -47,6 +65,7 @@ public class SystemPropertiesTest extends TestCase { } } + @Test @SmallTest public void testProperties() throws Exception { String value; @@ -93,6 +112,7 @@ public class SystemPropertiesTest extends TestCase { assertEquals(expected, value); } + @Test @SmallTest public void testHandle() throws Exception { String value; @@ -114,6 +134,7 @@ public class SystemPropertiesTest extends TestCase { assertEquals(12345, handle.getInt(12345)); } + @Test @SmallTest public void testIntegralProperties() throws Exception { testInt("", 123, 123); @@ -133,6 +154,7 @@ public class SystemPropertiesTest extends TestCase { testLong("-3147483647", 124, -3147483647L); } + @Test @SmallTest public void testUnset() throws Exception { assertEquals("abc", SystemProperties.get(UNSET_KEY, "abc")); @@ -142,6 +164,7 @@ public class SystemPropertiesTest extends TestCase { assertEquals(-10, SystemProperties.getLong(UNSET_KEY, -10)); } + @Test @SmallTest @SuppressWarnings("null") public void testNullKey() throws Exception { @@ -176,6 +199,7 @@ public class SystemPropertiesTest extends TestCase { } } + @Test @SmallTest public void testCallbacks() { // Latches are not really necessary, but are easy to use. @@ -220,6 +244,7 @@ public class SystemPropertiesTest extends TestCase { } } + @Test @SmallTest public void testDigestOf() { final String empty = SystemProperties.digestOf(); diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java deleted file mode 100644 index dcaf67660ffa..000000000000 --- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.util; - -import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED; -import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; - -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED; - -import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.ComponentName; -import android.content.Context; -import android.content.ContextWrapper; -import android.content.pm.UserInfo; -import android.os.Looper; -import android.os.RemoteException; -import android.os.UserManager; -import android.platform.test.annotations.IgnoreUnderRavenwood; -import android.platform.test.ravenwood.RavenwoodRule; -import android.provider.Settings; -import android.test.mock.MockContentResolver; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.util.test.FakeSettingsProvider; -import com.android.internal.widget.ILockSettings; -import com.android.internal.widget.IWeakEscrowTokenActivatedListener; -import com.android.internal.widget.IWeakEscrowTokenRemovedListener; -import com.android.internal.widget.LockPatternUtils; - -import com.google.android.collect.Lists; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; - -import java.nio.charset.StandardCharsets; -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@SmallTest -@IgnoreUnderRavenwood(blockedBy = LockPatternUtils.class) -public class LockPatternUtilsTest { - @Rule - public final RavenwoodRule mRavenwood = new RavenwoodRule(); - - private ILockSettings mLockSettings; - private static final int USER_ID = 1; - private static final int DEMO_USER_ID = 5; - - private LockPatternUtils mLockPatternUtils; - - private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode) - throws Exception { - mLockSettings = Mockito.mock(ILockSettings.class); - final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); - - final MockContentResolver cr = new MockContentResolver(context); - cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - when(context.getContentResolver()).thenReturn(cr); - Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode); - - when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn( - isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD - : LockPatternUtils.CREDENTIAL_TYPE_NONE); - when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED, - DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED); - // TODO(b/63758238): stop spying the class under test - mLockPatternUtils = spy(new LockPatternUtils(context)); - when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings); - doReturn(true).when(mLockPatternUtils).hasSecureLockScreen(); - - final UserInfo userInfo = Mockito.mock(UserInfo.class); - when(userInfo.isDemo()).thenReturn(isDemoUser); - final UserManager um = Mockito.mock(UserManager.class); - when(um.getUserInfo(DEMO_USER_ID)).thenReturn(userInfo); - when(context.getSystemService(Context.USER_SERVICE)).thenReturn(um); - } - - @Test - public void isUserInLockDown() throws Exception { - configureTest(true, false, 2); - - // GIVEN strong auth not required - when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED); - - // THEN user isn't in lockdown - assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID)); - - // GIVEN lockdown - when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn( - STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); - - // THEN user is in lockdown - assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID)); - - // GIVEN lockdown and lockout - when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn( - STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT); - - // THEN user is in lockdown - assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID)); - } - - @Test - public void isLockScreenDisabled_isDemoUser_true() throws Exception { - configureTest(false, true, 2); - assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID)); - } - - @Test - public void isLockScreenDisabled_isSecureAndDemoUser_false() throws Exception { - configureTest(true, true, 2); - assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID)); - } - - @Test - public void isLockScreenDisabled_isNotDemoUser_false() throws Exception { - configureTest(false, false, 2); - assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID)); - } - - @Test - public void isLockScreenDisabled_isNotInDemoMode_false() throws Exception { - configureTest(false, true, 0); - assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID)); - } - - @Test - public void testAddWeakEscrowToken() throws RemoteException { - ILockSettings ils = createTestLockSettings(); - byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8); - int testUserId = 10; - IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener(); - mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener); - verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener)); - } - - @Test - public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException { - ILockSettings ils = createTestLockSettings(); - IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener(); - mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener); - verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener)); - } - - @Test - public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException { - ILockSettings ils = createTestLockSettings(); - IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener(); - mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener); - verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener)); - } - - @Test - public void testRemoveAutoEscrowToken() throws RemoteException { - ILockSettings ils = createTestLockSettings(); - int testUserId = 10; - long testHandle = 100L; - mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId); - verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId)); - } - - @Test - public void testIsAutoEscrowTokenActive() throws RemoteException { - ILockSettings ils = createTestLockSettings(); - int testUserId = 10; - long testHandle = 100L; - mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId); - verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId)); - } - - @Test - public void testIsAutoEscrowTokenValid() throws RemoteException { - ILockSettings ils = createTestLockSettings(); - int testUserId = 10; - byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8); - long testHandle = 100L; - mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId); - verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId)); - } - - @Test - public void testSetEnabledTrustAgents() throws RemoteException { - int testUserId = 10; - ILockSettings ils = createTestLockSettings(); - ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class); - doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt()); - List<ComponentName> enabledTrustAgents = Lists.newArrayList( - ComponentName.unflattenFromString("com.android/.TrustAgent"), - ComponentName.unflattenFromString("com.test/.TestAgent")); - - mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId); - - assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent"); - } - - @Test - public void testGetEnabledTrustAgents() throws RemoteException { - int testUserId = 10; - ILockSettings ils = createTestLockSettings(); - when(ils.getString(anyString(), any(), anyInt())).thenReturn( - "com.android/.TrustAgent,com.test/.TestAgent"); - - List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId); - - assertThat(trustAgents).containsExactly( - ComponentName.unflattenFromString("com.android/.TrustAgent"), - ComponentName.unflattenFromString("com.test/.TestAgent")); - } - - @Test - public void testSetKnownTrustAgents() throws RemoteException { - int testUserId = 10; - ILockSettings ils = createTestLockSettings(); - ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class); - doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt()); - List<ComponentName> knownTrustAgents = Lists.newArrayList( - ComponentName.unflattenFromString("com.android/.TrustAgent"), - ComponentName.unflattenFromString("com.test/.TestAgent")); - - mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId); - - assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent"); - } - - @Test - public void testGetKnownTrustAgents() throws RemoteException { - int testUserId = 10; - ILockSettings ils = createTestLockSettings(); - when(ils.getString(anyString(), any(), anyInt())).thenReturn( - "com.android/.TrustAgent,com.test/.TestAgent"); - - List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId); - - assertThat(trustAgents).containsExactly( - ComponentName.unflattenFromString("com.android/.TrustAgent"), - ComponentName.unflattenFromString("com.test/.TestAgent")); - } - - @Test - public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue() - throws RemoteException { - TestStrongAuthTracker tracker = createStrongAuthTracker(); - tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED); - - assertTrue(tracker.isBiometricAllowedForUser( - /* isStrongBiometric = */ true, - DEMO_USER_ID)); - } - - @Test - public void isBiometricAllowedForUser_afterLockout_returnsFalse() - throws RemoteException { - TestStrongAuthTracker tracker = createStrongAuthTracker(); - tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT); - - assertFalse(tracker.isBiometricAllowedForUser( - /* isStrongBiometric = */ true, - DEMO_USER_ID)); - } - - - private TestStrongAuthTracker createStrongAuthTracker() { - final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext()); - return new TestStrongAuthTracker(context, Looper.getMainLooper()); - } - - private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker { - - TestStrongAuthTracker(Context context, Looper looper) { - super(context, looper); - } - - public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) { - handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID); - } - } - - private ILockSettings createTestLockSettings() { - final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); - mLockPatternUtils = spy(new LockPatternUtils(context)); - final ILockSettings ils = Mockito.mock(ILockSettings.class); - when(mLockPatternUtils.getLockSettings()).thenReturn(ils); - return ils; - } - - private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() { - return new IWeakEscrowTokenActivatedListener.Stub() { - @Override - public void onWeakEscrowTokenActivated(long handle, int userId) { - // Do nothing. - } - }; - } - - private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() { - return new IWeakEscrowTokenRemovedListener.Stub() { - @Override - public void onWeakEscrowTokenRemoved(long handle, int userId) { - // Do nothing. - } - }; - } -} diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index c77004d4eb17..da91a964565f 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1867,6 +1867,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java" }, + "-483957611": { + "message": "Resuming configuration dispatch for %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN", + "at": "com\/android\/server\/wm\/ActivityRecord.java" + }, "-481924678": { "message": "handleNotObscuredLocked w: %s, w.mHasSurface: %b, w.isOnScreen(): %b, w.isDisplayedLw(): %b, w.mAttrs.userActivityTimeout: %d", "level": "DEBUG", @@ -4021,6 +4027,12 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, + "1473051122": { + "message": "Pausing configuration dispatch for %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN", + "at": "com\/android\/server\/wm\/ActivityRecord.java" + }, "1494644409": { "message": " Rejecting as detached: %s", "level": "VERBOSE", diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index d023cea6d19d..1232baacdac7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -1020,7 +1020,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsController.finishInner: no valid PiP leash;" + "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d", - mPipTransaction.toString(), mPipTask.toString(), mPipTaskId); + mPipTransaction, mPipTask, mPipTaskId); } else { t.show(pipLeash); PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 5a74255df49a..e6faa6391cca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -93,7 +93,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL // On a smaller screen, don't require as much empty space on screen, as offscreen // drags will be restricted too much. - final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.taskId) + final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.displayId) .getResources().getConfiguration().smallestScreenWidthDp >= 600 ? R.dimen.freeform_required_visible_empty_space_in_header : R.dimen.small_screen_required_visible_empty_space_in_header; diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt index 47bff8de377e..0d1853534927 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt @@ -78,6 +78,14 @@ abstract class TvPipTestBase : PipTestBase(rotationToString(ROTATION_0), ROTATIO uiAutomation.dropShellPermissionIdentity() } + override fun onProcessStarted( + pid: Int, + processUid: Int, + packageUid: Int, + packageName: String, + processName: String + ) {} + override fun onForegroundActivitiesChanged(pid: Int, uid: Int, foreground: Boolean) {} override fun onForegroundServicesChanged(pid: Int, uid: Int, serviceTypes: Int) {} diff --git a/media/java/android/media/LoudnessCodecController.java b/media/java/android/media/LoudnessCodecController.java index b3e5c52b27b3..61c913166e1c 100644 --- a/media/java/android/media/LoudnessCodecController.java +++ b/media/java/android/media/LoudnessCodecController.java @@ -32,12 +32,13 @@ import androidx.annotation.Nullable; import java.util.HashMap; import java.util.HashSet; -import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; /** * Class for getting recommended loudness parameter updates for audio decoders as they are used @@ -320,11 +321,6 @@ public class LoudnessCodecController implements SafeCloseable { * Stops any loudness updates and frees up the resources. */ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API) - public void release() { - close(); - } - - /** @hide */ @Override public void close() { synchronized (mControllerLock) { @@ -339,9 +335,12 @@ public class LoudnessCodecController implements SafeCloseable { } /** @hide */ - /*package*/ Map<LoudnessCodecInfo, Set<MediaCodec>> getRegisteredMediaCodecs() { + /*package*/ void mediaCodecsConsume( + Consumer<Entry<LoudnessCodecInfo, Set<MediaCodec>>> consumer) { synchronized (mControllerLock) { - return mMediaCodecs; + for (Entry<LoudnessCodecInfo, Set<MediaCodec>> entry : mMediaCodecs.entrySet()) { + consumer.accept(entry); + } } } diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java index 46be54be55ec..fa08658a214f 100644 --- a/media/java/android/media/LoudnessCodecDispatcher.java +++ b/media/java/android/media/LoudnessCodecDispatcher.java @@ -32,7 +32,6 @@ import androidx.annotation.NonNull; import java.util.HashMap; import java.util.Iterator; -import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; @@ -81,16 +80,15 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { mConfiguratorListener.computeIfPresent(listener, (l, lcConfig) -> { // send the appropriate bundle for the user to update if (lcConfig.getSessionId() == sessionId) { - final Map<LoudnessCodecInfo, Set<MediaCodec>> mediaCodecsMap = - lcConfig.getRegisteredMediaCodecs(); - for (LoudnessCodecInfo codecInfo : mediaCodecsMap.keySet()) { + lcConfig.mediaCodecsConsume(mcEntry -> { + final LoudnessCodecInfo codecInfo = mcEntry.getKey(); final String infoKey = Integer.toString(codecInfo.hashCode()); Bundle bundle = null; if (params.containsKey(infoKey)) { bundle = new Bundle(params.getPersistableBundle(infoKey)); } - final Set<MediaCodec> mediaCodecs = mediaCodecsMap.get(codecInfo); + final Set<MediaCodec> mediaCodecs = mcEntry.getValue(); for (MediaCodec mediaCodec : mediaCodecs) { final String mediaCodecKey = Integer.toString( mediaCodec.hashCode()); @@ -121,7 +119,7 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { break; } } - } + }); } return lcConfig; }); diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp index 52060f1e6209..66fec1c528e7 100644 --- a/media/jni/soundpool/StreamManager.cpp +++ b/media/jni/soundpool/StreamManager.cpp @@ -35,10 +35,9 @@ static constexpr int32_t kMaxStreams = 32; // In R, we change this to true, as it is the correct way per SoundPool documentation. static constexpr bool kStealActiveStream_OldestFirst = true; -// kPlayOnCallingThread = true prior to R. // Changing to false means calls to play() are almost instantaneous instead of taking around // ~10ms to launch the AudioTrack. It is perhaps 100x faster. -static constexpr bool kPlayOnCallingThread = true; +static constexpr bool kPlayOnCallingThread = false; // Amount of time for a StreamManager thread to wait before closing. static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND; diff --git a/media/jni/soundpool/StreamManager.h b/media/jni/soundpool/StreamManager.h index adbab4b0f9d9..340b49bc6d6c 100644 --- a/media/jni/soundpool/StreamManager.h +++ b/media/jni/soundpool/StreamManager.h @@ -48,7 +48,7 @@ class JavaThread { public: JavaThread(std::function<void()> f, const char *name) : mF{std::move(f)} { - createThreadEtc(staticFunction, this, name); + createThreadEtc(staticFunction, this, name, ANDROID_PRIORITY_AUDIO); } JavaThread(JavaThread &&) = delete; // uses "this" ptr, not moveable. diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp index 25040a942061..e872a58c96cf 100644 --- a/media/jni/soundpool/android_media_SoundPool.cpp +++ b/media/jni/soundpool/android_media_SoundPool.cpp @@ -86,7 +86,7 @@ public: } // Retrieves the associated object, returns nullValue T if not available. - T get(JNIEnv *env, jobject thiz) { + T get(JNIEnv *env, jobject thiz) const { std::lock_guard lg(mLock); // NOLINTNEXTLINE(performance-no-int-to-ptr) auto ptr = reinterpret_cast<T*>(env->GetLongField(thiz, mFieldId)); @@ -167,8 +167,10 @@ private: // is possible by checking if the WeakGlobalRef is null equivalent. auto& getSoundPoolManager() { - static ObjectManager<std::shared_ptr<SoundPool>> soundPoolManager(fields.mNativeContext); - return soundPoolManager; + // never-delete singleton + static auto soundPoolManager = + new ObjectManager<std::shared_ptr<SoundPool>>(fields.mNativeContext); + return *soundPoolManager; } inline auto getSoundPool(JNIEnv *env, jobject thiz) { @@ -274,8 +276,9 @@ static_assert(std::is_same_v<JWeakValue*, jweak>); auto& getSoundPoolJavaRefManager() { // Note this can store shared_ptrs to either jweak and jobject, // as the underlying type is identical. - static ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>> concurrentHashMap; - return concurrentHashMap; + static auto concurrentHashMap = + new ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>>(); + return *concurrentHashMap; } // make_shared_globalref_from_localref() creates a sharable Java global diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java index 4f6ede508f7a..46256ba7cccb 100644 --- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java +++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java @@ -126,7 +126,7 @@ public class LoudnessCodecControllerTest { try { mLcc.addMediaCodec(mediaCodec); - mLcc.release(); // stops updats + mLcc.close(); // stops updates verify(mAudioService).stopLoudnessCodecUpdates(eq(mSessionId)); } finally { diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp index 10c570b30d7a..8ea46329af58 100644 --- a/native/graphics/jni/Android.bp +++ b/native/graphics/jni/Android.bp @@ -72,6 +72,9 @@ cc_library_shared { ], }, }, + stubs: { + symbol_file: "libjnigraphics.map.txt", + }, } // The headers module is in frameworks/native/Android.bp. @@ -93,15 +96,18 @@ cc_defaults { ], static_libs: ["libarect"], fuzz_config: { - cc: ["dichenzhang@google.com","scroggo@google.com"], + cc: [ + "dichenzhang@google.com", + "scroggo@google.com", + ], asan_options: [ "detect_odr_violation=1", ], hwasan_options: [ - // Image decoders may attempt to allocate a large amount of memory - // (especially if the encoded image is large). This doesn't - // necessarily mean there is a bug. Set allocator_may_return_null=1 - // for hwasan so the fuzzer can continue running. + // Image decoders may attempt to allocate a large amount of memory + // (especially if the encoded image is large). This doesn't + // necessarily mean there is a bug. Set allocator_may_return_null=1 + // for hwasan so the fuzzer can continue running. "allocator_may_return_null = 1", ], }, diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml index 5becc86927d2..f13402c7206d 100644 --- a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml +++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml @@ -23,7 +23,7 @@ android:shape="rectangle" android:top="1dp"> <shape> - <corners android:radius="16dp" /> + <corners android:radius="4dp" /> <solid android:color="@color/dropdown_container" /> </shape> </item> diff --git a/packages/CredentialManager/res/drawable/more_options_list_item.xml b/packages/CredentialManager/res/drawable/more_options_list_item.xml new file mode 100644 index 000000000000..d7b509ee48fd --- /dev/null +++ b/packages/CredentialManager/res/drawable/more_options_list_item.xml @@ -0,0 +1,31 @@ +<?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. + --> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi" + android:color="@android:color/transparent"> + <item + android:bottom="1dp" + android:shape="rectangle" + android:top="1dp"> + <shape> + <corners android:bottomLeftRadius="4dp" + android:bottomRightRadius="4dp"/> + <solid android:color="@color/sign_in_options_container" /> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml new file mode 100644 index 000000000000..929756cdf9cc --- /dev/null +++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml @@ -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. + --> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@android:id/content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin" + android:elevation="3dp"> + + <ImageView + android:id="@android:id/icon1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_alignParentStart="true" + android:contentDescription="@string/provider_icon_content_description" + android:background="@null"/> + <TextView + android:id="@android:id/text1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_toEndOf="@android:id/icon1" + android:minWidth="@dimen/autofill_dropdown_textview_min_width" + android:maxWidth="@dimen/autofill_dropdown_textview_max_width" + style="@style/autofill.TextTitle"/> + +</RelativeLayout> diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml index cb6c6b473244..1fe5e0ed41f9 100644 --- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml +++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml @@ -17,22 +17,25 @@ android:id="@android:id/content" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:maxWidth="@dimen/autofill_dropdown_layout_width" + android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin" android:elevation="3dp"> <ImageView android:id="@android:id/icon1" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:contentDescription="@string/provider_icon_content_description" android:layout_centerVertical="true" android:layout_alignParentStart="true" android:background="@null"/> <TextView android:id="@android:id/text1" - android:layout_width="@dimen/autofill_dropdown_text_width" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_toEndOf="@android:id/icon1" + android:minWidth="@dimen/autofill_dropdown_textview_min_width" + android:maxWidth="@dimen/autofill_dropdown_textview_max_width" style="@style/autofill.TextTitle"/> <TextView android:id="@android:id/text2" @@ -40,6 +43,8 @@ android:layout_height="wrap_content" android:layout_below="@android:id/text1" android:layout_toEndOf="@android:id/icon1" + android:minWidth="@dimen/autofill_dropdown_textview_min_width" + android:maxWidth="@dimen/autofill_dropdown_textview_max_width" style="@style/autofill.TextSubtitle"/> </RelativeLayout> diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml index dcb7ef9c3ed8..7cb1d01972b7 100644 --- a/packages/CredentialManager/res/values/colors.xml +++ b/packages/CredentialManager/res/values/colors.xml @@ -20,4 +20,6 @@ <color name="text_primary">#1A1B20</color> <color name="text_secondary">#44474F</color> <color name="dropdown_container">#F3F3FA</color> + <color name="sign_in_options_container">#DADADA</color> + <color name="sign_in_options_icon_color">#1B1B1B</color> </resources>
\ No newline at end of file diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml index 2a4719d027e2..3a8c78f6d854 100644 --- a/packages/CredentialManager/res/values/dimens.xml +++ b/packages/CredentialManager/res/values/dimens.xml @@ -18,11 +18,13 @@ <resources> <dimen name="autofill_view_top_padding">12dp</dimen> - <dimen name="autofill_view_right_padding">24dp</dimen> + <dimen name="autofill_view_right_padding">12dp</dimen> <dimen name="autofill_view_bottom_padding">12dp</dimen> <dimen name="autofill_view_left_padding">16dp</dimen> <dimen name="autofill_view_icon_to_text_padding">10dp</dimen> <dimen name="autofill_icon_size">24dp</dimen> - <dimen name="autofill_dropdown_layout_width">296dp</dimen> - <dimen name="autofill_dropdown_text_width">240dp</dimen> + <dimen name="autofill_dropdown_textview_min_width">112dp</dimen> + <dimen name="autofill_dropdown_textview_max_width">230dp</dimen> + <dimen name="dropdown_layout_horizontal_margin">24dp</dimen> + <integer name="autofill_max_visible_datasets">3</integer> </resources>
\ No newline at end of file diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 605e77bef34e..f98164b8788c 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -168,4 +168,9 @@ <string name="get_dialog_option_headline_use_a_different_device">Use a different device</string> <!-- Text shown on a snackbar when the app cancelled the UI. [CHAR LIMIT=120] --> <string name="request_cancelled_by">Request cancelled by <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string> + + <!-- Strings for dropdown presentation. --> + <!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] --> + <string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string> + <string name="provider_icon_content_description">Credential provider icon</string> </resources>
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 03ac605222ba..985f3228f402 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -30,6 +30,7 @@ import android.credentials.ui.GetCredentialProviderData import android.os.Bundle import android.os.CancellationSignal import android.os.OutcomeReceiver +import android.provider.Settings import android.credentials.Credential import android.service.autofill.AutofillService import android.service.autofill.Dataset @@ -48,7 +49,9 @@ import android.view.autofill.IAutoFillManagerClient import android.view.autofill.AutofillId import android.widget.inline.InlinePresentationSpec import android.credentials.CredentialManager +import android.widget.RemoteViews import androidx.autofill.inline.v1.InlineSuggestionUi +import androidx.core.content.ContextCompat import androidx.credentials.provider.CustomCredentialEntry import androidx.credentials.provider.PasswordCredentialEntry import androidx.credentials.provider.PublicKeyCredentialEntry @@ -115,7 +118,7 @@ class CredentialAutofillService : AutofillService() { } val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId, - requestId) + requestId) if (getCredRequest == null) { Log.i(TAG, "No credential manager request found") callback.onFailure("No credential manager request found") @@ -307,10 +310,14 @@ class CredentialAutofillService : AutofillService() { val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0 val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0 - var maxItemCount = totalEntryCount - if (inlineMaxSuggestedCount > 0) { - maxItemCount = maxItemCount.coerceAtMost(inlineMaxSuggestedCount) - } + val maxDropdownDisplayLimit = this.resources.getInteger( + com.android.credentialmanager.R.integer.autofill_max_visible_datasets) + var maxInlineItemCount = totalEntryCount + maxInlineItemCount = maxInlineItemCount.coerceAtMost(inlineMaxSuggestedCount) + val lastDropdownDatasetIndex = Settings.Global.getInt(this.contentResolver, + Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS, + (maxDropdownDisplayLimit - 1).coerceAtMost(totalEntryCount - 1)) + var i = 0 var datasetAdded = false @@ -333,13 +340,8 @@ class CredentialAutofillService : AutofillService() { Log.e(TAG, "PendingIntent was missing from the entry.") return@usernameLoop } - if (inlinePresentationSpecs == null) { - Log.i(TAG, "Inline presentation spec is null, " + - "building dropdown presentation only") - } - if (i >= maxItemCount) { - Log.e(TAG, "Skipping because reached the max item count.") - return@usernameLoop + if (i >= maxInlineItemCount && i >= lastDropdownDatasetIndex) { + return@usernameLoop; } val icon: Icon = if (primaryEntry.icon == null) { // The empty entry icon has non-null icon reference but null drawable reference. @@ -351,38 +353,26 @@ class CredentialAutofillService : AutofillService() { } // Create inline presentation var inlinePresentation: InlinePresentation? = null - var spec: InlinePresentationSpec? - if (inlinePresentationSpecs != null) { - if (i < inlinePresentationSpecsCount) { - spec = inlinePresentationSpecs[i] + if (inlinePresentationSpecs != null && i < maxInlineItemCount) { + val spec: InlinePresentationSpec? = if (i < inlinePresentationSpecsCount) { + inlinePresentationSpecs[i] } else { - spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1] + inlinePresentationSpecs[inlinePresentationSpecsCount - 1] } - val displayName: String = if (primaryEntry.credentialType == - CredentialType.PASSKEY && primaryEntry.displayName != null) { - primaryEntry.displayName!! - } else { - primaryEntry.userName - } - val sliceBuilder = InlineSuggestionUi - .newContentBuilder(pendingIntent) - .setTitle(displayName) - sliceBuilder.setStartIcon(icon) - if (primaryEntry.credentialType == - CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[displayName] - == true) { - sliceBuilder.setSubtitle(primaryEntry.userName) - } - inlinePresentation = InlinePresentation( - sliceBuilder.build().slice, spec, /* pinned= */ false) + inlinePresentation = createInlinePresentation(primaryEntry, pendingIntent, icon, + spec!!, duplicateDisplayNamesForPasskeys) + } + var dropdownPresentation: RemoteViews? = null + if (i < lastDropdownDatasetIndex) { + dropdownPresentation = RemoteViewsFactory + .createDropdownPresentation(this, icon, primaryEntry) } - val dropdownPresentation = RemoteViewsFactory.createDropdownPresentation( - this, icon, primaryEntry) - i++ val dataSetBuilder = Dataset.Builder() val presentationBuilder = Presentations.Builder() - .setMenuPresentation(dropdownPresentation) + if (dropdownPresentation != null) { + presentationBuilder.setMenuPresentation(dropdownPresentation) + } if (inlinePresentation != null) { presentationBuilder.setInlinePresentation(inlinePresentation) } @@ -398,6 +388,12 @@ class CredentialAutofillService : AutofillService() { .setAuthenticationExtras(fillInIntent.extras) .build()) datasetAdded = true + i++ + + if (i == lastDropdownDatasetIndex && bottomSheetPendingIntent != null) { + addDropdownMoreOptionsPresentation(bottomSheetPendingIntent, autofillId, + fillResponseBuilder) + } } val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs, inlinePresentationSpecsCount) @@ -408,6 +404,49 @@ class CredentialAutofillService : AutofillService() { return datasetAdded } + private fun createInlinePresentation(primaryEntry: CredentialEntryInfo, + pendingIntent: PendingIntent, + icon: Icon, + spec: InlinePresentationSpec, + duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>): + InlinePresentation { + val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY + && primaryEntry.displayName != null) { + primaryEntry.displayName!! + } else { + primaryEntry.userName + } + val sliceBuilder = InlineSuggestionUi + .newContentBuilder(pendingIntent) + .setTitle(displayName) + sliceBuilder.setStartIcon(icon) + if (primaryEntry.credentialType == + CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) { + sliceBuilder.setSubtitle(primaryEntry.userName) + } + return InlinePresentation( + sliceBuilder.build().slice, spec, /* pinned= */ false) + } + + private fun addDropdownMoreOptionsPresentation( + bottomSheetPendingIntent: PendingIntent, + autofillId: AutofillId, + fillResponseBuilder: FillResponse.Builder) { + val presentationBuilder = Presentations.Builder() + .setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this)) + + fillResponseBuilder.addDataset( + Dataset.Builder() + .setField( + autofillId, + Field.Builder().setPresentations( + presentationBuilder.build()) + .build()) + .setAuthentication(bottomSheetPendingIntent.intentSender) + .build() + ) + } + private fun getLastInlinePresentationSpec( inlinePresentationSpecs: List<InlinePresentationSpec>?, inlinePresentationSpecsCount: Int @@ -534,9 +573,9 @@ class CredentialAutofillService : AutofillService() { } private fun getCredManRequest( - structure: AssistStructure, - sessionId: Int, - requestId: Int + structure: AssistStructure, + sessionId: Int, + requestId: Int ): GetCredentialRequest? { val credentialOptions: MutableList<CredentialOption> = mutableListOf() traverseStructure(structure, credentialOptions) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt index e039dead043e..68f1c861d51b 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt @@ -44,7 +44,7 @@ class RemoteViewsFactory { if (credentialEntryInfo.credentialType == CredentialType.UNKNOWN) { return remoteViews } - setRemoteViewsPaddings(remoteViews, context) + setRemoteViewsPaddings(remoteViews, context, /* primaryTextBottomPadding=*/0) if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) { val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName remoteViews.setTextViewText(android.R.id.text1, displayName) @@ -81,8 +81,46 @@ class RemoteViewsFactory { return remoteViews } + fun createMoreSignInOptionsPresentation(context: Context): RemoteViews { + var layoutId: Int = com.android.credentialmanager.R.layout + .credman_dropdown_bottom_sheet + val remoteViews = RemoteViews(context.packageName, layoutId) + setRemoteViewsPaddings(remoteViews, context) + remoteViews.setTextViewText(android.R.id.text1, ContextCompat.getString(context, + com.android.credentialmanager + .R.string.dropdown_presentation_more_sign_in_options_text)) + + val textColorPrimary = ContextCompat.getColor(context, + com.android.credentialmanager.R.color.text_primary) + remoteViews.setTextColor(android.R.id.text1, textColorPrimary) + val icon = Icon.createWithResource(context, com + .android.credentialmanager.R.drawable.more_horiz_24px) + icon.setTint(ContextCompat.getColor(context, + com.android.credentialmanager.R.color.sign_in_options_icon_color)) + remoteViews.setImageViewIcon(android.R.id.icon1, icon) + remoteViews.setBoolean( + android.R.id.icon1, setAdjustViewBoundsMethodName, true); + remoteViews.setInt( + android.R.id.icon1, + setMaxHeightMethodName, + context.resources.getDimensionPixelSize( + com.android.credentialmanager.R.dimen.autofill_icon_size)); + val drawableId = + com.android.credentialmanager.R.drawable.more_options_list_item + remoteViews.setInt( + android.R.id.content, setBackgroundResourceMethodName, drawableId); + return remoteViews + } + private fun setRemoteViewsPaddings( remoteViews: RemoteViews, context: Context) { + val bottomPadding = context.resources.getDimensionPixelSize( + com.android.credentialmanager.R.dimen.autofill_view_bottom_padding) + setRemoteViewsPaddings(remoteViews, context, bottomPadding) + } + + private fun setRemoteViewsPaddings( + remoteViews: RemoteViews, context: Context, primaryTextBottomPadding: Int) { val leftPadding = context.resources.getDimensionPixelSize( com.android.credentialmanager.R.dimen.autofill_view_left_padding) val iconToTextPadding = context.resources.getDimensionPixelSize( @@ -104,7 +142,7 @@ class RemoteViewsFactory { iconToTextPadding, /* top=*/topPadding, /* right=*/rightPadding, - /* bottom=*/0) + primaryTextBottomPadding) remoteViews.setViewPadding( android.R.id.text2, iconToTextPadding, diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java index cd5f59731e7f..b015b2bce60a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java @@ -234,6 +234,6 @@ public class EditUserInfoController { EditUserPhotoController createEditUserPhotoController(Activity activity, ActivityStarter activityStarter, ImageView userPhotoView) { return new EditUserPhotoController(activity, activityStarter, userPhotoView, - mSavedPhoto, mSavedDrawable, mFileAuthority); + mSavedPhoto, mSavedDrawable, mFileAuthority, false); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java index e83b9bc25799..b2de5a948836 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java @@ -62,6 +62,7 @@ public class EditUserPhotoController { private static final String AVATAR_PICKER_ACTION = "com.android.avatarpicker" + ".FULL_SCREEN_ACTIVITY"; + static final String EXTRA_IS_USER_NEW = "is_user_new"; private final Activity mActivity; private final ActivityStarter mActivityStarter; @@ -72,9 +73,13 @@ public class EditUserPhotoController { private Bitmap mNewUserPhotoBitmap; private Drawable mNewUserPhotoDrawable; private String mCachedDrawablePath; - public EditUserPhotoController(Activity activity, ActivityStarter activityStarter, ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority) { + this(activity, activityStarter, view, savedBitmap, savedDrawable, fileAuthority, true); + } + public EditUserPhotoController(Activity activity, ActivityStarter activityStarter, + ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority, + boolean isUserNew) { mActivity = activity; mActivityStarter = activityStarter; mFileAuthority = fileAuthority; @@ -82,7 +87,7 @@ public class EditUserPhotoController { mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR); mImagesDir.mkdir(); mImageView = view; - mImageView.setOnClickListener(v -> showAvatarPicker()); + mImageView.setOnClickListener(v -> showAvatarPicker(isUserNew)); mNewUserPhotoBitmap = savedBitmap; mNewUserPhotoDrawable = savedDrawable; @@ -117,11 +122,12 @@ public class EditUserPhotoController { return mNewUserPhotoDrawable; } - private void showAvatarPicker() { + private void showAvatarPicker(boolean isUserNew) { Intent intent; if (Flags.avatarSync()) { intent = new Intent(AVATAR_PICKER_ACTION); intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.putExtra(EXTRA_IS_USER_NEW, isUserNew); } else { intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class); } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 2c35c777ab12..7eca04a5f85b 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -364,3 +364,10 @@ flag { description: "Enables styled focus states on pin input field if keyboard is connected" bug: "316106516" } + +flag { + name: "enable_notif_linearlayout_optimized" + namespace: "systemui" + description: "Enables notification specific LinearLayout optimization" + bug: "316110233" +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 409f15bb4bb8..761e74e52237 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -97,11 +97,13 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Popup import androidx.core.view.setPadding +import com.android.compose.modifiers.thenIf import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth import com.android.systemui.communal.ui.compose.extensions.allowGestures +import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel @@ -132,6 +134,8 @@ fun CommunalHub( val removeButtonEnabled by remember { derivedStateOf { selectedIndex.value != null || reorderingWidgets } } + val (isButtonToEditWidgetsShowing, setIsButtonToEditWidgetsShowing) = + remember { mutableStateOf(false) } val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize) val contentOffset = beforeContentPadding(contentPadding).toOffset() @@ -158,6 +162,11 @@ fun CommunalHub( } viewModel.setSelectedIndex(newIndex) } + } + .thenIf(!viewModel.isEditMode) { + Modifier.pointerInput(Unit) { + detectLongPressGesture { offset -> setIsButtonToEditWidgetsShowing(true) } + } }, ) { CommunalHubLazyGrid( @@ -207,6 +216,16 @@ fun CommunalHub( PopupOnDismissCtaTile(viewModel::onHidePopupAfterDismissCta) } + if (isButtonToEditWidgetsShowing) { + ButtonToEditWidgets( + onClick = { + setIsButtonToEditWidgetsShowing(false) + viewModel.onOpenWidgetEditor() + }, + onHide = { setIsButtonToEditWidgetsShowing(false) }, + ) + } + // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving // touches, so that the SceneTransitionLayout can intercept the touches and allow an edge // swipe back to the blank scene. @@ -414,6 +433,34 @@ private fun Toolbar( } @Composable +private fun ButtonToEditWidgets( + onClick: () -> Unit, + onHide: () -> Unit, +) { + Popup(alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onHide) { + val colors = LocalAndroidColorScheme.current + Button( + modifier = + Modifier.height(56.dp).background(colors.secondary, RoundedCornerShape(50.dp)), + onClick = onClick, + ) { + Icon( + imageVector = Icons.Outlined.Widgets, + contentDescription = stringResource(R.string.button_to_configure_widgets_text), + tint = colors.onSecondary, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.size(8.dp)) + Text( + text = stringResource(R.string.button_to_configure_widgets_text), + style = MaterialTheme.typography.titleSmall, + color = colors.onSecondary, + ) + } + } +} + +@Composable private fun PopupOnDismissCtaTile(onHidePopupAfterDismissCta: () -> Unit) { Popup( alignment = Alignment.TopCenter, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt index 14074944259b..bc1e429e57cf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt @@ -20,9 +20,13 @@ import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.waitForUpOrCancellation import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerInputScope +import androidx.compose.ui.util.fastAny +import androidx.compose.ui.util.fastForEach import kotlinx.coroutines.coroutineScope /** @@ -44,6 +48,41 @@ suspend fun PointerInputScope.observeTapsWithoutConsuming( } } +/** + * Detect long press gesture and calls onLongPress when detected. The callback parameter receives an + * Offset representing the position relative to the containing element. + */ +suspend fun PointerInputScope.detectLongPressGesture( + pass: PointerEventPass = PointerEventPass.Initial, + onLongPress: ((Offset) -> Unit), +) = coroutineScope { + awaitEachGesture { + val down = awaitFirstDown(pass = pass) + val longPressTimeout = viewConfiguration.longPressTimeoutMillis + // wait for first tap up or long press + try { + withTimeout(longPressTimeout) { waitForUpOrCancellation(pass = pass) } + } catch (_: PointerEventTimeoutCancellationException) { + // withTimeout throws exception if timeout has passed before block completes + onLongPress.invoke(down.position) + consumeUntilUp(pass) + } + } +} + +/** + * Consumes all pointer events until nothing is pressed and then returns. This method assumes that + * something is currently pressed. + */ +private suspend fun AwaitPointerEventScope.consumeUntilUp( + pass: PointerEventPass = PointerEventPass.Initial +) { + do { + val event = awaitPointerEvent(pass = pass) + event.changes.fastForEach { it.consume() } + } while (event.changes.fastAny { it.pressed }) +} + /** Consume all gestures on the initial pass so that child elements do not receive them. */ suspend fun PointerInputScope.consumeAllGestures() = coroutineScope { awaitEachGesture { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt index 56d6879e614e..bf02d8abf73c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt @@ -173,7 +173,8 @@ constructor( val belowLockIconPlaceable = belowLockIconMeasurable.measure( noMinConstraints.copy( - maxHeight = constraints.maxHeight - lockIconBounds.bottom + maxHeight = + (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0) ) ) val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt index fdf11668ae76..616a7b4752a0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt @@ -16,19 +16,42 @@ package com.android.systemui.keyguard.ui.composable.blueprint -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.material3.Text +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntRect +import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope +import com.android.compose.modifiers.padding +import com.android.systemui.Flags +import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.LockscreenLongPress +import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection +import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection +import com.android.systemui.keyguard.ui.composable.section.ClockSection +import com.android.systemui.keyguard.ui.composable.section.LockSection +import com.android.systemui.keyguard.ui.composable.section.NotificationSection +import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection +import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection +import com.android.systemui.keyguard.ui.composable.section.StatusBarSection import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel +import com.android.systemui.res.R +import com.android.systemui.shade.LargeScreenHeaderHelper import dagger.Binds import dagger.Module import dagger.multibindings.IntoSet +import java.util.Optional import javax.inject.Inject /** @@ -39,22 +62,174 @@ class SplitShadeBlueprint @Inject constructor( private val viewModel: LockscreenContentViewModel, + private val statusBarSection: StatusBarSection, + private val clockSection: ClockSection, + private val smartSpaceSection: SmartSpaceSection, + private val notificationSection: NotificationSection, + private val lockSection: LockSection, + private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>, + private val bottomAreaSection: BottomAreaSection, + private val settingsMenuSection: SettingsMenuSection, + private val clockInteractor: KeyguardClockInteractor, + private val largeScreenHeaderHelper: LargeScreenHeaderHelper, ) : LockscreenSceneBlueprint { override val id: String = "split-shade" @Composable override fun SceneScope.Content(modifier: Modifier) { + val isUdfpsVisible = viewModel.isUdfpsVisible + val burnIn = rememberBurnIn(clockInteractor) + val resources = LocalContext.current.resources + LockscreenLongPress( viewModel = viewModel.longPress, modifier = modifier, - ) { _ -> - Box(modifier.background(Color.Black)) { - Text( - text = "TODO(b/316211368): split shade blueprint", - color = Color.White, - modifier = Modifier.align(Alignment.Center), - ) + ) { onSettingsMenuPlaced -> + Layout( + content = { + // Constrained to above the lock icon. + Column( + modifier = Modifier.fillMaxSize(), + ) { + with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) } + Row( + modifier = Modifier.fillMaxSize(), + ) { + Column( + modifier = Modifier.fillMaxHeight().weight(weight = 1f), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + with(smartSpaceSection) { + SmartSpace( + burnInParams = burnIn.parameters, + onTopChanged = burnIn.onSmartspaceTopChanged, + modifier = + Modifier.fillMaxWidth() + .padding( + top = { + viewModel.getSmartSpacePaddingTop(resources) + } + ), + ) + } + + Spacer(modifier = Modifier.weight(weight = 1f)) + with(clockSection) { LargeClock() } + Spacer(modifier = Modifier.weight(weight = 1f)) + } + with(notificationSection) { + val splitShadeTopMargin: Dp = + if (Flags.centralizedStatusBarDimensRefactor()) { + largeScreenHeaderHelper.getLargeScreenHeaderHeight().dp + } else { + dimensionResource( + id = R.dimen.large_screen_shade_header_height + ) + } + Notifications( + modifier = + Modifier.fillMaxHeight() + .weight(weight = 1f) + .padding(top = splitShadeTopMargin) + ) + } + } + + if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) { + with(ambientIndicationSectionOptional.get()) { + AmbientIndication(modifier = Modifier.fillMaxWidth()) + } + } + } + + with(lockSection) { LockIcon() } + + // Aligned to bottom and constrained to below the lock icon. + Column(modifier = Modifier.fillMaxWidth()) { + if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) { + with(ambientIndicationSectionOptional.get()) { + AmbientIndication(modifier = Modifier.fillMaxWidth()) + } + } + + with(bottomAreaSection) { + IndicationArea(modifier = Modifier.fillMaxWidth()) + } + } + + // Aligned to bottom and NOT constrained by the lock icon. + with(bottomAreaSection) { + Shortcut(isStart = true, applyPadding = true) + Shortcut(isStart = false, applyPadding = true) + } + with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) } + }, + modifier = Modifier.fillMaxSize(), + ) { measurables, constraints -> + check(measurables.size == 6) + val aboveLockIconMeasurable = measurables[0] + val lockIconMeasurable = measurables[1] + val belowLockIconMeasurable = measurables[2] + val startShortcutMeasurable = measurables[3] + val endShortcutMeasurable = measurables[4] + val settingsMenuMeasurable = measurables[5] + + val noMinConstraints = + constraints.copy( + minWidth = 0, + minHeight = 0, + ) + val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints) + val lockIconBounds = + IntRect( + left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left], + top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top], + right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right], + bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom], + ) + + val aboveLockIconPlaceable = + aboveLockIconMeasurable.measure( + noMinConstraints.copy(maxHeight = lockIconBounds.top) + ) + val belowLockIconPlaceable = + belowLockIconMeasurable.measure( + noMinConstraints.copy( + maxHeight = + (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0) + ) + ) + val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints) + val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints) + val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints) + + layout(constraints.maxWidth, constraints.maxHeight) { + aboveLockIconPlaceable.place( + x = 0, + y = 0, + ) + lockIconPlaceable.place( + x = lockIconBounds.left, + y = lockIconBounds.top, + ) + belowLockIconPlaceable.place( + x = 0, + y = constraints.maxHeight - belowLockIconPlaceable.height, + ) + startShortcutPleaceable.place( + x = 0, + y = constraints.maxHeight - startShortcutPleaceable.height, + ) + endShortcutPleaceable.place( + x = constraints.maxWidth - endShortcutPleaceable.width, + y = constraints.maxHeight - endShortcutPleaceable.height, + ) + settingsMenuPlaceable.place( + x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2, + y = constraints.maxHeight - settingsMenuPlaceable.height, + ) + } } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt index f40b871e923c..8f218792ee32 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt @@ -16,7 +16,8 @@ package com.android.systemui.keyguard.ui.composable.section -import androidx.compose.foundation.layout.fillMaxWidth +import android.view.ViewGroup +import android.widget.FrameLayout import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -75,7 +76,13 @@ constructor( ) { content { AndroidView( - factory = { checkNotNull(currentClock).smallClock.view }, + factory = { context -> + FrameLayout(context).apply { + val newClockView = checkNotNull(currentClock).smallClock.view + (newClockView.parent as? ViewGroup)?.removeView(newClockView) + addView(newClockView) + } + }, modifier = Modifier.padding( horizontal = @@ -83,6 +90,12 @@ constructor( ) .padding(top = { viewModel.getSmallClockTopMargin(view.context) }) .onTopPlacementChanged(onTopChanged), + update = { + val newClockView = checkNotNull(currentClock).smallClock.view + it.removeAllViews() + (newClockView.parent as? ViewGroup)?.removeView(newClockView) + it.addView(newClockView) + }, ) } } @@ -116,8 +129,19 @@ constructor( ) { content { AndroidView( - factory = { checkNotNull(currentClock).largeClock.view }, - modifier = Modifier.fillMaxWidth() + factory = { context -> + FrameLayout(context).apply { + val newClockView = checkNotNull(currentClock).largeClock.view + (newClockView.parent as? ViewGroup)?.removeView(newClockView) + addView(newClockView) + } + }, + update = { + val newClockView = checkNotNull(currentClock).largeClock.view + it.removeAllViews() + (newClockView.parent as? ViewGroup)?.removeView(newClockView) + it.addView(newClockView) + }, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt new file mode 100644 index 000000000000..9287edf4ee51 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt @@ -0,0 +1,82 @@ +/* + * 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.accessibility.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.util.settings.FakeSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() { + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + private val secureSettings = FakeSettings() + + private val userA11yQsShortcutsRepositoryFactory = + object : UserA11yQsShortcutsRepository.Factory { + override fun create(userId: Int): UserA11yQsShortcutsRepository { + return UserA11yQsShortcutsRepository( + userId, + secureSettings, + testScope.backgroundScope, + testDispatcher, + ) + } + } + + private val underTest = + AccessibilityQsShortcutsRepositoryImpl(userA11yQsShortcutsRepositoryFactory) + + @Test + fun a11yQsShortcutTargetsForCorrectUsers() = + testScope.runTest { + val user0 = 0 + val targetsForUser0 = setOf("a", "b", "c") + val user1 = 1 + val targetsForUser1 = setOf("A") + val targetsFromUser0 by collectLastValue(underTest.a11yQsShortcutTargets(user0)) + val targetsFromUser1 by collectLastValue(underTest.a11yQsShortcutTargets(user1)) + + storeA11yQsShortcutTargetsForUser(targetsForUser0, user0) + storeA11yQsShortcutTargetsForUser(targetsForUser1, user1) + + assertThat(targetsFromUser0).isEqualTo(targetsForUser0) + assertThat(targetsFromUser1).isEqualTo(targetsForUser1) + } + + private fun storeA11yQsShortcutTargetsForUser(a11yQsTargets: Set<String>, forUser: Int) { + secureSettings.putStringForUser( + SETTING_NAME, + a11yQsTargets.joinToString(separator = ":"), + forUser + ) + } + + companion object { + private const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt new file mode 100644 index 000000000000..ce22e288e292 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.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.systemui.accessibility.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.util.settings.FakeSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class UserA11yQsShortcutsRepositoryTest : SysuiTestCase() { + private val secureSettings = FakeSettings() + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private val underTest = + UserA11yQsShortcutsRepository( + USER_ID, + secureSettings, + testScope.backgroundScope, + testDispatcher + ) + + @Test + fun targetsMatchesSetting() = + testScope.runTest { + val observedTargets by collectLastValue(underTest.targets) + val a11yQsTargets = setOf("a", "b", "c") + secureSettings.putStringForUser( + SETTING_NAME, + a11yQsTargets.joinToString(SEPARATOR), + USER_ID + ) + + assertThat(observedTargets).isEqualTo(a11yQsTargets) + } + + companion object { + private const val USER_ID = 0 + private const val SEPARATOR = ":" + private const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt index 81d5344ed264..bd9ca3035a07 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt @@ -16,24 +16,28 @@ package com.android.systemui.communal.data.repository +import android.content.pm.UserInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.scene.data.repository.SceneContainerRepository +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository -import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -44,19 +48,23 @@ import org.junit.runner.RunWith class CommunalRepositoryImplTest : SysuiTestCase() { private lateinit var underTest: CommunalRepositoryImpl - private val testDispatcher = StandardTestDispatcher() - private val testScope = TestScope(testDispatcher) + private lateinit var secureSettings: FakeSettings + private lateinit var userRepository: FakeUserRepository - private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic - private lateinit var sceneContainerRepository: SceneContainerRepository + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneContainerRepository = kosmos.sceneContainerRepository @Before fun setUp() { - val kosmos = testKosmos() - sceneContainerRepository = kosmos.sceneContainerRepository - featureFlagsClassic = FakeFeatureFlagsClassic() + secureSettings = FakeSettings() + userRepository = kosmos.fakeUserRepository - featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) + val listOfUserInfo = listOf(MAIN_USER_INFO) + userRepository.setUserInfos(listOfUserInfo) + + kosmos.fakeFeatureFlagsClassic.apply { set(Flags.COMMUNAL_SERVICE_ENABLED, true) } + mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) underTest = createRepositoryImpl(false) } @@ -64,9 +72,13 @@ class CommunalRepositoryImplTest : SysuiTestCase() { private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl { return CommunalRepositoryImpl( testScope.backgroundScope, - featureFlagsClassic, - FakeSceneContainerFlags(enabled = sceneContainerEnabled), + testScope.backgroundScope, + kosmos.testDispatcher, + kosmos.fakeFeatureFlagsClassic, + kosmos.fakeSceneContainerFlags.apply { enabled = sceneContainerEnabled }, sceneContainerRepository, + kosmos.fakeUserRepository, + secureSettings, ) } @@ -147,4 +159,29 @@ class CommunalRepositoryImplTest : SysuiTestCase() { assertThat(transitionState) .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT)) } + + @Test + fun communalEnabledState_false_whenGlanceableHubSettingFalse() = + testScope.runTest { + userRepository.setSelectedUserInfo(MAIN_USER_INFO) + secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, MAIN_USER_INFO.id) + + val communalEnabled by collectLastValue(underTest.communalEnabledState) + assertThat(communalEnabled).isFalse() + } + + @Test + fun communalEnabledState_true_whenGlanceableHubSettingTrue() = + testScope.runTest { + userRepository.setSelectedUserInfo(MAIN_USER_INFO) + secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, MAIN_USER_INFO.id) + + val communalEnabled by collectLastValue(underTest.communalEnabledState) + assertThat(communalEnabled).isTrue() + } + + companion object { + private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled" + private val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt new file mode 100644 index 000000000000..311122d7f8d5 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.domain.autoaddable + +import android.content.ComponentName +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.view.accessibility.Flags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.accessibility.AccessibilityShortcutController +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.ColorCorrectionTile +import com.android.systemui.qs.tiles.ColorInversionTile +import com.android.systemui.qs.tiles.OneHandedModeTile +import com.android.systemui.qs.tiles.ReduceBrightColorsTile +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class A11yShortcutAutoAddableListTest : SysuiTestCase() { + + private val factory = + object : A11yShortcutAutoAddable.Factory { + override fun create( + spec: TileSpec, + componentName: ComponentName + ): A11yShortcutAutoAddable { + return A11yShortcutAutoAddable(mock(), mock(), spec, componentName) + } + } + + @Test + @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOff_emptyResult() { + val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory) + + assertThat(autoAddables).isEmpty() + } + + @Test + @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOn_correctAutoAddables() { + val expected = + setOf( + factory.create( + TileSpec.create(ColorCorrectionTile.TILE_SPEC), + AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME + ), + factory.create( + TileSpec.create(ColorInversionTile.TILE_SPEC), + AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME + ), + factory.create( + TileSpec.create(OneHandedModeTile.TILE_SPEC), + AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME + ), + factory.create( + TileSpec.create(ReduceBrightColorsTile.TILE_SPEC), + AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME + ), + ) + + val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory) + + assertThat(autoAddables).isNotEmpty() + assertThat(autoAddables).containsExactlyElementsIn(expected) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt new file mode 100644 index 000000000000..3b33a43d9341 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.domain.autoaddable + +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.accessibility.data.repository.FakeAccessibilityQsShortcutsRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal +import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class A11yShortcutAutoAddableTest : SysuiTestCase() { + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private val a11yQsShortcutsRepository = FakeAccessibilityQsShortcutsRepository() + private val underTest = + A11yShortcutAutoAddable(a11yQsShortcutsRepository, testDispatcher, SPEC, TARGET_COMPONENT) + + @Test + fun settingNotSet_noSignal() = + testScope.runTest { + val signal by collectLastValue(underTest.autoAddSignal(USER_ID)) + + assertThat(signal).isNull() // null means no emitted value + } + + @Test + fun settingSetWithTarget_addSignal() = + testScope.runTest { + val signal by collectLastValue(underTest.autoAddSignal(USER_ID)) + assertThat(signal).isNull() + + a11yQsShortcutsRepository.setA11yQsShortcutTargets( + USER_ID, + setOf(TARGET_COMPONENT_FLATTEN) + ) + + assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC)) + } + + @Test + fun settingSetWithoutTarget_removeSignal() = + testScope.runTest { + val signal by collectLastValue(flow = underTest.autoAddSignal(USER_ID)) + assertThat(signal).isNull() + + a11yQsShortcutsRepository.setA11yQsShortcutTargets( + USER_ID, + setOf(OTHER_COMPONENT_FLATTEN) + ) + + assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC)) + } + + @Test + fun settingSetWithMultipleComponents_containsTarget_addSignal() = + testScope.runTest { + val signal by collectLastValue(underTest.autoAddSignal(USER_ID)) + assertThat(signal).isNull() + + a11yQsShortcutsRepository.setA11yQsShortcutTargets( + USER_ID, + setOf(OTHER_COMPONENT_FLATTEN, TARGET_COMPONENT_FLATTEN) + ) + + assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC)) + } + + @Test + fun settingSetWithMultipleComponents_doesNotContainTarget_removeSignal() = + testScope.runTest { + val signal by collectLastValue(underTest.autoAddSignal(USER_ID)) + assertThat(signal).isNull() + + a11yQsShortcutsRepository.setA11yQsShortcutTargets( + USER_ID, + setOf(OTHER_COMPONENT_FLATTEN, OTHER_COMPONENT_FLATTEN) + ) + + assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC)) + } + + @Test + fun multipleChangesWithTarget_onlyOneAddSignal() = + testScope.runTest { + val signals by collectValues(underTest.autoAddSignal(USER_ID)) + assertThat(signals).isEmpty() + + repeat(3) { + a11yQsShortcutsRepository.setA11yQsShortcutTargets( + USER_ID, + setOf(TARGET_COMPONENT_FLATTEN) + ) + } + + assertThat(signals.size).isEqualTo(1) + assertThat(signals[0]).isEqualTo(AutoAddSignal.Add(SPEC)) + } + + @Test + fun multipleChangesWithoutTarget_onlyOneRemoveSignal() = + testScope.runTest { + val signals by collectValues(underTest.autoAddSignal(USER_ID)) + assertThat(signals).isEmpty() + + repeat(3) { + a11yQsShortcutsRepository.setA11yQsShortcutTargets( + USER_ID, + setOf("$OTHER_COMPONENT_FLATTEN$it") + ) + } + + assertThat(signals.size).isEqualTo(1) + assertThat(signals[0]).isEqualTo(AutoAddSignal.Remove(SPEC)) + } + + @Test + fun settingSetWithTargetForUsers_onlySignalInThatUser() = + testScope.runTest { + val otherUserId = USER_ID + 1 + val signalTargetUser by collectLastValue(underTest.autoAddSignal(USER_ID)) + val signalOtherUser by collectLastValue(underTest.autoAddSignal(otherUserId)) + assertThat(signalTargetUser).isNull() + assertThat(signalOtherUser).isNull() + + a11yQsShortcutsRepository.setA11yQsShortcutTargets( + USER_ID, + setOf(TARGET_COMPONENT_FLATTEN) + ) + + assertThat(signalTargetUser).isEqualTo(AutoAddSignal.Add(SPEC)) + assertThat(signalOtherUser).isNull() + } + + @Test + fun strategyAlways() { + assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always) + } + + companion object { + private val SPEC = TileSpec.create("spec") + private val TARGET_COMPONENT = ComponentName("FakePkgName", "FakeClassName") + private val TARGET_COMPONENT_FLATTEN = TARGET_COMPONENT.flattenToString() + private val OTHER_COMPONENT_FLATTEN = + ComponentName("FakePkgName", "OtherClassName").flattenToString() + private const val USER_ID = 0 + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt index f23716ccca54..d5e43f44426b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt @@ -25,10 +25,13 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository +import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs @@ -44,7 +47,6 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -56,7 +58,8 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { - private val testScope = TestScope() + private val kosmos = Kosmos() + private val testScope = kosmos.testScope private val testDispatcher = StandardTestDispatcher() private val iStatusBarService = mock<IStatusBarService>() private val executor = FakeExecutor(FakeSystemClock()) @@ -79,6 +82,8 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { headsUpManager, powerInteractor, activeNotificationsInteractor, + kosmos.sceneContainerFlags, + kosmos::sceneInteractor, ) .apply { setUp(notificationPresenter, notificationsController) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt new file mode 100644 index 000000000000..51b834207cfd --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt @@ -0,0 +1,202 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags +import com.android.systemui.scene.shared.model.ObservableTransitionState +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.mockito.mock +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.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class ShadeControllerSceneImplTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor + private val deviceEntryInteractor = kosmos.deviceEntryInteractor + + private lateinit var shadeInteractor: ShadeInteractor + private lateinit var underTest: ShadeControllerSceneImpl + + @Before + fun setup() { + kosmos.testCase = this + kosmos.fakeSceneContainerFlags.enabled = true + kosmos.fakeFeatureFlagsClassic.apply { + set(Flags.FULL_SCREEN_USER_SWITCHER, false) + set(Flags.NSSL_DEBUG_LINES, false) + set(Flags.FULL_SCREEN_USER_SWITCHER, false) + } + kosmos.fakeDeviceEntryRepository.setUnlocked(true) + testScope.runCurrent() + shadeInteractor = kosmos.shadeInteractor + underTest = kosmos.shadeControllerSceneImpl + } + + @Test + fun animateCollapseShade_noForceNoExpansion() = + testScope.runTest { + // GIVEN shade is collapsed and a post-collapse action is enqueued + val testRunnable = mock<Runnable>() + setDeviceEntered(true) + setCollapsed() + underTest.addPostCollapseAction(testRunnable) + + // WHEN a collapse is requested + underTest.animateCollapseShade(0, force = false, delayed = false, 1f) + runCurrent() + + // THEN the shade remains collapsed and the post-collapse action ran + assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone) + verify(testRunnable, times(1)).run() + } + + @Test + fun animateCollapseShade_expandedExcludeFlagOn() = + testScope.runTest { + // GIVEN shade is fully expanded and a post-collapse action is enqueued + val testRunnable = mock<Runnable>() + underTest.addPostCollapseAction(testRunnable) + setDeviceEntered(true) + setShadeFullyExpanded() + + // WHEN a collapse is requested with FLAG_EXCLUDE_NOTIFICATION_PANEL + underTest.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) + runCurrent() + + // THEN the shade remains expanded and the post-collapse action did not run + assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade) + assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue() + verify(testRunnable, never()).run() + } + + @Test + fun animateCollapseShade_locked() = + testScope.runTest { + // GIVEN shade is fully expanded on lockscreen + setDeviceEntered(false) + setShadeFullyExpanded() + + // WHEN a collapse is requested + underTest.animateCollapseShade() + runCurrent() + + // THEN the shade collapses back to lockscreen and the post-collapse action ran + assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Lockscreen) + } + + @Test + fun animateCollapseShade_unlocked() = + testScope.runTest { + // GIVEN shade is fully expanded on an unlocked device + setDeviceEntered(true) + setShadeFullyExpanded() + + // WHEN a collapse is requested + underTest.animateCollapseShade() + runCurrent() + + // THEN the shade collapses back to lockscreen and the post-collapse action ran + assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone) + } + + @Test + fun onCollapseShade_runPostCollapseActionsCalled() = + testScope.runTest { + // GIVEN shade is expanded and a post-collapse action is enqueued + val testRunnable = mock<Runnable>() + setShadeFullyExpanded() + underTest.addPostCollapseAction(testRunnable) + + // WHEN shade collapses + setCollapsed() + + // THEN post-collapse action ran + verify(testRunnable, times(1)).run() + } + + @Test + fun postOnShadeExpanded() = + testScope.runTest { + // GIVEN shade is collapsed and a post-collapse action is enqueued + val testRunnable = mock<Runnable>() + setCollapsed() + underTest.postOnShadeExpanded(testRunnable) + + // WHEN shade expands + setShadeFullyExpanded() + + // THEN post-collapse action ran + verify(testRunnable, times(1)).run() + } + + private fun setScene(key: SceneKey) { + sceneInteractor.changeScene(SceneModel(key), "test") + sceneInteractor.setTransitionState( + MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) + ) + testScope.runCurrent() + } + + private fun setDeviceEntered(isEntered: Boolean) { + setScene( + if (isEntered) { + SceneKey.Gone + } else { + SceneKey.Lockscreen + } + ) + assertThat(deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered) + } + + private fun setCollapsed() { + setScene(SceneKey.Gone) + assertThat(shadeInteractor.isAnyExpanded.value).isFalse() + } + + private fun setShadeFullyExpanded() { + setScene(SceneKey.Shade) + assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt index 0a10b2c85ebe..0c7ce970cf3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt @@ -16,11 +16,10 @@ package com.android.systemui.statusbar.notification.collection.render +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FakeFeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline @@ -34,18 +33,19 @@ import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever @SmallTest +@RunWith(AndroidJUnit4::class) class GroupExpansionManagerTest : SysuiTestCase() { - private lateinit var gem: GroupExpansionManagerImpl + private lateinit var underTest: GroupExpansionManagerImpl private val dumpManager: DumpManager = mock() private val groupMembershipManager: GroupMembershipManager = mock() - private val featureFlags = FakeFeatureFlagsClassic() private val pipeline: NotifPipeline = mock() private lateinit var beforeRenderListListener: OnBeforeRenderListListener @@ -85,79 +85,57 @@ class GroupExpansionManagerTest : SysuiTestCase() { whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1) whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2) - gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags) + underTest = GroupExpansionManagerImpl(dumpManager, groupMembershipManager) } @Test - fun testNotifyOnlyOnChange_enabled() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun notifyOnlyOnChange() { var listenerCalledCount = 0 - gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ } + underTest.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ } - gem.setGroupExpanded(summary1, false) + underTest.setGroupExpanded(summary1, false) assertThat(listenerCalledCount).isEqualTo(0) - gem.setGroupExpanded(summary1, true) + underTest.setGroupExpanded(summary1, true) assertThat(listenerCalledCount).isEqualTo(1) - gem.setGroupExpanded(summary2, true) - assertThat(listenerCalledCount).isEqualTo(2) - gem.setGroupExpanded(summary1, true) + underTest.setGroupExpanded(summary2, true) assertThat(listenerCalledCount).isEqualTo(2) - gem.setGroupExpanded(summary2, false) - assertThat(listenerCalledCount).isEqualTo(3) - } - - @Test - fun testNotifyOnlyOnChange_disabled() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) - - var listenerCalledCount = 0 - gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ } - - gem.setGroupExpanded(summary1, false) - assertThat(listenerCalledCount).isEqualTo(1) - gem.setGroupExpanded(summary1, true) + underTest.setGroupExpanded(summary1, true) assertThat(listenerCalledCount).isEqualTo(2) - gem.setGroupExpanded(summary2, true) + underTest.setGroupExpanded(summary2, false) assertThat(listenerCalledCount).isEqualTo(3) - gem.setGroupExpanded(summary1, true) - assertThat(listenerCalledCount).isEqualTo(4) - gem.setGroupExpanded(summary2, false) - assertThat(listenerCalledCount).isEqualTo(5) } @Test - fun testExpandUnattachedEntry() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun expandUnattachedEntry() { // First, expand the entry when it is attached. - gem.setGroupExpanded(summary1, true) - assertThat(gem.isGroupExpanded(summary1)).isTrue() + underTest.setGroupExpanded(summary1, true) + assertThat(underTest.isGroupExpanded(summary1)).isTrue() // Un-attach it, and un-expand it. NotificationEntryBuilder.setNewParent(summary1, null) - gem.setGroupExpanded(summary1, false) + underTest.setGroupExpanded(summary1, false) // Expanding again should throw. - assertThrows(IllegalArgumentException::class.java) { gem.setGroupExpanded(summary1, true) } + assertThrows(IllegalArgumentException::class.java) { + underTest.setGroupExpanded(summary1, true) + } } @Test - fun testSyncWithPipeline() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - gem.attach(pipeline) + fun syncWithPipeline() { + underTest.attach(pipeline) beforeRenderListListener = withArgCaptor { verify(pipeline).addOnBeforeRenderListListener(capture()) } val listener: OnGroupExpansionChangeListener = mock() - gem.registerGroupExpansionChangeListener(listener) + underTest.registerGroupExpansionChangeListener(listener) beforeRenderListListener.onBeforeRenderList(entries) verify(listener, never()).onGroupExpansionChange(any(), any()) // Expand one of the groups. - gem.setGroupExpanded(summary1, true) + underTest.setGroupExpanded(summary1, true) verify(listener).onGroupExpansionChange(summary1.row, true) // Empty the pipeline list and verify that the group is no longer expanded. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt index c1ffa641c6a4..2cbcc5a8d925 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt @@ -16,67 +16,35 @@ package com.android.systemui.statusbar.notification.collection.render +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.google.common.truth.Truth.assertThat -import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class GroupMembershipManagerTest : SysuiTestCase() { - private lateinit var gmm: GroupMembershipManagerImpl - - private val featureFlags = FakeFeatureFlagsClassic() - - @Before - fun setUp() { - gmm = GroupMembershipManagerImpl(featureFlags) - } + private var underTest = GroupMembershipManagerImpl() @Test - fun testIsChildInGroup_topLevel() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) + fun isChildInGroup_topLevel() { val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build() - assertThat(gmm.isChildInGroup(topLevelEntry)).isFalse() - } - - @Test - fun testIsChildInGroup_noParent_old() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) - val noParentEntry = NotificationEntryBuilder().setParent(null).build() - assertThat(gmm.isChildInGroup(noParentEntry)).isTrue() + assertThat(underTest.isChildInGroup(topLevelEntry)).isFalse() } @Test - fun testIsChildInGroup_noParent_new() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + fun isChildInGroup_noParent() { val noParentEntry = NotificationEntryBuilder().setParent(null).build() - assertThat(gmm.isChildInGroup(noParentEntry)).isFalse() - } - @Test - fun testIsChildInGroup_summary_old() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) - - val groupKey = "group" - val summary = - NotificationEntryBuilder() - .setGroup(mContext, groupKey) - .setGroupSummary(mContext, true) - .build() - GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - - assertThat(gmm.isChildInGroup(summary)).isTrue() + assertThat(underTest.isChildInGroup(noParentEntry)).isFalse() } @Test - fun testIsChildInGroup_summary_new() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun isChildInGroup_summary() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -85,27 +53,17 @@ class GroupMembershipManagerTest : SysuiTestCase() { .build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - assertThat(gmm.isChildInGroup(summary)).isFalse() + assertThat(underTest.isChildInGroup(summary)).isFalse() } @Test - fun testIsChildInGroup_child() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) - val childEntry = NotificationEntryBuilder().build() - assertThat(gmm.isChildInGroup(childEntry)).isTrue() - } - - @Test - fun testIsGroupSummary_topLevelEntry() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + fun isGroupSummary_topLevelEntry() { val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build() - assertThat(gmm.isGroupSummary(entry)).isFalse() + assertThat(underTest.isGroupSummary(entry)).isFalse() } @Test - fun testIsGroupSummary_summary() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun isGroupSummary_summary() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -114,13 +72,11 @@ class GroupMembershipManagerTest : SysuiTestCase() { .build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - assertThat(gmm.isGroupSummary(summary)).isTrue() + assertThat(underTest.isGroupSummary(summary)).isTrue() } @Test - fun testIsGroupSummary_child() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun isGroupSummary_child() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -130,20 +86,17 @@ class GroupMembershipManagerTest : SysuiTestCase() { val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build() - assertThat(gmm.isGroupSummary(entry)).isFalse() + assertThat(underTest.isGroupSummary(entry)).isFalse() } @Test - fun testGetGroupSummary_topLevelEntry() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + fun getGroupSummary_topLevelEntry() { val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build() - assertThat(gmm.getGroupSummary(entry)).isNull() + assertThat(underTest.getGroupSummary(entry)).isNull() } @Test - fun testGetGroupSummary_summary() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun getGroupSummary_summary() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -152,13 +105,11 @@ class GroupMembershipManagerTest : SysuiTestCase() { .build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - assertThat(gmm.getGroupSummary(summary)).isEqualTo(summary) + assertThat(underTest.getGroupSummary(summary)).isEqualTo(summary) } @Test - fun testGetGroupSummary_child() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun getGroupSummary_child() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -168,6 +119,6 @@ class GroupMembershipManagerTest : SysuiTestCase() { val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build() - assertThat(gmm.getGroupSummary(entry)).isEqualTo(summary) + assertThat(underTest.getGroupSummary(entry)).isEqualTo(summary) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt index 00a86ffc5a8f..cc4ebd4aa4c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt @@ -1,15 +1,17 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 @@ -18,9 +20,9 @@ import android.app.PendingIntent import android.content.Intent import android.os.RemoteException import android.os.UserHandle -import android.testing.AndroidTestingRunner import android.view.View import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.ActivityIntentHelper @@ -66,7 +68,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class ActivityStarterImplTest : SysuiTestCase() { @Mock private lateinit var centralSurfaces: CentralSurfaces @Mock private lateinit var assistManager: AssistManager @@ -139,7 +141,7 @@ class ActivityStarterImplTest : SysuiTestCase() { } @Test - fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLockscreen_activityLaunchAnimator() { + fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLs_launchAnimator() { val pendingIntent = mock(PendingIntent::class.java) val parent = FrameLayout(context) val view = @@ -214,7 +216,7 @@ class ActivityStarterImplTest : SysuiTestCase() { mainExecutor.runAllReady() verify(deviceProvisionedController).isDeviceProvisioned - verify(shadeController).runPostCollapseRunnables() + verify(shadeController).collapseShadeForActivityStart() } @Test @@ -226,7 +228,7 @@ class ActivityStarterImplTest : SysuiTestCase() { mainExecutor.runAllReady() verify(deviceProvisionedController).isDeviceProvisioned - verify(shadeController, never()).runPostCollapseRunnables() + verify(shadeController, never()).collapseShadeForActivityStart() } @Test diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml index 2c7467d726b4..fab7840a6a51 100644 --- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml +++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml @@ -27,7 +27,7 @@ tools:parentTag="com.android.systemui.privacy.OngoingPrivacyChip"> > - <LinearLayout + <com.android.systemui.animation.view.LaunchableLinearLayout android:id="@+id/icons_container" android:layout_height="@dimen/ongoing_appops_chip_height" android:layout_width="wrap_content" diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index dca84b9fab7c..b792acc8b097 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -27,10 +27,11 @@ android:fitsSystemWindows="true"> <!-- Placeholder for the communal UI that will be replaced if the feature is enabled. --> - <ViewStub + <View android:id="@+id/communal_ui_stub" android:layout_width="match_parent" - android:layout_height="match_parent" /> + android:layout_height="match_parent" + android:visibility="gone" /> <com.android.systemui.scrim.ScrimView android:id="@+id/scrim_behind" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 9bc7681665f1..2b43360f0689 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1089,6 +1089,8 @@ <string name="cta_label_to_open_widget_picker">Add more widgets</string> <!-- Text for the popup to be displayed after dismissing the CTA tile. [CHAR LIMIT=50] --> <string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string> + <!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] --> + <string name="button_to_configure_widgets_text">Customize widgets</string> <!-- Label for the button which configures widgets [CHAR LIMIT=NONE] --> <string name="edit_widget">Edit widget</string> <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java index 259cca8c01e2..9e92c939dbbc 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java @@ -16,8 +16,11 @@ package com.android.systemui.shared.system; -import android.graphics.Matrix; +import static android.os.Trace.TRACE_TAG_INPUT; + import android.os.Looper; +import android.os.Trace; +import android.util.Log; import android.view.BatchedInputEventReceiver; import android.view.Choreographer; import android.view.InputChannel; @@ -52,23 +55,24 @@ public class InputChannelCompat { return target.addBatch(src); } - /** @see MotionEvent#createRotateMatrix */ - public static Matrix createRotationMatrix( - /*@Surface.Rotation*/ int rotation, int displayW, int displayH) { - return MotionEvent.createRotateMatrix(rotation, displayW, displayH); - } - /** * @see BatchedInputEventReceiver */ public static class InputEventReceiver { + private final String mName; private final BatchedInputEventReceiver mReceiver; + @Deprecated public InputEventReceiver(InputChannel inputChannel, Looper looper, Choreographer choreographer, final InputEventListener listener) { - mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) { + this("unknown", inputChannel, looper, choreographer, listener); + } + public InputEventReceiver(String name, InputChannel inputChannel, Looper looper, + Choreographer choreographer, final InputEventListener listener) { + mName = name; + mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) { @Override public void onInputEvent(InputEvent event) { listener.onInputEvent(event); @@ -89,6 +93,9 @@ public class InputChannelCompat { */ public void dispose() { mReceiver.dispose(); + Trace.instant(TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " receiver disposed"); + Log.d(InputMonitorCompat.TAG, "Input event receiver for monitor (" + mName + + ") disposed"); } } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java index c4aac111f24c..78beaf76ea78 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java @@ -17,8 +17,13 @@ package com.android.systemui.shared.system; import android.hardware.input.InputManagerGlobal; import android.os.Looper; +import android.os.Trace; +import android.util.Log; import android.view.Choreographer; import android.view.InputMonitor; +import android.view.SurfaceControl; + +import androidx.annotation.NonNull; import com.android.systemui.shared.system.InputChannelCompat.InputEventListener; import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver; @@ -27,14 +32,20 @@ import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver; * @see android.view.InputMonitor */ public class InputMonitorCompat { + static final String TAG = "InputMonitorCompat"; private final InputMonitor mInputMonitor; + private final String mName; /** * Monitor input on the specified display for gestures. */ - public InputMonitorCompat(String name, int displayId) { + public InputMonitorCompat(@NonNull String name, int displayId) { + mName = name + "-disp" + displayId; mInputMonitor = InputManagerGlobal.getInstance() .monitorGestureInput(name, displayId); + Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " created"); + Log.d(TAG, "Input monitor (" + mName + ") created"); + } /** @@ -45,10 +56,19 @@ public class InputMonitorCompat { } /** + * @see InputMonitor#getSurface() + */ + public SurfaceControl getSurface() { + return mInputMonitor.getSurface(); + } + + /** * @see InputMonitor#dispose() */ public void dispose() { mInputMonitor.dispose(); + Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " disposed"); + Log.d(TAG, "Input monitor (" + mName + ") disposed"); } /** @@ -56,7 +76,9 @@ public class InputMonitorCompat { */ public InputEventReceiver getInputReceiver(Looper looper, Choreographer choreographer, InputEventListener listener) { - return new InputEventReceiver(mInputMonitor.getInputChannel(), looper, choreographer, + Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " receiver created"); + Log.d(TAG, "Input event receiver for monitor (" + mName + ") created"); + return new InputEventReceiver(mName, mInputMonitor.getInputChannel(), looper, choreographer, listener); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 3e8c6a76998a..536f3afdd575 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -114,7 +114,6 @@ import com.android.settingslib.WirelessUtils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; -import com.android.systemui.Flags; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -383,7 +382,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private List<SubscriptionInfo> mSubscriptionInfo; @VisibleForTesting protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; - private boolean mFingerprintDetectRunning; private boolean mIsDreaming; private boolean mLogoutEnabled; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; @@ -1005,7 +1003,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean wasCancellingRestarting = mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING; mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; - mFingerprintDetectRunning = false; if (wasCancellingRestarting) { KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } else { @@ -1114,9 +1111,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING; boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING; mFingerprintRunningState = fingerprintRunningState; - if (mFingerprintRunningState == BIOMETRIC_STATE_STOPPED) { - mFingerprintDetectRunning = false; - } mLogger.logFingerprintRunningState(mFingerprintRunningState); // Clients of KeyguardUpdateMonitor don't care about the internal state about the // asynchronousness of the cancel cycle. So only notify them if the actually running state @@ -2105,7 +2099,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting void resetBiometricListeningState() { mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; - mFingerprintDetectRunning = false; } @VisibleForTesting @@ -2544,10 +2537,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return; } final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported()); - final boolean running = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING; - final boolean runningOrRestarting = running + final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING || mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING; - final boolean runDetect = shouldRunFingerprintDetect(); if (runningOrRestarting && !shouldListenForFingerprint) { if (action == BIOMETRIC_ACTION_START) { mLogger.v("Ignoring stopListeningForFingerprint()"); @@ -2559,24 +2550,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLogger.v("Ignoring startListeningForFingerprint()"); return; } - startListeningForFingerprint(runDetect); - } else if (running && runDetect && !mFingerprintDetectRunning) { - if (action == BIOMETRIC_ACTION_STOP) { - mLogger.v("Ignoring startListeningForFingerprint(detect)"); - return; - } - // stop running authentication and start running fingerprint detection - stopListeningForFingerprint(); - startListeningForFingerprint(true); + startListeningForFingerprint(); } } - private boolean shouldRunFingerprintDetect() { - return !isUnlockingWithFingerprintAllowed() - || (Flags.runFingerprintDetectOnDismissibleKeyguard() - && getUserCanSkipBouncer(mSelectedUserInteractor.getSelectedUserId())); - } - /** * If a user is encrypted or not. * This is NOT related to the lock screen being visible or not. @@ -2832,6 +2809,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && biometricEnabledForUser && !isUserInLockdown(user); final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed(); + final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled(); final boolean shouldListenBouncerState = !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing; @@ -2894,7 +2872,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - private void startListeningForFingerprint(boolean runDetect) { + private void startListeningForFingerprint() { final int userId = mSelectedUserInteractor.getSelectedUserId(); final boolean unlockPossible = isUnlockWithFingerprintPossible(userId); if (mFingerprintCancelSignal != null) { @@ -2924,20 +2902,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintInteractiveToAuthProvider.getVendorExtension(userId)); } - if (runDetect) { + if (!isUnlockingWithFingerprintAllowed()) { mLogger.v("startListeningForFingerprint - detect"); mFpm.detectFingerprint( mFingerprintCancelSignal, mFingerprintDetectionCallback, fingerprintAuthenticateOptions); - mFingerprintDetectRunning = true; } else { mLogger.v("startListeningForFingerprint"); mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal, mFingerprintAuthenticationCallback, null /* handler */, fingerprintAuthenticateOptions); - mFingerprintDetectRunning = false; } setFingerprintRunningState(BIOMETRIC_STATE_RUNNING); } @@ -3962,7 +3938,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mSelectedUserInteractor.getSelectedUserId())); pw.println(" getUserUnlockedWithBiometric()=" + getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId())); - pw.println(" mFingerprintDetectRunning=" + mFingerprintDetectRunning); pw.println(" SIM States:"); for (SimData data : mSimDatas.values()) { pw.println(" " + data.toString()); diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java index 0f5f869cba5d..43728260248a 100644 --- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java @@ -20,11 +20,12 @@ import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.pm.UserInfo; +import android.os.HandlerExecutor; +import android.os.HandlerThread; import android.os.UserHandle; import androidx.annotation.NonNull; -import com.android.systemui.res.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.systemui.GuestResetOrExitSessionReceiver.ResetSessionDialogFactory; @@ -32,6 +33,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.qs.QSUserSwitcherEvent; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.UserSwitcherController; @@ -61,6 +63,7 @@ public class GuestResumeSessionReceiver { private final SecureSettings mSecureSettings; private final ResetSessionDialogFactory mResetSessionDialogFactory; private final GuestSessionNotification mGuestSessionNotification; + private final HandlerThread mHandlerThread; @VisibleForTesting public final UserTracker.Callback mUserChangedCallback = @@ -111,13 +114,16 @@ public class GuestResumeSessionReceiver { mSecureSettings = secureSettings; mGuestSessionNotification = guestSessionNotification; mResetSessionDialogFactory = resetSessionDialogFactory; + mHandlerThread = new HandlerThread("GuestResumeSessionReceiver"); + mHandlerThread.start(); } /** * Register this receiver with the {@link BroadcastDispatcher} */ public void register() { - mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); + mUserTracker.addCallback(mUserChangedCallback, + new HandlerExecutor(mHandlerThread.getThreadHandler())); } private void cancelDialog() { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt index 8c2d221e3f97..35f9344ae897 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt @@ -16,6 +16,8 @@ package com.android.systemui.accessibility +import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepository +import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepositoryImpl import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository import com.android.systemui.accessibility.data.repository.ColorCorrectionRepositoryImpl import com.android.systemui.accessibility.data.repository.ColorInversionRepository @@ -31,4 +33,9 @@ interface AccessibilityModule { @Binds fun colorInversionRepository(impl: ColorInversionRepositoryImpl): ColorInversionRepository + + @Binds + fun accessibilityQsShortcutsRepository( + impl: AccessibilityQsShortcutsRepositoryImpl + ): AccessibilityQsShortcutsRepository } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt new file mode 100644 index 000000000000..401ac0f9337b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.data.repository + +import android.util.SparseArray +import androidx.annotation.GuardedBy +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.SharedFlow + +/** Provides data related to accessibility quick setting shortcut option. */ +interface AccessibilityQsShortcutsRepository { + /** + * Observable for the a11y features the user chooses in the Settings app to use the quick + * setting option. + */ + fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>> +} + +@SysUISingleton +class AccessibilityQsShortcutsRepositoryImpl +@Inject +constructor( + private val userA11yQsShortcutsRepositoryFactory: UserA11yQsShortcutsRepository.Factory, +) : AccessibilityQsShortcutsRepository { + + @GuardedBy("userA11yQsShortcutsRepositories") + private val userA11yQsShortcutsRepositories = SparseArray<UserA11yQsShortcutsRepository>() + + override fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>> { + return synchronized(userA11yQsShortcutsRepositories) { + if (userId !in userA11yQsShortcutsRepositories) { + val userA11yQsShortcutsRepository = + userA11yQsShortcutsRepositoryFactory.create(userId) + userA11yQsShortcutsRepositories.put(userId, userA11yQsShortcutsRepository) + } + userA11yQsShortcutsRepositories.get(userId).targets + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt new file mode 100644 index 000000000000..ed91f03cc56e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.data.repository + +import android.provider.Settings +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn + +/** + * Single user version of [AccessibilityQsShortcutsRepository]. It provides a similar interface as + * [TileSpecRepository], but focusing solely on the user it was created for. It observes the changes + * on the [Settings.Secure.ACCESSIBILITY_QS_TARGETS] for a given user + */ +class UserA11yQsShortcutsRepository +@AssistedInject +constructor( + @Assisted private val userId: Int, + private val secureSettings: SecureSettings, + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { + val targets = + secureSettings + .observerFlow(userId, SETTING_NAME) + // Force an update + .onStart { emit(Unit) } + .map { getA11yQsShortcutTargets(userId) } + .flowOn(backgroundDispatcher) + .shareIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + replay = 1 + ) + + private fun getA11yQsShortcutTargets(userId: Int): Set<String> { + val settingValue = secureSettings.getStringForUser(SETTING_NAME, userId) ?: "" + return settingValue.split(SETTING_SEPARATOR).filterNot { it.isEmpty() }.toSet() + } + + companion object { + const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS + const val SETTING_SEPARATOR = ":" + } + + @AssistedFactory + interface Factory { + fun create( + userId: Int, + ): UserA11yQsShortcutsRepository + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 86f372a94848..71d0e7d6081a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -35,7 +35,6 @@ import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.log.core.LogLevel import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -51,7 +50,6 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.ViewController -import kotlinx.coroutines.ExperimentalCoroutinesApi import java.io.PrintWriter import javax.inject.Inject import javax.inject.Provider @@ -64,7 +62,6 @@ import javax.inject.Provider * * The ripple uses the accent color of the current theme. */ -@ExperimentalCoroutinesApi @SysUISingleton class AuthRippleController @Inject constructor( private val sysuiContext: Context, @@ -316,18 +313,6 @@ class AuthRippleController @Inject constructor( mView.fadeDwellRipple() } } - - override fun onBiometricDetected( - userId: Int, - biometricSourceType: BiometricSourceType, - isStrongBiometric: Boolean - ) { - // TODO (b/309804148): add support detect auth ripple for deviceEntryUdfpsRefactor - if (!DeviceEntryUdfpsRefactor.isEnabled && - keyguardUpdateMonitor.getUserCanSkipBouncer(userId)) { - showUnlockRipple(biometricSourceType) - } - } } private val configurationChangedListener = diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt index ad2136af4b86..d28dbc0ae06f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt @@ -94,6 +94,10 @@ constructor( override fun onAuthenticationStopped() { updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning) } + + override fun onAuthenticationSucceeded(requestReason: Int, userId: Int) {} + + override fun onAuthenticationFailed(requestReason: Int, userId: Int) {} } updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt index 16e7f05fae37..96582cb56dd7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt @@ -114,7 +114,7 @@ private fun createNewRowLayout(inflater: LayoutInflater): LinearLayout { private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn( resources: Resources, ): Boolean { - val passedInText: CharSequence = + val passedInText: String = when (this) { is PromptContentItemPlainText -> text is PromptContentItemBulletedText -> text 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 e78a7a942993..0f1340a63032 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 @@ -259,7 +259,9 @@ constructor( /** Custom content view for the prompt. */ val contentView: Flow<PromptContentView?> = - promptSelectorInteractor.prompt.map { it?.contentView }.distinctUntilChanged() + promptSelectorInteractor.prompt + .map { if (customBiometricPrompt()) it?.contentView else null } + .distinctUntilChanged() private val originalDescription = promptSelectorInteractor.prompt.map { it?.description ?: "" }.distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt index 1f4be4060223..addd880f2079 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt @@ -20,13 +20,18 @@ import com.android.systemui.Flags.communalHub import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -34,16 +39,26 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext /** Encapsulates the state of communal mode. */ interface CommunalRepository { /** Whether communal features are enabled. */ val isCommunalEnabled: Boolean + /** + * A {@link StateFlow} that tracks whether communal hub is enabled (it can be disabled in + * settings). + */ + val communalEnabledState: StateFlow<Boolean> + /** Whether the communal hub is showing. */ val isCommunalHubShowing: Flow<Boolean> @@ -72,13 +87,36 @@ interface CommunalRepository { class CommunalRepositoryImpl @Inject constructor( + @Application private val applicationScope: CoroutineScope, @Background backgroundScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, private val featureFlagsClassic: FeatureFlagsClassic, sceneContainerFlags: SceneContainerFlags, sceneContainerRepository: SceneContainerRepository, + userRepository: UserRepository, + private val secureSettings: SecureSettings ) : CommunalRepository { + + private val communalEnabledSettingState: Flow<Boolean> = + userRepository.selectedUserInfo + .flatMapLatest { userInfo -> observeSettings(userInfo.id) } + .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed()) + + override val communalEnabledState: StateFlow<Boolean> = + if (featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()) { + communalEnabledSettingState + .filterNotNull() + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = true + ) + } else { + MutableStateFlow(false) + } + override val isCommunalEnabled: Boolean - get() = featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub() + get() = communalEnabledState.value private val _desiredScene: MutableStateFlow<CommunalSceneKey> = MutableStateFlow(CommunalSceneKey.DEFAULT) @@ -115,4 +153,26 @@ constructor( } else { desiredScene.map { sceneKey -> sceneKey == CommunalSceneKey.Communal } } + + private fun observeSettings(userId: Int): Flow<Boolean> = + secureSettings + .observerFlow( + userId = userId, + names = + arrayOf( + GLANCEABLE_HUB_ENABLED, + ) + ) + // Force an update + .onStart { emit(Unit) } + .map { readFromSettings(userId) } + + private suspend fun readFromSettings(userId: Int): Boolean = + withContext(backgroundDispatcher) { + secureSettings.getIntForUser(GLANCEABLE_HUB_ENABLED, 1, userId) == 1 + } + + companion object { + private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled" + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 4c5871d796b1..c36f7fa22c82 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -72,7 +72,12 @@ constructor( val isCommunalEnabled: Boolean get() = communalRepository.isCommunalEnabled - val isCommunalAvailable = + /** A {@link StateFlow} that tracks whether communal features are enabled. */ + val communalEnabledState: StateFlow<Boolean> + get() = communalRepository.communalEnabledState + + /** Whether communal features are enabled and available. */ + val isCommunalAvailable: StateFlow<Boolean> = flowOf(isCommunalEnabled) .flatMapLatest { enabled -> if (enabled) diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 3f7c152f47d8..d2883cce06c2 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -44,11 +44,11 @@ object Flags { // 100 - notification // TODO(b/297792660): Tracking Bug @JvmField val UNCLEARED_TRANSIENT_HUN_FIX = - unreleasedFlag("uncleared_transient_hun_fix", teamfood = true) + releasedFlag("uncleared_transient_hun_fix") // TODO(b/298308067): Tracking Bug @JvmField val SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX = - unreleasedFlag("swipe_uncleared_transient_view_fix", teamfood = true) + releasedFlag("swipe_uncleared_transient_view_fix") // TODO(b/254512751): Tracking Bug val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = @@ -102,12 +102,6 @@ object Flags { default = true ) - /** Only notify group expansion listeners when a change happens. */ - // TODO(b/292213543): Tracking Bug - @JvmField - val NOTIFICATION_GROUP_EXPANSION_CHANGE = - releasedFlag("notification_group_expansion_change") - // TODO(b/301955929) @JvmField val NOTIF_LS_BACKGROUND_THREAD = diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 3de9e68909cf..a95ddb5a0201 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -2437,6 +2437,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene return true; } }); + mGlobalActionsLayout.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); mGlobalActionsLayout.setRotationListener(this::onRotate); mGlobalActionsLayout.setAdapter(mAdapter); mContainer = findViewById(com.android.systemui.res.R.id.global_actions_container); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt index bc6c7cbf35fb..ad589dfcff9e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt @@ -120,8 +120,20 @@ constructor( } else { connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin) } - connect(nicId, START, PARENT_ID, START) - connect(nicId, END, PARENT_ID, END) + connect( + nicId, + START, + PARENT_ID, + START, + context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal) + ) + connect( + nicId, + END, + PARENT_ID, + END, + context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal) + ) constrainHeight( nicId, context.resources.getDimensionPixelSize(R.dimen.notification_shelf_height) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt index a1b3f270f642..fe4f07d022dd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt @@ -31,6 +31,7 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.constraintlayout.widget.ConstraintSet.VISIBLE import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT import com.android.systemui.Flags +import com.android.systemui.customization.R as customizationR import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.KeyguardSection @@ -160,16 +161,14 @@ constructor( var largeClockTopMargin = context.resources.getDimensionPixelSize(R.dimen.status_bar_height) + context.resources.getDimensionPixelSize( - com.android.systemui.customization.R.dimen.small_clock_padding_top + customizationR.dimen.small_clock_padding_top ) + context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) largeClockTopMargin += getDimen(DATE_WEATHER_VIEW_HEIGHT) largeClockTopMargin += getDimen(ENHANCED_SMARTSPACE_HEIGHT) if (!keyguardClockViewModel.useLargeClock) { largeClockTopMargin -= - context.resources.getDimensionPixelSize( - com.android.systemui.customization.R.dimen.small_clock_height - ) + context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height) } connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin) constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT) @@ -177,18 +176,15 @@ constructor( constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT) constrainHeight( R.id.lockscreen_clock_view, - context.resources.getDimensionPixelSize( - com.android.systemui.customization.R.dimen.small_clock_height - ) + context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height) ) connect( R.id.lockscreen_clock_view, START, PARENT_ID, START, - context.resources.getDimensionPixelSize( - com.android.systemui.customization.R.dimen.clock_padding_start - ) + context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) + + context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal) ) var smallClockTopMargin = if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) { @@ -199,9 +195,7 @@ constructor( } if (keyguardClockViewModel.useLargeClock) { smallClockTopMargin -= - context.resources.getDimensionPixelSize( - com.android.systemui.customization.R.dimen.small_clock_height - ) + context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height) } connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt index 8c5e9b4c6817..d75a72f91061 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.Context -import android.view.View import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.END @@ -67,7 +66,6 @@ constructor( notificationStackSizeCalculator, mainDispatcher, ) { - private val smartSpaceBarrier = View.generateViewId() override fun applyConstraints(constraintSet: ConstraintSet) { if (!KeyguardShadeMigrationNssl.isEnabled) { return diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index 37842a84c3d3..2f99719df36c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -31,7 +31,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.KeyguardSmartspaceViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel -import com.android.systemui.shared.R +import com.android.systemui.res.R as R +import com.android.systemui.shared.R as sharedR import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import dagger.Lazy import javax.inject.Inject @@ -100,94 +101,94 @@ constructor( if (!migrateClocksToBlueprint()) { return } + val horizontalPaddingStart = + context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) + + context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal) + val horizontalPaddingEnd = + context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end) + + context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal) constraintSet.apply { // migrate addDateWeatherView, addWeatherView from KeyguardClockSwitchController - constrainHeight(R.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT) - constrainWidth(R.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT) + constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT) + constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT) connect( - R.id.date_smartspace_view, + sharedR.id.date_smartspace_view, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, - context.resources.getDimensionPixelSize( - com.android.systemui.res.R.dimen.below_clock_padding_start - ) + horizontalPaddingStart ) - constrainWidth(R.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT) + constrainWidth(sharedR.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT) connect( - R.id.weather_smartspace_view, + sharedR.id.weather_smartspace_view, ConstraintSet.TOP, - R.id.date_smartspace_view, + sharedR.id.date_smartspace_view, ConstraintSet.TOP ) connect( - R.id.weather_smartspace_view, + sharedR.id.weather_smartspace_view, ConstraintSet.BOTTOM, - R.id.date_smartspace_view, + sharedR.id.date_smartspace_view, ConstraintSet.BOTTOM ) connect( - R.id.weather_smartspace_view, + sharedR.id.weather_smartspace_view, ConstraintSet.START, - R.id.date_smartspace_view, + sharedR.id.date_smartspace_view, ConstraintSet.END, 4 ) // migrate addSmartspaceView from KeyguardClockSwitchController - constrainHeight(R.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT) + constrainHeight(sharedR.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT) connect( - R.id.bc_smartspace_view, + sharedR.id.bc_smartspace_view, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, - context.resources.getDimensionPixelSize( - com.android.systemui.res.R.dimen.below_clock_padding_start - ) + horizontalPaddingStart ) connect( - R.id.bc_smartspace_view, + sharedR.id.bc_smartspace_view, ConstraintSet.END, if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID - else com.android.systemui.res.R.id.split_shade_guideline, + else R.id.split_shade_guideline, ConstraintSet.END, - context.resources.getDimensionPixelSize( - com.android.systemui.res.R.dimen.below_clock_padding_end - ) + horizontalPaddingEnd ) if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) { - clear(R.id.date_smartspace_view, ConstraintSet.TOP) + clear(sharedR.id.date_smartspace_view, ConstraintSet.TOP) connect( - R.id.date_smartspace_view, + sharedR.id.date_smartspace_view, ConstraintSet.BOTTOM, - R.id.bc_smartspace_view, + sharedR.id.bc_smartspace_view, ConstraintSet.TOP ) } else { - clear(R.id.date_smartspace_view, ConstraintSet.BOTTOM) + clear(sharedR.id.date_smartspace_view, ConstraintSet.BOTTOM) connect( - R.id.date_smartspace_view, + sharedR.id.date_smartspace_view, ConstraintSet.TOP, - com.android.systemui.res.R.id.lockscreen_clock_view, + R.id.lockscreen_clock_view, ConstraintSet.BOTTOM ) connect( - R.id.bc_smartspace_view, + sharedR.id.bc_smartspace_view, ConstraintSet.TOP, - R.id.date_smartspace_view, + sharedR.id.date_smartspace_view, ConstraintSet.BOTTOM ) } createBarrier( - com.android.systemui.res.R.id.smart_space_barrier_bottom, + R.id.smart_space_barrier_bottom, Barrier.BOTTOM, 0, *intArrayOf( - R.id.bc_smartspace_view, - R.id.date_smartspace_view, - R.id.weather_smartspace_view, + sharedR.id.bc_smartspace_view, + sharedR.id.date_smartspace_view, + sharedR.id.weather_smartspace_view, ) ) } @@ -212,7 +213,7 @@ constructor( private fun updateVisibility(constraintSet: ConstraintSet) { constraintSet.apply { setVisibility( - R.id.weather_smartspace_view, + sharedR.id.weather_smartspace_view, when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) { true -> ConstraintSet.GONE false -> @@ -223,7 +224,7 @@ constructor( } ) setVisibility( - R.id.date_smartspace_view, + sharedR.id.date_smartspace_view, if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE else ConstraintSet.VISIBLE ) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt index 23ee00d88fdc..a3029b284934 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt @@ -146,7 +146,7 @@ constructor( null, UserHandle.ALL ) - userTracker.addCallback(userTrackerCallback, mainExecutor) + userTracker.addCallback(userTrackerCallback, backgroundExecutor) loadSavedComponents() } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 58e042868607..91c86dff34ea 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -84,6 +84,7 @@ import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputChannelCompat; +import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.shared.system.TaskStackChangeListener; @@ -261,7 +262,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private boolean mIsTrackpadThreeFingerSwipe; private boolean mIsButtonForcedVisible; - private InputMonitor mInputMonitor; + private InputMonitorCompat mInputMonitor; private InputChannelCompat.InputEventReceiver mInputEventReceiver; private NavigationEdgeBackPlugin mEdgeBackPlugin; @@ -665,10 +666,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } // Register input event receiver - mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput( - "edge-swipe", mDisplayId); - mInputEventReceiver = new InputChannelCompat.InputEventReceiver( - mInputMonitor.getInputChannel(), Looper.getMainLooper(), + mInputMonitor = new InputMonitorCompat("edge-swipe", mDisplayId); + mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(), Choreographer.getInstance(), this::onInputEvent); // Add a nav bar panel window diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index 958ace358816..21de185ee838 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -26,6 +26,8 @@ import android.content.res.Configuration; import android.database.ContentObserver; import android.os.BatteryManager; import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.HandlerThread; import android.os.IThermalEventListener; import android.os.IThermalService; import android.os.PowerManager; @@ -95,6 +97,7 @@ public class PowerUI implements private Future mLastShowWarningTask; private boolean mEnableSkinTemperatureWarning; private boolean mEnableUsbTemperatureAlarm; + private final HandlerThread mHandlerThread; private int mLowBatteryAlertCloseLevel; private final int[] mLowBatteryReminderLevels = new int[2]; @@ -167,6 +170,8 @@ public class PowerUI implements mPowerManager = powerManager; mWakefulnessLifecycle = wakefulnessLifecycle; mUserTracker = userTracker; + mHandlerThread = new HandlerThread("PowerUI"); + mHandlerThread.start(); } public void start() { @@ -185,7 +190,8 @@ public class PowerUI implements false, obs, UserHandle.USER_ALL); updateBatteryWarningLevels(); mReceiver.init(); - mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); + mUserTracker.addCallback(mUserChangedCallback, + new HandlerExecutor(mHandlerThread.getThreadHandler())); mWakefulnessLifecycle.addObserver(mWakefulnessObserver); // Check to see if we need to let the user know that the phone previously shut down due diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt index 8e1b00d825aa..7a4be3fdc93b 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt @@ -23,11 +23,11 @@ import android.view.Gravity.END import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout import com.android.settingslib.Utils import com.android.systemui.res.R -import com.android.systemui.animation.view.LaunchableFrameLayout import com.android.systemui.statusbar.events.BackgroundAnimatableView class OngoingPrivacyChip @JvmOverloads constructor( @@ -35,7 +35,7 @@ class OngoingPrivacyChip @JvmOverloads constructor( attrs: AttributeSet? = null, defStyleAttrs: Int = 0, defStyleRes: Int = 0 -) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView { +) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView { private var configuration: Configuration private var iconMargin = 0 @@ -43,6 +43,8 @@ class OngoingPrivacyChip @JvmOverloads constructor( private var iconColor = 0 private val iconsContainer: LinearLayout + val launchableContentView + get() = iconsContainer var privacyList = emptyList<PrivacyItem>() set(value) { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt index 76ef8a2b813c..f121630d180e 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt @@ -26,9 +26,9 @@ import android.content.pm.PackageManager import android.os.UserHandle import android.permission.PermissionGroupUsage import android.permission.PermissionManager -import android.view.View import androidx.annotation.MainThread import androidx.annotation.WorkerThread +import androidx.core.view.isVisible import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.appops.AppOpsController @@ -214,7 +214,7 @@ class PrivacyDialogControllerV2( * @param context A context to use to create the dialog. * @see filterAndSelect */ - fun showDialog(context: Context, view: View? = null) { + fun showDialog(context: Context, privacyChip: OngoingPrivacyChip? = null) { dismissDialog() backgroundExecutor.execute { val usage = permGroupUsage() @@ -277,8 +277,8 @@ class PrivacyDialogControllerV2( ) d.setShowForAllUsers(true) d.addOnDismissListener(onDialogDismissed) - if (view != null) { - val controller = getPrivacyDialogController(view) + if (privacyChip != null) { + val controller = getPrivacyDialogController(privacyChip) if (controller == null) { d.show() } else { @@ -296,10 +296,13 @@ class PrivacyDialogControllerV2( } } - private fun getPrivacyDialogController(source: View): DialogLaunchAnimator.Controller? { - val delegate = DialogLaunchAnimator.Controller.fromView(source) ?: return null + private fun getPrivacyDialogController( + source: OngoingPrivacyChip + ): DialogLaunchAnimator.Controller? { + val delegate = + DialogLaunchAnimator.Controller.fromView(source.launchableContentView) ?: return null return object : DialogLaunchAnimator.Controller by delegate { - override fun shouldAnimateExit() = false + override fun shouldAnimateExit() = source.isVisible } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt index adea26e5aa26..e1ec338cec6f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt @@ -18,6 +18,8 @@ package com.android.systemui.qs.pipeline.dagger import android.content.res.Resources import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.pipeline.domain.autoaddable.A11yShortcutAutoAddable +import com.android.systemui.qs.pipeline.domain.autoaddable.A11yShortcutAutoAddableList import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSetting import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSettingList import com.android.systemui.qs.pipeline.domain.autoaddable.CastAutoAddable @@ -51,6 +53,16 @@ interface BaseAutoAddableModule { ) .toSet() } + + @Provides + @ElementsIntoSet + fun providesA11yShortcutAutoAddable( + a11yShortcutAutoAddableFactory: A11yShortcutAutoAddable.Factory + ): Set<AutoAddable> { + return A11yShortcutAutoAddableList.getA11yShortcutAutoAddables( + a11yShortcutAutoAddableFactory + ) + } } @Binds @IntoSet fun bindCastAutoAddable(impl: CastAutoAddable): AutoAddable diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt new file mode 100644 index 000000000000..2cebbe3f372c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.domain.autoaddable + +import android.content.ComponentName +import android.provider.Settings +import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepository +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal +import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking +import com.android.systemui.qs.pipeline.domain.model.AutoAddable +import com.android.systemui.qs.pipeline.shared.TileSpec +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.util.Objects +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map + +/** + * [A11yShortcutAutoAddable] will auto add/remove qs tile of the accessibility framework feature + * based on the user's choices in the Settings app. + * + * The a11y feature component is added to [Settings.Secure.ACCESSIBILITY_QS_TARGETS] when the user + * selects to use qs tile as a shortcut for the a11 feature in the Settings app. The accessibility + * feature component is removed from [Settings.Secure.ACCESSIBILITY_QS_TARGETS] when the user + * doesn't want to use qs tile as a shortcut for the a11y feature in the Settings app. + * + * [A11yShortcutAutoAddable] tracks a [Settings.Secure.ACCESSIBILITY_QS_TARGETS] and when its value + * changes, it will emit a [AutoAddSignal.Add] for the [spec] if the [componentName] is a substring + * of the value; it will emit a [AutoAddSignal.Remove] for the [spec] if the [componentName] is not + * a substring of the value. + */ +class A11yShortcutAutoAddable +@AssistedInject +constructor( + private val a11yQsShortcutsRepository: AccessibilityQsShortcutsRepository, + @Background private val bgDispatcher: CoroutineDispatcher, + @Assisted private val spec: TileSpec, + @Assisted private val componentName: ComponentName +) : AutoAddable { + + override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> { + return a11yQsShortcutsRepository + .a11yQsShortcutTargets(userId) + .map { it.contains(componentName.flattenToString()) } + .filterNotNull() + .distinctUntilChanged() + .map { if (it) AutoAddSignal.Add(spec) else AutoAddSignal.Remove(spec) } + .flowOn(bgDispatcher) + } + + override val autoAddTracking = AutoAddTracking.Always + + override val description = + "A11yShortcutAutoAddableSetting: $spec:$componentName ($autoAddTracking)" + + override fun equals(other: Any?): Boolean { + return other is A11yShortcutAutoAddable && + spec == other.spec && + componentName == other.componentName + } + + override fun hashCode(): Int { + return Objects.hash(spec, componentName) + } + + override fun toString(): String { + return description + } + + @AssistedFactory + interface Factory { + fun create(spec: TileSpec, componentName: ComponentName): A11yShortcutAutoAddable + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt new file mode 100644 index 000000000000..08e39204386e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.domain.autoaddable + +import android.view.accessibility.Flags +import com.android.internal.accessibility.AccessibilityShortcutController +import com.android.systemui.qs.pipeline.domain.model.AutoAddable +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.ColorCorrectionTile +import com.android.systemui.qs.tiles.ColorInversionTile +import com.android.systemui.qs.tiles.OneHandedModeTile +import com.android.systemui.qs.tiles.ReduceBrightColorsTile + +object A11yShortcutAutoAddableList { + + /** + * Generate a collection of [A11yShortcutAutoAddable] for the framework tiles related to + * accessibility features with shortcut options + */ + fun getA11yShortcutAutoAddables(factory: A11yShortcutAutoAddable.Factory): Set<AutoAddable> { + return if (Flags.a11yQsShortcut()) { + setOf( + factory.create( + TileSpec.create(ColorCorrectionTile.TILE_SPEC), + AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME + ), + factory.create( + TileSpec.create(ColorInversionTile.TILE_SPEC), + AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME + ), + factory.create( + TileSpec.create(OneHandedModeTile.TILE_SPEC), + AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME + ), + factory.create( + TileSpec.create(ReduceBrightColorsTile.TILE_SPEC), + AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME + ), + ) + } else { + emptySet() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt index 216d716b07a4..88863cbad1ee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt @@ -17,6 +17,8 @@ package com.android.systemui.qs.tiles import android.app.AlertDialog +import android.app.BroadcastOptions +import android.app.PendingIntent import android.content.Intent import android.os.Handler import android.os.Looper @@ -42,6 +44,8 @@ import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.recordissue.RecordIssueDialogDelegate import com.android.systemui.res.R +import com.android.systemui.screenrecord.RecordingService +import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.KeyguardDismissUtil import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject @@ -61,6 +65,7 @@ constructor( private val keyguardDismissUtil: KeyguardDismissUtil, private val keyguardStateController: KeyguardStateController, private val dialogLaunchAnimator: DialogLaunchAnimator, + private val userContextProvider: UserContextProvider, private val delegateFactory: RecordIssueDialogDelegate.Factory, ) : QSTileImpl<QSTile.BooleanState>( @@ -91,12 +96,22 @@ constructor( public override fun handleClick(view: View?) { if (isRecording) { isRecording = false + stopScreenRecord() } else { mUiHandler.post { showPrompt(view) } } refreshState() } + private fun stopScreenRecord() = + PendingIntent.getService( + userContextProvider.userContext, + RecordingService.REQUEST_CODE, + RecordingService.getStopIntent(userContextProvider.userContext), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle()) + private fun showPrompt(view: View?) { val dialog: AlertDialog = delegateFactory diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 592cb3b18e80..211b459471de 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -192,6 +192,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi private DialogLaunchAnimator mDialogLaunchAnimator; private boolean mHasWifiEntries; private WifiStateWorker mWifiStateWorker; + private boolean mHasActiveSubId; @VisibleForTesting static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f; @@ -299,6 +300,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi mExecutor); // Listen the subscription changes mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener(); + refreshHasActiveSubId(); mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, mOnSubscriptionsChangedListener); mDefaultDataSubId = getDefaultDataSubscriptionId(); @@ -901,18 +903,22 @@ public class InternetDialogController implements AccessPointController.AccessPoi * @return whether there is the carrier item in the slice. */ boolean hasActiveSubId() { - if (mSubscriptionManager == null) { - if (DEBUG) { - Log.d(TAG, "SubscriptionManager is null, can not check carrier."); - } + if (isAirplaneModeEnabled() || mTelephonyManager == null) { return false; } - if (isAirplaneModeEnabled() || mTelephonyManager == null - || mSubscriptionManager.getActiveSubscriptionIdList().length <= 0) { - return false; + return mHasActiveSubId; + } + + private void refreshHasActiveSubId() { + if (mSubscriptionManager == null) { + mHasActiveSubId = false; + Log.e(TAG, "SubscriptionManager is null, set mHasActiveSubId = false"); + return; } - return true; + + mHasActiveSubId = mSubscriptionManager.getActiveSubscriptionIdList().length > 0; + Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubId); } /** @@ -1204,6 +1210,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi @Override public void onSubscriptionsChanged() { + refreshHasActiveSubId(); updateListener(); } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt index e2959fe834d4..1c37908235bd 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt @@ -23,16 +23,22 @@ import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository +import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.scene.shared.model.ObservableTransitionState +import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.init.NotificationsController import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.policy.HeadsUpManager import javax.inject.Inject +import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -47,6 +53,8 @@ constructor( private val headsUpManager: HeadsUpManager, private val powerInteractor: PowerInteractor, private val activeNotificationsInteractor: ActiveNotificationsInteractor, + sceneContainerFlags: SceneContainerFlags, + sceneInteractorProvider: Provider<SceneInteractor>, ) : CoreStartable { private var notificationPresenter: NotificationPresenter? = null @@ -58,11 +66,28 @@ constructor( /** * True if lockscreen (including AOD) or the shade is visible and false otherwise. Notably, * false if the bouncer is visible. - * - * TODO(b/297080059): Use [SceneInteractor] as the source of truth if the scene flag is on. */ val isLockscreenOrShadeVisible: StateFlow<Boolean> = - windowRootViewVisibilityRepository.isLockscreenOrShadeVisible + if (!sceneContainerFlags.isEnabled()) { + windowRootViewVisibilityRepository.isLockscreenOrShadeVisible + } else { + sceneInteractorProvider + .get() + .transitionState + .map { state -> + when (state) { + is ObservableTransitionState.Idle -> + state.scene == SceneKey.Shade || state.scene == SceneKey.Lockscreen + is ObservableTransitionState.Transition -> + state.toScene == SceneKey.Shade || + state.toScene == SceneKey.Lockscreen || + state.fromScene == SceneKey.Shade || + state.fromScene == SceneKey.Lockscreen + } + } + .distinctUntilChanged() + .stateIn(scope, SharingStarted.Eagerly, false) + } /** * True if lockscreen (including AOD) or the shade is visible **and** the user is currently diff --git a/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt new file mode 100644 index 000000000000..f71a401d1612 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import com.android.systemui.assist.AssistManager +import com.android.systemui.log.LogBuffer +import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import dagger.Lazy +import kotlinx.coroutines.ExperimentalCoroutinesApi + +/** A base class for non-empty implementations of ShadeController. */ +@OptIn(ExperimentalCoroutinesApi::class) +abstract class BaseShadeControllerImpl( + private val touchLog: LogBuffer, + protected val commandQueue: CommandQueue, + protected val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, + protected val notificationShadeWindowController: NotificationShadeWindowController, + protected val assistManagerLazy: Lazy<AssistManager> +) : ShadeController { + protected lateinit var notifPresenter: NotificationPresenter + /** Runnables to run after completing a collapse of the shade. */ + private val postCollapseActions = ArrayList<Runnable>() + + override fun start() { + logTouchesTo(touchLog) + } + + final override fun animateExpandShade() { + if (isShadeEnabled) { + expandToNotifications() + } + } + + /** Expand the shade with notifications visible. */ + protected abstract fun expandToNotifications() + + final override fun animateExpandQs() { + if (isShadeEnabled) { + expandToQs() + } + } + + /** Expand the shade showing only quick settings. */ + protected abstract fun expandToQs() + + final override fun addPostCollapseAction(action: Runnable) { + postCollapseActions.add(action) + } + + protected fun runPostCollapseActions() { + val clonedList: ArrayList<Runnable> = ArrayList(postCollapseActions) + postCollapseActions.clear() + for (r in clonedList) { + r.run() + } + statusBarKeyguardViewManager.readyForKeyguardDone() + } + + final override fun onLaunchAnimationEnd(launchIsFullScreen: Boolean) { + if (!this.notifPresenter.isCollapsing()) { + onClosingFinished() + } + if (launchIsFullScreen) { + instantCollapseShade() + } + } + final override fun onLaunchAnimationCancelled(isLaunchForActivity: Boolean) { + if ( + notifPresenter.isPresenterFullyCollapsed() && + !notifPresenter.isCollapsing() && + isLaunchForActivity + ) { + onClosingFinished() + } else { + collapseShade(true /* animate */) + } + } + + protected fun onClosingFinished() { + runPostCollapseActions() + if (!this.notifPresenter.isPresenterFullyCollapsed()) { + // if we set it not to be focusable when collapsing, we have to undo it when we aborted + // the closing + notificationShadeWindowController.setNotificationShadeFocusable(true) + } + } + + override fun setNotificationPresenter(presenter: NotificationPresenter) { + notifPresenter = presenter + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 44cb0d6ff2ba..97ec3f98cf0c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -22,6 +22,7 @@ import android.os.SystemClock import android.view.GestureDetector import android.view.MotionEvent import android.view.View +import android.view.ViewGroup import com.android.internal.annotations.VisibleForTesting import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.ui.viewmodel.CommunalViewModel @@ -33,6 +34,7 @@ import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.util.kotlin.collectFlow import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow /** * Controller that's responsible for the glanceable hub container view and its touch handling. @@ -49,7 +51,7 @@ constructor( private val powerManager: PowerManager, ) { /** The container view for the hub. This will not be initialized until [initView] is called. */ - private lateinit var communalContainerView: View + private var communalContainerView: View? = null /** * The width of the area in which a right edge swipe can open the hub, in pixels. Read from @@ -108,6 +110,11 @@ constructor( return communalInteractor.isCommunalEnabled && isComposeAvailable() } + /** Returns a {@link StateFlow} that tracks whether communal hub is enabled. */ + fun enabledState(): StateFlow<Boolean> { + return communalInteractor.communalEnabledState + } + /** * Creates the container view containing the glanceable hub UI. * @@ -125,42 +132,44 @@ constructor( if (!isEnabled()) { throw RuntimeException("Glanceable hub is not enabled") } - if (::communalContainerView.isInitialized) { + if (communalContainerView != null) { throw RuntimeException("Communal view has already been initialized") } communalContainerView = containerView rightEdgeSwipeRegionWidth = - communalContainerView.resources.getDimensionPixelSize( + containerView.resources.getDimensionPixelSize( R.dimen.communal_right_edge_swipe_region_width ) topEdgeSwipeRegionWidth = - communalContainerView.resources.getDimensionPixelSize( + containerView.resources.getDimensionPixelSize( R.dimen.communal_top_edge_swipe_region_height ) bottomEdgeSwipeRegionWidth = - communalContainerView.resources.getDimensionPixelSize( + containerView.resources.getDimensionPixelSize( R.dimen.communal_bottom_edge_swipe_region_height ) collectFlow( - communalContainerView, + containerView, keyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState), { anyBouncerShowing = it } ) - collectFlow( - communalContainerView, - communalInteractor.isCommunalShowing, - { hubShowing = it } - ) - collectFlow( - communalContainerView, - shadeInteractor.isAnyFullyExpanded, - { shadeShowing = it } - ) + collectFlow(containerView, communalInteractor.isCommunalShowing, { hubShowing = it }) + collectFlow(containerView, shadeInteractor.isAnyFullyExpanded, { shadeShowing = it }) + + communalContainerView = containerView + + return containerView + } - return communalContainerView + /** Removes the container view from its parent. */ + fun disposeView() { + communalContainerView?.let { + (it.parent as ViewGroup).removeView(it) + communalContainerView = null + } } /** @@ -173,10 +182,10 @@ constructor( * to be fully in control of its own touch handling. */ fun onTouchEvent(ev: MotionEvent): Boolean { - if (!::communalContainerView.isInitialized) { - return false - } + return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false + } + private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean { val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN val isUp = ev.actionMasked == MotionEvent.ACTION_UP val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL @@ -190,7 +199,7 @@ constructor( if (hubShowing && isDown) { val y = ev.rawY val topSwipe: Boolean = y <= topEdgeSwipeRegionWidth - val bottomSwipe = y >= communalContainerView.height - bottomEdgeSwipeRegionWidth + val bottomSwipe = y >= view.height - bottomEdgeSwipeRegionWidth if (topSwipe || bottomSwipe) { // Don't intercept touches at the top/bottom edge so that swipes can open the @@ -200,7 +209,7 @@ constructor( if (!hubOccluded) { isTrackingHubTouch = true - dispatchTouchEvent(ev) + dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a // gesture may return false from dispatchTouchEvent. return true @@ -209,7 +218,7 @@ constructor( if (isUp || isCancel) { isTrackingHubTouch = false } - dispatchTouchEvent(ev) + dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a gesture // may return false from dispatchTouchEvent. return true @@ -223,11 +232,10 @@ constructor( if (!isTrackingOpenGesture && isDown) { val x = ev.rawX - val inOpeningSwipeRegion: Boolean = - x >= communalContainerView.width - rightEdgeSwipeRegionWidth + val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth if (inOpeningSwipeRegion && !hubOccluded) { isTrackingOpenGesture = true - dispatchTouchEvent(ev) + dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a // gesture may return false from dispatchTouchEvent. return true @@ -236,7 +244,7 @@ constructor( if (isUp || isCancel) { isTrackingOpenGesture = false } - dispatchTouchEvent(ev) + dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a gesture // may return false from dispatchTouchEvent. return true @@ -249,8 +257,8 @@ constructor( * Dispatches the touch event to the communal container and sends a user activity event to reset * the screen timeout. */ - private fun dispatchTouchEvent(ev: MotionEvent) { - communalContainerView.dispatchTouchEvent(ev) + private fun dispatchTouchEvent(view: View, ev: MotionEvent) { + view.dispatchTouchEvent(ev) powerManager.userActivity( SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_TOUCH, diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index aeccf0031419..c0ceba3c1d4f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2883,7 +2883,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void onTrackingStarted() { endClosing(); mShadeRepository.setLegacyShadeTracking(true); - mTrackingStartedListener.onTrackingStarted(); + if (mTrackingStartedListener != null) { + mTrackingStartedListener.onTrackingStarted(); + } notifyExpandingStarted(); updateExpansionAndVisibility(); mScrimController.onTrackingStarted(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 863bb36e4ed0..5ecc54b09806 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -605,16 +605,21 @@ public class NotificationShadeWindowViewController implements Dumpable { * The layout lives in {@link R.id.communal_ui_stub}. */ public void setupCommunalHubLayout() { - if (!mGlanceableHubContainerController.isEnabled()) { - return; - } - - // Replace the placeholder view with the communal UI. - View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub); - int index = mView.indexOfChild(communalPlaceholder); - mView.removeView(communalPlaceholder); - - mView.addView(mGlanceableHubContainerController.initView(mView.getContext()), index); + collectFlow( + mView, + mGlanceableHubContainerController.enabledState(), + isEnabled -> { + if (isEnabled) { + View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub); + int index = mView.indexOfChild(communalPlaceholder); + mView.addView( + mGlanceableHubContainerController.initView(mView.getContext()), + index); + } else { + mGlanceableHubContainerController.disposeView(); + } + } + ); } private boolean didNotificationPanelInterceptEvent(MotionEvent ev) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java index 2c4b0b990e6d..ec4b23a56483 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java @@ -33,10 +33,20 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; * {@link com.android.systemui.keyguard.KeyguardViewMediator} and others. */ public interface ShadeController extends CoreStartable { - /** True if the shade UI is enabled on this particular Android variant and false otherwise. */ + /** + * True if the shade UI is enabled on this particular Android variant and false otherwise. + * + * @deprecated use ShadeInteractor instead + */ + @Deprecated boolean isShadeEnabled(); - /** Make our window larger and the shade expanded */ + /** + * Make our window larger and the shade expanded + * + * @deprecated will no longer be needed when keyguard is a sibling view to the shade + */ + @Deprecated void instantExpandShade(); /** Collapse the shade instantly with no animation. */ @@ -74,13 +84,28 @@ public interface ShadeController extends CoreStartable { /** Expand the shade with quick settings expanded with an animation. */ void animateExpandQs(); - /** Posts a request to collapse the shade. */ + /** + * Posts a request to collapse the shade. + * + * @deprecated use #animateCollapseShade + */ + @Deprecated void postAnimateCollapseShade(); - /** Posts a request to force collapse the shade. */ + /** + * Posts a request to force collapse the shade. + * + * @deprecated use #animateForceCollapseShade + */ + @Deprecated void postAnimateForceCollapseShade(); - /** Posts a request to expand the shade to quick settings. */ + /** + * Posts a request to expand the shade to quick settings. + * + * @deprecated use #animateExpandQs + */ + @Deprecated void postAnimateExpandQs(); /** Cancels any ongoing expansion touch handling and collapses the shade. */ @@ -90,26 +115,29 @@ public interface ShadeController extends CoreStartable { * If the shade is not fully expanded, collapse it animated. * * @return Seems to always return false + * @deprecated use {@link #collapseShade()} instead */ + @Deprecated boolean closeShadeIfOpen(); /** - * Returns whether the shade state is the keyguard or not. - */ - boolean isKeyguard(); - - /** * Returns whether the shade is currently open. * Even though in the current implementation shade is in expanded state on keyguard, this * method makes distinction between shade being truly open and plain keyguard state: * - if QS and notifications are visible on the screen, return true * - for any other state, including keyguard, return false + * + * @deprecated will be replaced by ShadeInteractor once scene container launches */ + @Deprecated boolean isShadeFullyOpen(); /** * Returns whether shade or QS are currently opening or collapsing. + * + * @deprecated will be replaced by ShadeInteractor once scene container launches */ + @Deprecated boolean isExpandingOrCollapsing(); /** @@ -127,37 +155,67 @@ public interface ShadeController extends CoreStartable { */ void addPostCollapseAction(Runnable action); - /** Run all of the runnables added by {@link #addPostCollapseAction}. */ - void runPostCollapseRunnables(); - /** * Close the shade if it was open * * @return true if the shade was open, else false */ - boolean collapseShade(); + void collapseShade(); /** * If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse * the shade. Post collapse runnables will be executed * * @param animate true to animate the collapse, false for instantaneous collapse + * @deprecated call either #animateCollapseShade or #instantCollapseShade */ + @Deprecated void collapseShade(boolean animate); - /** Calls #collapseShade if already on the main thread. If not, posts a call to it. */ + /** + * Calls #collapseShade if already on the main thread. If not, posts a call to it. + * @deprecated call #collapseShade + */ + @Deprecated void collapseOnMainThread(); - /** Makes shade expanded but not visible. */ + /** + * If necessary, instantly collapses the shade for an activity start, otherwise runs the + * post-collapse runnables. Instant collapse is ok here, because the purpose is to have the + * shade collapsed when the user returns to SysUI from the launched activity. + */ + void collapseShadeForActivityStart(); + + /** + * Makes shade expanded but not visible. + * + * @deprecated no longer needed once keyguard is a sibling view to the shade + */ + @Deprecated void makeExpandedInvisible(); - /** Makes shade expanded and visible. */ + /** + * Makes shade expanded and visible. + * + * @deprecated no longer needed once keyguard is a sibling view to the shade + */ + @Deprecated void makeExpandedVisible(boolean force); - /** Returns whether the shade is expanded and visible. */ + /** + * Returns whether the shade is expanded and visible. + * + * @deprecated no longer needed once keyguard is a sibling view to the shade + */ + @Deprecated boolean isExpandedVisible(); - /** Handle status bar touch event. */ + /** + * Handle status bar touch event. + * + * @deprecated only called by CentralSurfaces, which is being deleted + */ + @Deprecated void onStatusBarTouch(MotionEvent event); /** Called when a launch animation was cancelled. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt index 82959eea562b..08a0c934702d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt @@ -42,9 +42,6 @@ open class ShadeControllerEmptyImpl @Inject constructor() : ShadeController { override fun closeShadeIfOpen(): Boolean { return false } - override fun isKeyguard(): Boolean { - return false - } override fun isShadeFullyOpen(): Boolean { return false } @@ -53,12 +50,10 @@ open class ShadeControllerEmptyImpl @Inject constructor() : ShadeController { } override fun postOnShadeExpanded(action: Runnable?) {} override fun addPostCollapseAction(action: Runnable?) {} - override fun runPostCollapseRunnables() {} - override fun collapseShade(): Boolean { - return false - } + override fun collapseShade() {} override fun collapseShade(animate: Boolean) {} override fun collapseOnMainThread() {} + override fun collapseShadeForActivityStart() {} override fun makeExpandedInvisible() {} override fun makeExpandedVisible(force: Boolean) {} override fun isExpandedVisible(): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java index fdc7eecd9b15..e8d9c35a893b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java @@ -33,7 +33,6 @@ import com.android.systemui.log.dagger.ShadeTouchLog; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -44,14 +43,13 @@ import com.android.systemui.statusbar.window.StatusBarWindowController; import dagger.Lazy; -import java.util.ArrayList; import java.util.concurrent.Executor; import javax.inject.Inject; /** An implementation of {@link ShadeController}. */ @SysUISingleton -public final class ShadeControllerImpl implements ShadeController { +public final class ShadeControllerImpl extends BaseShadeControllerImpl { private static final String TAG = "ShadeControllerImpl"; private static final boolean SPEW = false; @@ -60,7 +58,6 @@ public final class ShadeControllerImpl implements ShadeController { private final CommandQueue mCommandQueue; private final Executor mMainExecutor; - private final LogBuffer mTouchLog; private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; private final KeyguardStateController mKeyguardStateController; private final NotificationShadeWindowController mNotificationShadeWindowController; @@ -73,12 +70,9 @@ public final class ShadeControllerImpl implements ShadeController { private final Lazy<AssistManager> mAssistManagerLazy; private final Lazy<NotificationGutsManager> mGutsManager; - private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>(); - private boolean mExpandedVisible; private boolean mLockscreenOrShadeVisible; - private NotificationPresenter mPresenter; private NotificationShadeWindowViewController mNotificationShadeWindowViewController; private ShadeVisibilityListener mShadeVisibilityListener; @@ -99,9 +93,13 @@ public final class ShadeControllerImpl implements ShadeController { Lazy<AssistManager> assistManagerLazy, Lazy<NotificationGutsManager> gutsManager ) { + super(touchLog, + commandQueue, + statusBarKeyguardViewManager, + notificationShadeWindowController, + assistManagerLazy); mCommandQueue = commandQueue; mMainExecutor = mainExecutor; - mTouchLog = touchLog; mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor; mShadeViewControllerLazy = shadeViewControllerLazy; mStatusBarStateController = statusBarStateController; @@ -117,7 +115,7 @@ public final class ShadeControllerImpl implements ShadeController { @Override public boolean isShadeEnabled() { - return true; + return mCommandQueue.panelsEnabled() && mDeviceProvisionedController.isCurrentUserSetup(); } @Override @@ -125,20 +123,16 @@ public final class ShadeControllerImpl implements ShadeController { // Make our window larger and the panel expanded. makeExpandedVisible(true /* force */); getShadeViewController().expand(false /* animate */); - mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */); + getCommandQueue().recomputeDisableFlags(mDisplayId, false /* animate */); } @Override public void animateCollapseShade(int flags, boolean force, boolean delayed, float speedUpFactor) { if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) { - runPostCollapseRunnables(); + runPostCollapseActions(); return; } - if (SPEW) { - Log.d(TAG, - "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags); - } if (getNotificationShadeWindowView() != null && getShadeViewController().canBeCollapsed() && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) { @@ -151,28 +145,19 @@ public final class ShadeControllerImpl implements ShadeController { } @Override - public void animateExpandShade() { - if (!mCommandQueue.panelsEnabled()) { - return; - } + protected void expandToNotifications() { getShadeViewController().expandToNotifications(); } @Override - public void animateExpandQs() { - if (!mCommandQueue.panelsEnabled()) { - return; - } - // Settings are not available in setup - if (!mDeviceProvisionedController.isCurrentUserSetup()) return; - + protected void expandToQs() { getShadeViewController().expandToQs(); } @Override public boolean closeShadeIfOpen() { if (!getShadeViewController().isFullyCollapsed()) { - mCommandQueue.animateCollapsePanels( + getCommandQueue().animateCollapsePanels( CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); notifyVisibilityChanged(false); mAssistManagerLazy.get().hideAssist(); @@ -181,11 +166,6 @@ public final class ShadeControllerImpl implements ShadeController { } @Override - public boolean isKeyguard() { - return mStatusBarStateController.getState() == StatusBarState.KEYGUARD; - } - - @Override public boolean isShadeFullyOpen() { return getShadeViewController().isShadeFullyExpanded(); } @@ -224,46 +204,34 @@ public final class ShadeControllerImpl implements ShadeController { } @Override - public void addPostCollapseAction(Runnable action) { - mPostCollapseRunnables.add(action); + public void collapseShade() { + collapseShadeInternal(); } - @Override - public void runPostCollapseRunnables() { - ArrayList<Runnable> clonedList = new ArrayList<>(mPostCollapseRunnables); - mPostCollapseRunnables.clear(); - int size = clonedList.size(); - for (int i = 0; i < size; i++) { - clonedList.get(i).run(); - } - mStatusBarKeyguardViewManager.readyForKeyguardDone(); - } - - @Override - public boolean collapseShade() { + private boolean collapseShadeInternal() { if (!getShadeViewController().isFullyCollapsed()) { // close the shade if it was open animateCollapseShadeForcedDelayed(); notifyVisibilityChanged(false); - return true; } else { return false; } } + @Override public void collapseShade(boolean animate) { if (animate) { - boolean willCollapse = collapseShade(); + boolean willCollapse = collapseShadeInternal(); if (!willCollapse) { - runPostCollapseRunnables(); + runPostCollapseActions(); } - } else if (!mPresenter.isPresenterFullyCollapsed()) { + } else if (!getNotifPresenter().isPresenterFullyCollapsed()) { instantCollapseShade(); notifyVisibilityChanged(false); } else { - runPostCollapseRunnables(); + runPostCollapseActions(); } } @@ -296,46 +264,16 @@ public final class ShadeControllerImpl implements ShadeController { } } - private void onClosingFinished() { - runPostCollapseRunnables(); - if (!mPresenter.isPresenterFullyCollapsed()) { - // if we set it not to be focusable when collapsing, we have to undo it when we aborted - // the closing - mNotificationShadeWindowController.setNotificationShadeFocusable(true); - } - } - - @Override - public void onLaunchAnimationCancelled(boolean isLaunchForActivity) { - if (mPresenter.isPresenterFullyCollapsed() - && !mPresenter.isCollapsing() - && isLaunchForActivity) { - onClosingFinished(); - } else { - collapseShade(true /* animate */); - } - } - - @Override - public void onLaunchAnimationEnd(boolean launchIsFullScreen) { - if (!mPresenter.isCollapsing()) { - onClosingFinished(); - } - if (launchIsFullScreen) { - instantCollapseShade(); - } - } - @Override public void instantCollapseShade() { getShadeViewController().instantCollapse(); - runPostCollapseRunnables(); + runPostCollapseActions(); } @Override public void makeExpandedVisible(boolean force) { if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); - if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) { + if (!force && (mExpandedVisible || !getCommandQueue().panelsEnabled())) { return; } @@ -346,7 +284,7 @@ public final class ShadeControllerImpl implements ShadeController { mNotificationShadeWindowController.setPanelVisible(true); notifyVisibilityChanged(true); - mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */); + getCommandQueue().recomputeDisableFlags(mDisplayId, !force /* animate */); notifyExpandedVisibleChanged(true); } @@ -377,9 +315,9 @@ public final class ShadeControllerImpl implements ShadeController { -1 /* y */, true /* resetMenu */); - runPostCollapseRunnables(); + runPostCollapseActions(); notifyExpandedVisibleChanged(false); - mCommandQueue.recomputeDisableFlags( + getCommandQueue().recomputeDisableFlags( mDisplayId, getShadeViewController().shouldHideStatusBarIconsWhenExpanded()); @@ -421,11 +359,6 @@ public final class ShadeControllerImpl implements ShadeController { } @Override - public void setNotificationPresenter(NotificationPresenter presenter) { - mPresenter = presenter; - } - - @Override public void setNotificationShadeWindowViewController( NotificationShadeWindowViewController controller) { mNotificationShadeWindowViewController = controller; @@ -441,8 +374,8 @@ public final class ShadeControllerImpl implements ShadeController { @Override public void start() { - TouchLogger.logTouchesTo(mTouchLog); - getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables); + super.start(); + getShadeViewController().setTrackingStartedListener(this::runPostCollapseActions); getShadeViewController().setOpenCloseListener( new OpenCloseListener() { @Override @@ -456,4 +389,16 @@ public final class ShadeControllerImpl implements ShadeController { } }); } + + @Override + public void collapseShadeForActivityStart() { + if (isExpandedVisible() && !mStatusBarKeyguardViewManager.isBouncerShowing()) { + animateCollapseShadeForcedDelayed(); + } else { + // Do it after DismissAction has been processed to conserve the + // needed ordering. + mMainExecutor.execute(this::runPostCollapseActions); + } + } + } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt new file mode 100644 index 000000000000..10b9db0a349b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import android.view.MotionEvent +import com.android.systemui.assist.AssistManager +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.dagger.ShadeTouchLog +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.shade.ShadeController.ShadeVisibilityListener +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import dagger.Lazy +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +/** + * Implementation of ShadeController backed by scenes instead of NPVC. + * + * TODO(b/300258424) rename to ShadeControllerImpl and inline/delete all the deprecated methods + */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class ShadeControllerSceneImpl +@Inject +constructor( + @Background private val scope: CoroutineScope, + private val shadeInteractor: ShadeInteractor, + private val sceneInteractor: SceneInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, + private val notificationStackScrollLayout: NotificationStackScrollLayout, + @ShadeTouchLog private val touchLog: LogBuffer, + commandQueue: CommandQueue, + statusBarKeyguardViewManager: StatusBarKeyguardViewManager, + notificationShadeWindowController: NotificationShadeWindowController, + assistManagerLazy: Lazy<AssistManager>, +) : + BaseShadeControllerImpl( + touchLog, + commandQueue, + statusBarKeyguardViewManager, + notificationShadeWindowController, + assistManagerLazy, + ) { + + init { + scope.launch { + shadeInteractor.isAnyExpanded.collect { + if (!it) { + runPostCollapseActions() + } + } + } + } + + override fun isShadeEnabled() = shadeInteractor.isShadeEnabled.value + + override fun isShadeFullyOpen(): Boolean = shadeInteractor.isAnyFullyExpanded.value + + override fun isExpandingOrCollapsing(): Boolean = + shadeInteractor.anyExpansion.value > 0f && shadeInteractor.anyExpansion.value < 1f + + override fun instantExpandShade() { + // Do nothing + } + + override fun instantCollapseShade() { + // TODO(b/315921512) add support for instant transition + sceneInteractor.changeScene( + SceneModel(getCollapseDestinationScene(), "instant"), + "hide shade" + ) + } + + override fun animateCollapseShade( + flags: Int, + force: Boolean, + delayed: Boolean, + speedUpFactor: Float + ) { + if (!force && !shadeInteractor.isAnyExpanded.value) { + runPostCollapseActions() + return + } + if ( + shadeInteractor.isAnyExpanded.value && + flags and CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL == 0 + ) { + // release focus immediately to kick off focus change transition + notificationShadeWindowController.setNotificationShadeFocusable(false) + notificationStackScrollLayout.cancelExpandHelper() + sceneInteractor.changeScene( + SceneModel(SceneKey.Shade, null), + "ShadeController.animateExpandShade" + ) + if (delayed) { + scope.launch { + delay(125) + animateCollapseShadeInternal() + } + } else { + animateCollapseShadeInternal() + } + } + } + + private fun animateCollapseShadeInternal() { + sceneInteractor.changeScene( + SceneModel(getCollapseDestinationScene(), "ShadeController.animateCollapseShade"), + "ShadeController.animateCollapseShade" + ) + } + + private fun getCollapseDestinationScene(): SceneKey { + return if (deviceEntryInteractor.isDeviceEntered.value) { + SceneKey.Gone + } else { + SceneKey.Lockscreen + } + } + + override fun cancelExpansionAndCollapseShade() { + // TODO do we need to actually cancel the touch session? + animateCollapseShade() + } + + override fun closeShadeIfOpen(): Boolean { + if (shadeInteractor.isAnyExpanded.value) { + commandQueue.animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, + true /* force */ + ) + assistManagerLazy.get().hideAssist() + } + return false + } + + override fun collapseShade() { + animateCollapseShadeForcedDelayed() + } + + override fun collapseShade(animate: Boolean) { + if (animate) { + animateCollapseShade() + } else { + instantCollapseShade() + } + } + + override fun collapseOnMainThread() { + // TODO if this works with delegation alone, we can deprecate and delete + collapseShade() + } + + override fun expandToNotifications() { + sceneInteractor.changeScene( + SceneModel(SceneKey.Shade, null), + "ShadeController.animateExpandShade" + ) + } + + override fun expandToQs() { + sceneInteractor.changeScene( + SceneModel(SceneKey.QuickSettings, null), + "ShadeController.animateExpandQs" + ) + } + + override fun setVisibilityListener(listener: ShadeVisibilityListener) { + scope.launch { sceneInteractor.isVisible.collect { listener.expandedVisibleChanged(it) } } + } + + @ExperimentalCoroutinesApi + override fun collapseShadeForActivityStart() { + if (shadeInteractor.isAnyExpanded.value) { + animateCollapseShadeForcedDelayed() + } else { + runPostCollapseActions() + } + } + + override fun postAnimateCollapseShade() { + animateCollapseShade() + } + + override fun postAnimateForceCollapseShade() { + animateCollapseShadeForced() + } + + override fun postAnimateExpandQs() { + expandToQs() + } + + override fun postOnShadeExpanded(action: Runnable) { + // TODO verify that clicking "reply" in a work profile notification launches the app + // TODO verify that there's not a way to replace and deprecate this method + scope.launch { + shadeInteractor.isAnyFullyExpanded.first { it } + action.run() + } + } + + override fun makeExpandedInvisible() { + // Do nothing + } + + override fun makeExpandedVisible(force: Boolean) { + // Do nothing + } + + override fun isExpandedVisible(): Boolean { + return sceneInteractor.desiredScene.value.key != SceneKey.Gone + } + + override fun onStatusBarTouch(event: MotionEvent) { + // The only call to this doesn't happen with KeyguardShadeMigrationNssl enabled + throw UnsupportedOperationException() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index c057147b022a..fc2c3ee14206 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -53,6 +53,20 @@ abstract class ShadeModule { @Provides @SysUISingleton + fun provideShadeController( + sceneContainerFlags: SceneContainerFlags, + sceneContainerOn: Provider<ShadeControllerSceneImpl>, + sceneContainerOff: Provider<ShadeControllerImpl> + ): ShadeController { + return if (sceneContainerFlags.isEnabled()) { + sceneContainerOn.get() + } else { + sceneContainerOff.get() + } + } + + @Provides + @SysUISingleton fun provideShadeAnimationInteractor( sceneContainerFlags: SceneContainerFlags, sceneContainerOn: Provider<ShadeAnimationInteractorSceneContainerImpl>, @@ -79,8 +93,4 @@ abstract class ShadeModule { abstract fun bindsShadeViewController( notificationPanelViewController: NotificationPanelViewController ): ShadeViewController - - @Binds - @SysUISingleton - abstract fun bindsShadeController(shadeControllerImpl: ShadeControllerImpl): ShadeController } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index 31a4de4d630e..43ede2aa288d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -29,7 +29,7 @@ interface ShadeInteractor : BaseShadeInteractor { val isShadeEnabled: StateFlow<Boolean> /** Whether either the shade or QS is fully expanded. */ - val isAnyFullyExpanded: Flow<Boolean> + val isAnyFullyExpanded: StateFlow<Boolean> /** Whether the Shade is fully expanded. */ val isShadeFullyExpanded: Flow<Boolean> diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt index 6defbcf29a6c..55dd6744d44e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt @@ -34,7 +34,7 @@ class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor { override val isQsBypassingShade: Flow<Boolean> = inactiveFlowBoolean override val isQsFullscreen: Flow<Boolean> = inactiveFlowBoolean override val anyExpansion: StateFlow<Float> = inactiveFlowFloat - override val isAnyFullyExpanded: Flow<Boolean> = inactiveFlowBoolean + override val isAnyFullyExpanded: StateFlow<Boolean> = inactiveFlowBoolean override val isShadeFullyExpanded: Flow<Boolean> = inactiveFlowBoolean override val isAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean override val isUserInteractingWithShade: Flow<Boolean> = inactiveFlowBoolean diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt index 6407b5a4d16c..a71cf950cbe1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt @@ -55,12 +55,20 @@ constructor( private val baseShadeInteractor: BaseShadeInteractor, ) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor { override val isShadeEnabled: StateFlow<Boolean> = - disableFlagsRepository.disableFlags - .map { it.isShadeEnabled() } + combine( + deviceProvisioningRepository.isFactoryResetProtectionActive, + disableFlagsRepository.disableFlags, + ) { isFrpActive, isDisabledByFlags -> + isDisabledByFlags.isShadeEnabled() && !isFrpActive + } + .distinctUntilChanged() .stateIn(scope, SharingStarted.Eagerly, initialValue = false) - override val isAnyFullyExpanded: Flow<Boolean> = - anyExpansion.map { it >= 1f }.distinctUntilChanged() + override val isAnyFullyExpanded: StateFlow<Boolean> = + anyExpansion + .map { it >= 1f } + .distinctUntilChanged() + .stateIn(scope, SharingStarted.Eagerly, initialValue = false) override val isShadeFullyExpanded: Flow<Boolean> = baseShadeInteractor.shadeExpansion.map { it >= 1f }.distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index fc84973c46bd..28d4457b264b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -35,7 +35,6 @@ import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; -import android.os.HandlerExecutor; import android.os.Looper; import android.provider.Settings; import android.telephony.CarrierConfigManager; @@ -61,7 +60,6 @@ import com.android.settingslib.mobile.MobileStatusTracker.SubscriptionDefaults; import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.DataUsageController; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -73,6 +71,7 @@ import com.android.systemui.log.LogBuffer; import com.android.systemui.log.core.LogLevel; import com.android.systemui.log.dagger.StatusBarNetworkControllerLog; import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -85,6 +84,8 @@ import com.android.systemui.util.CarrierConfigTracker; import dalvik.annotation.optimization.NeverCompile; +import kotlin.Unit; + import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -99,8 +100,6 @@ import java.util.stream.Collectors; import javax.inject.Inject; -import kotlin.Unit; - /** Platform implementation of the network controller. **/ @SysUISingleton public class NetworkControllerImpl extends BroadcastReceiver @@ -350,7 +349,7 @@ public class NetworkControllerImpl extends BroadcastReceiver // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it updateAirplaneMode(true /* force callback */); mUserTracker = userTracker; - mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler)); + mUserTracker.addCallback(mUserChangedCallback, mBgExecutor); deviceProvisionedController.addCallback(new DeviceProvisionedListener() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java index 2d5afd56da72..3cdb2cd9b5c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java @@ -21,8 +21,6 @@ import androidx.annotation.NonNull; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -53,14 +51,11 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl */ private final Set<NotificationEntry> mExpandedGroups = new HashSet<>(); - private final FeatureFlags mFeatureFlags; - @Inject public GroupExpansionManagerImpl(DumpManager dumpManager, - GroupMembershipManager groupMembershipManager, FeatureFlags featureFlags) { + GroupMembershipManager groupMembershipManager) { mDumpManager = dumpManager; mGroupMembershipManager = groupMembershipManager; - mFeatureFlags = featureFlags; } /** @@ -86,10 +81,8 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl }; public void attach(NotifPipeline pipeline) { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) { - mDumpManager.registerDumpable(this); - pipeline.addOnBeforeRenderListListener(mNotifTracker); - } + mDumpManager.registerDumpable(this); + pipeline.addOnBeforeRenderListListener(mNotifTracker); } @Override @@ -105,8 +98,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl @Override public void setGroupExpanded(NotificationEntry entry, boolean expanded) { NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry); - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE) - && entry.getParent() == null) { + if (entry.getParent() == null) { if (expanded) { throw new IllegalArgumentException("Cannot expand group that is not attached"); } else { @@ -124,7 +116,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl } // Only notify listeners if something changed. - if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE) || changed) { + if (changed) { sendOnGroupExpandedChange(entry, expanded); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java index cb7935369564..da1247953c4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java @@ -22,8 +22,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.flags.FeatureFlagsClassic; -import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -38,25 +36,17 @@ import javax.inject.Inject; */ @SysUISingleton public class GroupMembershipManagerImpl implements GroupMembershipManager { - FeatureFlagsClassic mFeatureFlags; - @Inject - public GroupMembershipManagerImpl(FeatureFlagsClassic featureFlags) { - mFeatureFlags = featureFlags; - } + public GroupMembershipManagerImpl() {} @Override public boolean isGroupSummary(@NonNull NotificationEntry entry) { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) { - if (entry.getParent() == null) { - // The entry is not attached, so it doesn't count. - return false; - } - // If entry is a summary, its parent is a GroupEntry with summary = entry. - return entry.getParent().getSummary() == entry; - } else { - return getGroupSummary(entry) == entry; + if (entry.getParent() == null) { + // The entry is not attached, so it doesn't count. + return false; } + // If entry is a summary, its parent is a GroupEntry with summary = entry. + return entry.getParent().getSummary() == entry; } @Nullable @@ -70,12 +60,8 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager { @Override public boolean isChildInGroup(@NonNull NotificationEntry entry) { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) { - // An entry is a child if it's not a summary or top level entry, but it is attached. - return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null; - } else { - return !isTopLevelEntry(entry); - } + // An entry is a child if it's not a summary or top level entry, but it is attached. + return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt index aca8b64c05d2..342828c4b5d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt @@ -6,6 +6,7 @@ import android.database.ContentObserver import android.net.Uri import android.os.Handler import android.os.HandlerExecutor +import android.os.HandlerThread import android.os.UserHandle import android.provider.Settings import com.android.keyguard.KeyguardUpdateMonitor @@ -87,6 +88,7 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS) private val onStateChangedListeners = ListenerSet<Consumer<String>>() private var hideSilentNotificationsOnLockscreen: Boolean = false + private val handlerThread: HandlerThread = HandlerThread("KeyguardNotificationVis") private val userTrackerCallback = object : UserTracker.Callback { override fun onUserChanged(newUser: Int, userContext: Context) { @@ -154,7 +156,9 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( notifyStateChanged("onStatusBarUpcomingStateChanged") } }) - userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler)) + handlerThread.start() + userTracker.addCallback(userTrackerCallback, + HandlerExecutor(handlerThread.getThreadHandler())) } override fun addOnStateChangedListener(listener: Consumer<String>) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 4349b3b5aeb3..c6832bc20e6d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -247,6 +247,9 @@ public class NotificationLogger implements StateListener, CoreStartable, public void setUpWithContainer(NotificationListContainer listContainer) { mListContainer = listContainer; + if (mLogging) { + mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged); + } } @Override @@ -294,7 +297,9 @@ public class NotificationLogger implements StateListener, CoreStartable, lockscreen = mLockscreen != null && mLockscreen; } mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications()); - mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged); + if (mListContainer != null) { + mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged); + } // Sometimes, the transition from lockscreenOrShadeVisible=false -> // lockscreenOrShadeVisible=true doesn't cause the scroller to emit child location // events. Hence generate one ourselves to guarantee that we're reporting visible diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt index d10b556de0fb..8bc8e8cc49a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt @@ -22,11 +22,9 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag -import com.android.systemui.statusbar.notification.row.NotificationRowModule.NOTIF_REMOTEVIEWS_FACTORIES import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import javax.inject.Named /** * Implementation of [NotifLayoutInflaterFactory]. This class uses a set of @@ -37,8 +35,7 @@ class NotifLayoutInflaterFactory constructor( @Assisted private val row: ExpandableNotificationRow, @Assisted @InflationFlag val layoutType: Int, - @Named(NOTIF_REMOTEVIEWS_FACTORIES) - private val remoteViewsFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory> + private val notifRemoteViewsFactoryContainer: NotifRemoteViewsFactoryContainer ) : LayoutInflater.Factory2 { override fun onCreateView( @@ -49,7 +46,7 @@ constructor( ): View? { var handledFactory: NotifRemoteViewsFactory? = null var result: View? = null - for (layoutFactory in remoteViewsFactories) { + for (layoutFactory in notifRemoteViewsFactoryContainer.factories) { layoutFactory.instantiate(row, layoutType, parent, name, context, attrs)?.run { check(handledFactory == null) { "$layoutFactory tries to produce name:$name with type:$layoutType. " + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt new file mode 100644 index 000000000000..99177c270b32 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.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.statusbar.notification.row + +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import javax.inject.Inject + +interface NotifRemoteViewsFactoryContainer { + val factories: Set<NotifRemoteViewsFactory> +} + +class NotifRemoteViewsFactoryContainerImpl +@Inject +constructor( + featureFlags: FeatureFlags, + precomputedTextViewFactory: PrecomputedTextViewFactory, + bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory, + callLayoutSetDataAsyncFactory: CallLayoutSetDataAsyncFactory, +) : NotifRemoteViewsFactoryContainer { + override val factories: Set<NotifRemoteViewsFactory> = buildSet { + add(precomputedTextViewFactory) + if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) { + add(bigPictureLayoutInflaterFactory) + } + if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) { + add(callLayoutSetDataAsyncFactory) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java index 46ddba4d4d8d..200a08a2ea65 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java @@ -17,26 +17,15 @@ package com.android.systemui.statusbar.notification.row; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import dagger.Binds; import dagger.Module; -import dagger.Provides; -import dagger.multibindings.ElementsIntoSet; - -import java.util.HashSet; -import java.util.Set; - -import javax.inject.Named; /** * Dagger Module containing notification row and view inflation implementations. */ @Module public abstract class NotificationRowModule { - public static final String NOTIF_REMOTEVIEWS_FACTORIES = - "notif_remoteviews_factories"; /** * Provides notification row content binder instance. @@ -54,24 +43,11 @@ public abstract class NotificationRowModule { public abstract NotifRemoteViewCache provideNotifRemoteViewCache( NotifRemoteViewCacheImpl cacheImpl); - /** Provides view factories to be inflated in notification content. */ - @Provides - @ElementsIntoSet - @Named(NOTIF_REMOTEVIEWS_FACTORIES) - static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories( - FeatureFlags featureFlags, - PrecomputedTextViewFactory precomputedTextViewFactory, - BigPictureLayoutInflaterFactory bigPictureLayoutInflaterFactory, - CallLayoutSetDataAsyncFactory callLayoutSetDataAsyncFactory - ) { - final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>(); - replacementFactories.add(precomputedTextViewFactory); - if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) { - replacementFactories.add(bigPictureLayoutInflaterFactory); - } - if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) { - replacementFactories.add(callLayoutSetDataAsyncFactory); - } - return replacementFactories; - } + /** + * Provides notification remote view factory container + */ + @Binds + @SysUISingleton + public abstract NotifRemoteViewsFactoryContainer provideNotifRemoteViewsFactoryContainer( + NotifRemoteViewsFactoryContainerImpl containerImpl); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index 63194c37bbe1..8a56da3dafab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -797,18 +797,7 @@ constructor( } } if (dismissShade) { - if ( - shadeControllerLazy.get().isExpandedVisible && - !statusBarKeyguardViewManagerLazy.get().isBouncerShowing - ) { - shadeControllerLazy.get().animateCollapseShadeForcedDelayed() - } else { - // Do it after DismissAction has been processed to conserve the - // needed ordering. - postOnUiThread { - shadeControllerLazy.get().runPostCollapseRunnables() - } - } + shadeControllerLazy.get().collapseShadeForActivityStart() } return deferred } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index 20d1fff91443..a2d8d1579e3d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -28,6 +28,8 @@ import android.icu.lang.UCharacter; import android.icu.text.DateTimePatternGenerator; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.HandlerThread; import android.os.Parcelable; import android.os.SystemClock; import android.os.UserHandle; @@ -48,11 +50,11 @@ import android.widget.TextView; import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.FontSizeUtils; -import com.android.systemui.res.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.demomode.DemoModeCommandReceiver; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.StatusBarIconController; @@ -106,6 +108,7 @@ public class Clock extends TextView implements private final int mAmPmStyle; private boolean mShowSeconds; private Handler mSecondsHandler; + private HandlerThread mHandlerThread; // Fields to cache the width so the clock remains at an approximately constant width private int mCharsAtCurrentWidth = -1; @@ -146,6 +149,8 @@ public class Clock extends TextView implements } mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class); mUserTracker = Dependency.get(UserTracker.class); + mHandlerThread = new HandlerThread("Clock"); + mHandlerThread.start(); setIncludeFontPadding(false); } @@ -205,7 +210,8 @@ public class Clock extends TextView implements Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS, StatusBarIconController.ICON_HIDE_LIST); mCommandQueue.addCallback(this); - mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); + mUserTracker.addCallback(mUserChangedCallback, + new HandlerExecutor(mHandlerThread.getThreadHandler())); mCurrentUserId = mUserTracker.getUserId(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java index b7d8ee3943e3..a7440d6c200e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java @@ -21,6 +21,8 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.HandlerExecutor; +import android.os.HandlerThread; import android.os.UserHandle; import androidx.annotation.NonNull; @@ -51,6 +53,7 @@ public class NextAlarmControllerImpl extends BroadcastReceiver private final UserTracker mUserTracker; private AlarmManager mAlarmManager; private AlarmManager.AlarmClockInfo mNextAlarm; + private HandlerThread mHandlerThread; private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { @@ -75,7 +78,10 @@ public class NextAlarmControllerImpl extends BroadcastReceiver IntentFilter filter = new IntentFilter(); filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); broadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL); - mUserTracker.addCallback(mUserChangedCallback, mainExecutor); + mHandlerThread = new HandlerThread("NextAlarmControllerImpl"); + mHandlerThread.start(); + mUserTracker.addCallback(mUserChangedCallback, + new HandlerExecutor(mHandlerThread.getThreadHandler())); updateNextAlarm(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java index 9f4a90658b2e..6a6efbc11362 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java @@ -157,7 +157,7 @@ public class SecurityControllerImpl implements SecurityController { // TODO: re-register network callback on user change. mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback); onUserSwitched(mUserTracker.getUserId()); - mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); + mUserTracker.addCallback(mUserChangedCallback, mBgExecutor); } public void dump(PrintWriter pw, String[] args) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java index 2ed9d1548007..0bc0e88114a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java @@ -36,9 +36,9 @@ import androidx.annotation.NonNull; import com.android.internal.util.UserIcons; import com.android.settingslib.drawable.UserIconDrawable; -import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import java.util.ArrayList; @@ -66,11 +66,11 @@ public class UserInfoControllerImpl implements UserInfoController { /** */ @Inject - public UserInfoControllerImpl(Context context, @Main Executor mainExecutor, + public UserInfoControllerImpl(Context context, @Background Executor bgExecutor, UserTracker userTracker) { mContext = context; mUserTracker = userTracker; - mUserTracker.addCallback(mUserChangedCallback, mainExecutor); + mUserTracker.addCallback(mUserChangedCallback, bgExecutor); IntentFilter profileFilter = new IntentFilter(); profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index df210b073e77..f0b49307aad5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -29,6 +29,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.HandlerThread; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; @@ -81,6 +82,7 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { private volatile int mZenMode; private long mZenUpdateTime; private NotificationManager.Policy mConsolidatedNotificationPolicy; + private HandlerThread mHandlerThread; private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { @@ -133,6 +135,8 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { } } }; + mHandlerThread = new HandlerThread("ZenModeControllerImpl"); + mHandlerThread.start(); mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver); updateZenMode(getModeSettingValueFromProvider()); @@ -143,7 +147,8 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { mSetupObserver = new SetupObserver(handler); mSetupObserver.register(); mUserManager = context.getSystemService(UserManager.class); - mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(handler)); + mUserTracker.addCallback(mUserChangedCallback, + new HandlerExecutor(mHandlerThread.getThreadHandler())); // This registers the alarm broadcast receiver for the current user mUserChangedCallback.onUserChanged(getCurrentUser(), context); diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 2b9ad50c1257..77518db9184c 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -480,7 +480,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { return; } - mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor); + mUserTracker.addCallback(mUserTrackerCallback, mBgExecutor); mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java index 550a65c01bfc..f5b4d17ae7d3 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java @@ -26,6 +26,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.HandlerThread; import android.os.Looper; import android.os.UserManager; import android.provider.Settings; @@ -38,11 +39,11 @@ import androidx.annotation.WorkerThread; import com.android.internal.util.ArrayUtils; import com.android.systemui.DejankUtils; -import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.qs.QSHost; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.SystemUIDialog; @@ -98,6 +99,7 @@ public class TunerServiceImpl extends TunerService { private UserTracker.Callback mCurrentUserTracker; private UserTracker mUserTracker; private final ComponentName mTunerComponent; + private HandlerThread mHandlerThread; /** */ @@ -117,7 +119,8 @@ public class TunerServiceImpl extends TunerService { mDemoModeController = demoModeController; mUserTracker = userTracker; mTunerComponent = new ComponentName(mContext, TunerActivity.class); - + mHandlerThread = new HandlerThread("TunerServiceImpl"); + mHandlerThread.start(); for (UserInfo user : UserManager.get(mContext).getUsers()) { mCurrentUser = user.getUserHandle().getIdentifier(); if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) { @@ -135,7 +138,7 @@ public class TunerServiceImpl extends TunerService { } }; mUserTracker.addCallback(mCurrentUserTracker, - new HandlerExecutor(mainHandler)); + new HandlerExecutor(mHandlerThread.getThreadHandler())); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index cf76c0d2e696..74e133923378 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -190,7 +190,7 @@ constructor( } } - tracker.addCallback(callback, mainDispatcher.asExecutor()) + tracker.addCallback(callback, backgroundDispatcher.asExecutor()) send(currentSelectionStatus) awaitClose { tracker.removeCallback(callback) } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 1e801aeb5a29..7c6ad233d853 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -32,6 +32,8 @@ import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; +import android.os.HandlerExecutor; +import android.os.HandlerThread; import android.os.IBinder; import android.util.Log; import android.view.Display; @@ -125,6 +127,7 @@ public final class WMShell implements private final DisplayTracker mDisplayTracker; private final NoteTaskInitializer mNoteTaskInitializer; private final Executor mSysUiMainExecutor; + private HandlerThread mHandlerThread; // Listeners and callbacks. Note that we prefer member variable over anonymous class here to // avoid the situation that some implementations, like KeyguardUpdateMonitor, use WeakReference @@ -206,6 +209,8 @@ public final class WMShell implements mDisplayTracker = displayTracker; mNoteTaskInitializer = noteTaskInitializer; mSysUiMainExecutor = sysUiMainExecutor; + mHandlerThread = new HandlerThread("WMShell"); + mHandlerThread.start(); } @Override @@ -219,7 +224,8 @@ public final class WMShell implements mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); // Subscribe to user changes - mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); + mUserTracker.addCallback(mUserChangedCallback, + new HandlerExecutor(mHandlerThread.getThreadHandler())); mCommandQueue.addCallback(this); mPipOptional.ifPresent(this::initPip); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index d06457ba86ab..be06cc5d3d1d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -121,7 +121,6 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated; import com.android.keyguard.logging.KeyguardUpdateMonitorLogger; import com.android.settingslib.fuelgauge.BatteryStatus; -import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; @@ -930,7 +929,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void trustAgentHasTrust() { // WHEN user has trust - givenSelectedUserCanSkipBouncerFromTrustedState(); + mKeyguardUpdateMonitor.onTrustChanged(true, true, + mSelectedUserInteractor.getSelectedUserId(), 0, null); // THEN user is considered as "having trust" and bouncer can be skipped Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust( @@ -954,7 +954,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void trustAgentHasTrust_fingerprintLockout() { // GIVEN user has trust - givenSelectedUserCanSkipBouncerFromTrustedState(); + mKeyguardUpdateMonitor.onTrustChanged(true, true, + mSelectedUserInteractor.getSelectedUserId(), 0, null); Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust( mSelectedUserInteractor.getSelectedUserId())); @@ -2015,43 +2016,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void runFpDetectFlagDisabled_sideFps_keyguardDismissible_fingerprintAuthenticateRuns() { - mSetFlagsRule.disableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD); - - // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks) - // will trigger updateBiometricListeningState(); - clearInvocations(mFingerprintManager); - mKeyguardUpdateMonitor.resetBiometricListeningState(); - - // GIVEN the user can skip the bouncer - givenSelectedUserCanSkipBouncerFromTrustedState(); - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); - mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */); - mTestableLooper.processAllMessages(); - - // WHEN verify authenticate runs - verifyFingerprintAuthenticateCall(); - } - - @Test - public void sideFps_keyguardDismissible_fingerprintDetectRuns() { - mSetFlagsRule.enableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD); - // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks) - // will trigger updateBiometricListeningState(); - clearInvocations(mFingerprintManager); - mKeyguardUpdateMonitor.resetBiometricListeningState(); - - // GIVEN the user can skip the bouncer - givenSelectedUserCanSkipBouncerFromTrustedState(); - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); - mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */); - mTestableLooper.processAllMessages(); - - // WHEN verify detect runs - verifyFingerprintDetectCall(); - } - - @Test public void testFingerprintSensorProperties() throws RemoteException { mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered( new ArrayList<>()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt index e0c6bbad5635..0ba9abe2d36c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt @@ -32,12 +32,16 @@ import com.android.internal.statusbar.IStatusBarService import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.QuickSettingsController import com.android.systemui.shade.ShadeController @@ -59,7 +63,6 @@ import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import org.junit.Before import org.junit.Rule @@ -76,7 +79,8 @@ import org.mockito.junit.MockitoJUnit @RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) class BackActionInteractorTest : SysuiTestCase() { - private val testScope = TestScope() + private val kosmos = Kosmos() + private val testScope = kosmos.testScope private val executor = FakeExecutor(FakeSystemClock()) @JvmField @Rule var mockitoRule = MockitoJUnit.rule() @@ -105,6 +109,8 @@ class BackActionInteractorTest : SysuiTestCase() { headsUpManager, powerInteractor, activeNotificationsInteractor, + kosmos.sceneContainerFlags, + kosmos::sceneInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 8127bb1f0465..6a9c88151dd0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -1226,6 +1226,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @Test fun descriptionOverriddenByContentView() = runGenericTest(contentView = promptContentView, description = "test description") { + mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) val contentView by collectLastValue(viewModel.contentView) val description by collectLastValue(viewModel.description) @@ -1236,6 +1237,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @Test fun descriptionWithoutContentView() = runGenericTest(description = "test description") { + mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) val contentView by collectLastValue(viewModel.contentView) val description by collectLastValue(viewModel.description) diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt index e4432f3038bc..0636831c7c66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt @@ -30,13 +30,11 @@ import android.os.UserHandle import android.permission.PermissionGroupUsage import android.permission.PermissionManager import android.testing.AndroidTestingRunner -import android.view.View import android.widget.LinearLayout import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator -import com.android.systemui.animation.LaunchableView import com.android.systemui.appops.AppOpsController import com.android.systemui.plugins.ActivityStarter import com.android.systemui.privacy.logging.PrivacyLogger @@ -206,10 +204,7 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testShowDialogShowsDialogWithView() { val parent = LinearLayout(context) - val view = - object : View(context), LaunchableView { - override fun setShouldBlockVisibilityChanges(block: Boolean) {} - } + val view = OngoingPrivacyChip(context) parent.addView(view) val usage = createMockPermGroupUsage() `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt index fa02e8cb3e54..f98b68f2309b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt @@ -173,7 +173,7 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { captor.value.onClick(privacyChip) verify(privacyDialogController).showDialog(any(Context::class.java)) verify(privacyDialogControllerV2, never()) - .showDialog(any(Context::class.java), any(View::class.java)) + .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java)) } @Test @@ -186,7 +186,7 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { captor.value.onClick(privacyChip) verify(privacyDialogController).showDialog(any(Context::class.java)) verify(privacyDialogControllerV2, never()) - .showDialog(any(Context::class.java), any(View::class.java)) + .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java)) } @Test @@ -207,7 +207,7 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { captor.value.onClick(privacyChip) verify(privacyDialogController, never()).showDialog(any(Context::class.java)) verify(privacyDialogControllerV2, never()) - .showDialog(any(Context::class.java), any(View::class.java)) + .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt index c7479fd50db1..1ed8c3cdf0ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.recordissue.RecordIssueDialogDelegate import com.android.systemui.res.R +import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.KeyguardDismissUtil import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController @@ -65,6 +66,7 @@ class RecordIssueTileTest : SysuiTestCase() { @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var dialogLauncherAnimator: DialogLaunchAnimator + @Mock private lateinit var userContextProvider: UserContextProvider @Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory @Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate @Mock private lateinit var dialog: SystemUIDialog @@ -94,6 +96,7 @@ class RecordIssueTileTest : SysuiTestCase() { keyguardDismissUtil, keyguardStateController, dialogLauncherAnimator, + userContextProvider, delegateFactory, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java index b24b8773d600..c0ef50fa9072 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -1069,6 +1069,22 @@ public class InternetDialogControllerTest extends SysuiTestCase { assertThat(mInternetDialogController.mCallback).isNull(); } + @Test + public void hasActiveSubId_activeSubIdListIsEmpty_returnFalse() { + when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{}); + mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged(); + + assertThat(mInternetDialogController.hasActiveSubId()).isFalse(); + } + + @Test + public void hasActiveSubId_activeSubIdListNotEmpty_returnTrue() { + when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID}); + mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged(); + + assertThat(mInternetDialogController.hasActiveSubId()).isTrue(); + } + private String getResourcesString(String name) { return mContext.getResources().getString(getResourcesId(name)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index 45f68694e378..a6e240b1b701 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -22,6 +22,7 @@ import android.testing.TestableLooper import android.testing.ViewUtils import android.view.MotionEvent import android.view.View +import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalRepository @@ -61,6 +62,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Mock private lateinit var shadeInteractor: ShadeInteractor @Mock private lateinit var powerManager: PowerManager + private lateinit var parentView: FrameLayout private lateinit var containerView: View private lateinit var testableLooper: TestableLooper @@ -225,15 +227,38 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() } + @Test + fun onTouchEvent_containerViewDisposed_doesNotIntercept() { + // Communal is open. + communalRepository.setDesiredScene(CommunalSceneKey.Communal) + + initAndAttachContainerView() + testableLooper.processAllMessages() + + // Touch events are intercepted. + assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue() + + // Container view disposed. + underTest.disposeView() + + // Touch events are not intercepted. + assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() + } + private fun initAndAttachContainerView() { containerView = View(context) + + parentView = FrameLayout(context) + parentView.addView(containerView) + // Make view clickable so that dispatchTouchEvent returns true. containerView.isClickable = true underTest.initView(containerView) // Attach the view so that flows start collecting. - ViewUtils.attachView(containerView) + ViewUtils.attachView(parentView) // Give the view a size so that determining if a touch starts at the right edge works. + parentView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT) containerView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index a11839c56b0f..6681ceee3d09 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -74,10 +74,12 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor @@ -480,6 +482,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { } @Test + @Ignore("b/321332798") fun setsUpCommunalHubLayout_whenFlagEnabled() { if (!isComposeAvailable()) { return @@ -511,6 +514,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { } whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(false) + whenever(mGlanceableHubContainerController.enabledState()) + .thenReturn(MutableStateFlow(false)) val mockCommunalPlaceholder = mock(View::class.java) val fakeViewIndex = 20 @@ -520,8 +525,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { underTest.setupCommunalHubLayout() - // No adding or removing of views occurs. - verify(view, times(0)).removeView(mockCommunalPlaceholder) + // No adding of views occurs. verify(view, times(0)).addView(any(), eq(fakeViewIndex)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt index c4911a41b4a7..cc79ca4efaa1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt @@ -22,15 +22,18 @@ import android.view.Display import android.view.WindowManager import androidx.test.filters.SmallTest import com.android.internal.statusbar.IStatusBarService -import com.android.keyguard.TestScopeProvider import com.android.systemui.SysuiTestCase import com.android.systemui.assist.AssistManager import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope import com.android.systemui.log.LogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository @@ -64,6 +67,8 @@ class ShadeControllerImplTest : SysuiTestCase() { private val executor = FakeExecutor(FakeSystemClock()) private val testDispatcher = StandardTestDispatcher() private val activeNotificationsRepository = ActiveNotificationListRepository() + private val kosmos = Kosmos() + private val testScope = kosmos.testScope @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var keyguardStateController: KeyguardStateController @@ -84,12 +89,14 @@ class ShadeControllerImplTest : SysuiTestCase() { private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy { WindowRootViewVisibilityInteractor( - TestScopeProvider.getTestScope(), + testScope, WindowRootViewVisibilityRepository(iStatusBarService, executor), FakeKeyguardRepository(), headsUpManager, PowerInteractorFactory.create().powerInteractor, - ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher) + ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher), + kosmos.sceneContainerFlags, + kosmos::sceneInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt index 65697b736cbd..36f643ab9cca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt @@ -24,7 +24,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotifPipeline @@ -53,8 +52,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @@ -78,28 +77,22 @@ class ConversationCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: ConversationCoordinator - private val featureFlags = FakeFeatureFlagsClassic() - @Before fun setUp() { MockitoAnnotations.initMocks(this) - coordinator = ConversationCoordinator( - peopleNotificationIdentifier, - conversationIconManager, - HighPriorityProvider( + coordinator = + ConversationCoordinator( peopleNotificationIdentifier, - GroupMembershipManagerImpl(featureFlags) - ), - headerController - ) + conversationIconManager, + HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()), + headerController + ) whenever(channel.isImportantConversation).thenReturn(true) coordinator.attach(pipeline) // capture arguments: - promoter = withArgCaptor { - verify(pipeline).addPromoter(capture()) - } + promoter = withArgCaptor { verify(pipeline).addPromoter(capture()) } beforeRenderListListener = withArgCaptor { verify(pipeline).addOnBeforeRenderListListener(capture()) } @@ -111,10 +104,10 @@ class ConversationCoordinatorTest : SysuiTestCase() { entry = NotificationEntryBuilder().setChannel(channel).build() val section = NotifSection(peopleAlertingSectioner, 0) - entryA = NotificationEntryBuilder().setChannel(channel) - .setSection(section).setTag("A").build() - entryB = NotificationEntryBuilder().setChannel(channel) - .setSection(section).setTag("B").build() + entryA = + NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("A").build() + entryB = + NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("B").build() } @Test @@ -129,11 +122,12 @@ class ConversationCoordinatorTest : SysuiTestCase() { val altChildA = NotificationEntryBuilder().setTag("A").build() val altChildB = NotificationEntryBuilder().setTag("B").build() val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build() - val groupEntry = GroupEntryBuilder() - .setParent(GroupEntry.ROOT_ENTRY) - .setSummary(summary) - .setChildren(listOf(entry, altChildA, altChildB)) - .build() + val groupEntry = + GroupEntryBuilder() + .setParent(GroupEntry.ROOT_ENTRY) + .setSummary(summary) + .setChildren(listOf(entry, altChildA, altChildB)) + .build() assertTrue(promoter.shouldPromoteToTopLevel(entry)) assertFalse(promoter.shouldPromoteToTopLevel(altChildA)) assertFalse(promoter.shouldPromoteToTopLevel(altChildB)) @@ -146,41 +140,42 @@ class ConversationCoordinatorTest : SysuiTestCase() { @Test fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() { // GIVEN - val alertingEntry = NotificationEntryBuilder().setChannel(channel) - .setImportance(IMPORTANCE_DEFAULT).build() + val alertingEntry = + NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_DEFAULT).build() whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry)) - .thenReturn(TYPE_PERSON) + .thenReturn(TYPE_PERSON) // put alerting people notifications in this section assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue() - } + } @Test fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() { // GIVEN - val silentEntry = NotificationEntryBuilder().setChannel(channel) - .setImportance(IMPORTANCE_LOW).build() + val silentEntry = + NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build() whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry)) - .thenReturn(TYPE_PERSON) + .thenReturn(TYPE_PERSON) // THEN put silent people notifications in this section assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue() // People Alerting sectioning happens before the silent one. - // It claims high important conversations and rest of conversations will be considered as silent. + // It claims high important conversations and rest of conversations will be considered as + // silent. assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isFalse() } @Test fun testNotInPeopleSection() { // GIVEN - val entry = NotificationEntryBuilder().setChannel(channel) - .setImportance(IMPORTANCE_LOW).build() - val importantEntry = NotificationEntryBuilder().setChannel(channel) - .setImportance(IMPORTANCE_HIGH).build() + val entry = + NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build() + val importantEntry = + NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_HIGH).build() whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry)) - .thenReturn(TYPE_NON_PERSON) + .thenReturn(TYPE_NON_PERSON) whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry)) - .thenReturn(TYPE_NON_PERSON) + .thenReturn(TYPE_NON_PERSON) // THEN - only put people notification either silent or alerting assertThat(peopleSilentSectioner.isInSection(entry)).isFalse() @@ -190,19 +185,23 @@ class ConversationCoordinatorTest : SysuiTestCase() { @Test fun testInAlertingPeopleSectionWhenThereIsAnImportantChild() { // GIVEN - val altChildA = NotificationEntryBuilder().setTag("A") - .setImportance(IMPORTANCE_DEFAULT).build() - val altChildB = NotificationEntryBuilder().setTag("B") - .setImportance(IMPORTANCE_LOW).build() - val summary = NotificationEntryBuilder().setId(2) - .setImportance(IMPORTANCE_LOW).setChannel(channel).build() - val groupEntry = GroupEntryBuilder() + val altChildA = + NotificationEntryBuilder().setTag("A").setImportance(IMPORTANCE_DEFAULT).build() + val altChildB = NotificationEntryBuilder().setTag("B").setImportance(IMPORTANCE_LOW).build() + val summary = + NotificationEntryBuilder() + .setId(2) + .setImportance(IMPORTANCE_LOW) + .setChannel(channel) + .build() + val groupEntry = + GroupEntryBuilder() .setParent(GroupEntry.ROOT_ENTRY) .setSummary(summary) .setChildren(listOf(altChildA, altChildB)) .build() whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary)) - .thenReturn(TYPE_PERSON) + .thenReturn(TYPE_PERSON) // THEN assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index ff02ef3d4e62..b01281c4daa3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -42,9 +42,9 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceId; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; -import com.android.keyguard.TestScopeProvider; import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.domain.interactor.PowerInteractorFactory; import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository; @@ -110,7 +110,8 @@ public class NotificationLoggerTest extends SysuiTestCase { private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private NotificationPanelLoggerFake mNotificationPanelLoggerFake = new NotificationPanelLoggerFake(); - private final TestScope mTestScope = TestScopeProvider.getTestScope(); + private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); + private final TestScope mTestScope = mKosmos.getTestScope(); private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); private final PowerInteractor mPowerInteractor = PowerInteractorFactory.create().getPowerInteractor(); @@ -133,7 +134,9 @@ public class NotificationLoggerTest extends SysuiTestCase { mKeyguardRepository, mHeadsUpManager, mPowerInteractor, - mActiveNotificationsInteractor); + mActiveNotificationsInteractor, + mKosmos.getFakeSceneContainerFlags(), + () -> mKosmos.getSceneInteractor()); mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); mEntry = new NotificationEntryBuilder() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt index 3f7fc979b1e3..fd4192151c57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt @@ -62,7 +62,7 @@ class NotifLayoutInflaterFactoryTest : SysuiTestCase() { fun onCreateView_noMatchingViewForName_returnNull() { // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts val layoutType = FLAG_CONTENT_VIEW_EXPANDED - inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies) + inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies) // WHEN we try to inflate an ImageView for the expanded layout val createdView = inflaterFactory.onCreateView("ImageView", context, attrs) @@ -78,7 +78,7 @@ class NotifLayoutInflaterFactoryTest : SysuiTestCase() { fun onCreateView_noMatchingViewForLayoutType_returnNull() { // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts val layoutType = FLAG_CONTENT_VIEW_HEADS_UP - inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies) + inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies) // WHEN we try to inflate a TextView for the heads-up layout val createdView = inflaterFactory.onCreateView("TextView", context, attrs) @@ -94,7 +94,7 @@ class NotifLayoutInflaterFactoryTest : SysuiTestCase() { fun onCreateView_matchingViews_returnReplacementView() { // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts val layoutType = FLAG_CONTENT_VIEW_EXPANDED - inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies) + inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies) // WHEN we try to inflate a TextView for the expanded layout val createdView = inflaterFactory.onCreateView("TextView", context, attrs) @@ -110,7 +110,7 @@ class NotifLayoutInflaterFactoryTest : SysuiTestCase() { // GIVEN we have two factories that replaces TextViews in expanded layouts val layoutType = FLAG_CONTENT_VIEW_EXPANDED inflaterFactory = - NotifLayoutInflaterFactory( + createNotifLayoutInflaterFactory( row, layoutType, setOf( @@ -147,4 +147,18 @@ class NotifLayoutInflaterFactoryTest : SysuiTestCase() { null } } + + private fun createNotifLayoutInflaterFactory( + row: ExpandableNotificationRow, + layoutType: Int, + notifRemoteViewsFactoryContainer: Set<NotifRemoteViewsFactory> + ) = + NotifLayoutInflaterFactory( + row, + layoutType, + object : NotifRemoteViewsFactoryContainer { + override val factories: Set<NotifRemoteViewsFactory> = + notifRemoteViewsFactoryContainer + } + ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 71613edb8737..65491937c285 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -69,9 +69,9 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.internal.statusbar.IStatusBarService; -import com.android.keyguard.TestScopeProvider; import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.people.widget.PeopleSpaceWidgetManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; @@ -124,7 +124,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { private NotificationChannel mTestNotificationChannel = new NotificationChannel( TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); - private TestScope mTestScope = TestScopeProvider.getTestScope(); + private KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); + private TestScope mTestScope = mKosmos.getTestScope(); private JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); private TestableLooper mTestableLooper; @@ -182,7 +183,10 @@ public class NotificationGutsManagerTest extends SysuiTestCase { new FakeKeyguardRepository(), mHeadsUpManager, PowerInteractorFactory.create().getPowerInteractor(), - mActiveNotificationsInteractor); + mActiveNotificationsInteractor, + mKosmos.getFakeSceneContainerFlags(), + () -> mKosmos.getSceneInteractor() + ); mGutsManager = new NotificationGutsManager( mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt new file mode 100644 index 000000000000..446b9d0bd434 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt @@ -0,0 +1,631 @@ +/* + * 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.row + +import android.R +import android.app.AppOpsManager +import android.app.INotificationManager +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.ShortcutManager +import android.content.pm.launcherApps +import android.graphics.Color +import android.os.Binder +import android.os.Handler +import android.os.userManager +import android.provider.Settings +import android.service.notification.NotificationListenerService.Ranking +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.TestableLooper.RunWithLooper +import android.util.ArraySet +import android.view.View +import android.view.accessibility.accessibilityManager +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.metricsLogger +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.internal.statusbar.statusBarService +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.kosmos.testScope +import com.android.systemui.people.widget.PeopleSpaceWidgetManager +import com.android.systemui.plugins.activityStarter +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.power.domain.interactor.PowerInteractorFactory.create +import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository +import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags +import com.android.systemui.scene.shared.model.ObservableTransitionState +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.settings.UserContextProvider +import com.android.systemui.shade.shadeControllerSceneImpl +import com.android.systemui.statusbar.NotificationEntryHelper +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.AssistantFeedbackController +import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier +import com.android.systemui.statusbar.notification.stack.NotificationListContainer +import com.android.systemui.statusbar.notificationLockscreenUserManager +import com.android.systemui.statusbar.policy.deviceProvisionedController +import com.android.systemui.statusbar.policy.headsUpManager +import com.android.systemui.testKosmos +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.kotlin.JavaAdapter +import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.wmshell.BubblesManager +import java.util.Optional +import junit.framework.Assert +import kotlin.test.assertEquals +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.runCurrent +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.invocation.InvocationOnMock + +/** Tests for [NotificationGutsManager] with the scene container enabled. */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotificationGutsManagerWithScenesTest : SysuiTestCase() { + private val testNotificationChannel = + NotificationChannel( + TEST_CHANNEL_ID, + TEST_CHANNEL_ID, + NotificationManager.IMPORTANCE_DEFAULT + ) + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val javaAdapter = JavaAdapter(testScope.backgroundScope) + private val executor = FakeExecutor(FakeSystemClock()) + private lateinit var testableLooper: TestableLooper + private lateinit var handler: Handler + private lateinit var helper: NotificationTestHelper + private lateinit var gutsManager: NotificationGutsManager + private lateinit var windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor + + private val metricsLogger = kosmos.metricsLogger + private val deviceProvisionedController = kosmos.deviceProvisionedController + private val accessibilityManager = kosmos.accessibilityManager + private val mBarService = kosmos.statusBarService + private val launcherApps = kosmos.launcherApps + private val shadeController = kosmos.shadeControllerSceneImpl + private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager + private val statusBarStateController = kosmos.statusBarStateController + private val headsUpManager = kosmos.headsUpManager + private val activityStarter = kosmos.activityStarter + private val userManager = kosmos.userManager + private val activeNotificationsInteractor = kosmos.activeNotificationsInteractor + private val sceneInteractor = kosmos.sceneInteractor + + @Mock private lateinit var onUserInteractionCallback: OnUserInteractionCallback + @Mock private lateinit var presenter: NotificationPresenter + @Mock private lateinit var notificationActivityStarter: NotificationActivityStarter + @Mock private lateinit var notificationListContainer: NotificationListContainer + @Mock + private lateinit var onSettingsClickListener: NotificationGutsManager.OnSettingsClickListener + @Mock private lateinit var highPriorityProvider: HighPriorityProvider + @Mock private lateinit var notificationManager: INotificationManager + @Mock private lateinit var shortcutManager: ShortcutManager + @Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController + @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier + @Mock private lateinit var contextTracker: UserContextProvider + @Mock private lateinit var bubblesManager: BubblesManager + @Mock private lateinit var peopleSpaceWidgetManager: PeopleSpaceWidgetManager + @Mock private lateinit var assistantFeedbackController: AssistantFeedbackController + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + val sceneContainerFlags = kosmos.fakeSceneContainerFlags + sceneContainerFlags.enabled = true + testableLooper = TestableLooper.get(this) + allowTestableLooperAsMainThread() + handler = Handler.createAsync(testableLooper.getLooper()) + helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)) + Mockito.`when`(accessibilityManager.isTouchExplorationEnabled).thenReturn(false) + windowRootViewVisibilityInteractor = + WindowRootViewVisibilityInteractor( + testScope.backgroundScope, + WindowRootViewVisibilityRepository(mBarService, executor), + FakeKeyguardRepository(), + headsUpManager, + create().powerInteractor, + activeNotificationsInteractor, + sceneContainerFlags, + { sceneInteractor }, + ) + gutsManager = + NotificationGutsManager( + mContext, + handler, + handler, + javaAdapter, + accessibilityManager, + highPriorityProvider, + notificationManager, + userManager, + peopleSpaceWidgetManager, + launcherApps, + shortcutManager, + channelEditorDialogController, + contextTracker, + assistantFeedbackController, + Optional.of(bubblesManager), + UiEventLoggerFake(), + onUserInteractionCallback, + shadeController, + windowRootViewVisibilityInteractor, + notificationLockscreenUserManager, + statusBarStateController, + mBarService, + deviceProvisionedController, + metricsLogger, + headsUpManager, + activityStarter + ) + gutsManager.setUpWithPresenter( + presenter, + notificationListContainer, + onSettingsClickListener + ) + gutsManager.setNotificationActivityStarter(notificationActivityStarter) + gutsManager.start() + } + + @Test + fun testOpenAndCloseGuts() { + val guts = Mockito.spy(NotificationGuts(mContext)) + Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock + -> + handler.post((invocation.arguments[0] as Runnable)) + null + } + + // Test doesn't support animation since the guts view is not attached. + Mockito.doNothing() + .`when`(guts) + .openControls( + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyBoolean(), + ArgumentMatchers.any(Runnable::class.java) + ) + val realRow = createTestNotificationRow() + val menuItem = createTestMenuItem(realRow) + val row = Mockito.spy(realRow) + Mockito.`when`(row!!.windowToken).thenReturn(Binder()) + Mockito.`when`(row.guts).thenReturn(guts) + Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem)) + assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong()) + testableLooper.processAllMessages() + verify(guts) + .openControls( + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyBoolean(), + ArgumentMatchers.any(Runnable::class.java) + ) + verify(headsUpManager).setGutsShown(realRow!!.entry, true) + assertEquals(View.VISIBLE.toLong(), guts.visibility.toLong()) + gutsManager.closeAndSaveGuts(false, false, true, 0, 0, false) + verify(guts) + .closeControls( + ArgumentMatchers.anyBoolean(), + ArgumentMatchers.anyBoolean(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyBoolean() + ) + verify(row, Mockito.times(1)).setGutsView(ArgumentMatchers.any()) + testableLooper.processAllMessages() + verify(headsUpManager).setGutsShown(realRow.entry, false) + } + + @Test + fun testLockscreenShadeVisible_visible_gutsNotClosed() { + // First, start out lockscreen or shade as not visible + setIsLockscreenOrShadeVisible(false) + testScope.testScheduler.runCurrent() + val guts = Mockito.mock(NotificationGuts::class.java) + gutsManager.exposedGuts = guts + + // WHEN the lockscreen or shade becomes visible + setIsLockscreenOrShadeVisible(true) + testScope.testScheduler.runCurrent() + + // THEN the guts are not closed + verify(guts, Mockito.never()).removeCallbacks(ArgumentMatchers.any()) + verify(guts, Mockito.never()) + .closeControls( + ArgumentMatchers.anyBoolean(), + ArgumentMatchers.anyBoolean(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyBoolean() + ) + } + + @Test + fun testLockscreenShadeVisible_notVisible_gutsClosed() { + // First, start out lockscreen or shade as visible + setIsLockscreenOrShadeVisible(true) + testScope.testScheduler.runCurrent() + val guts = Mockito.mock(NotificationGuts::class.java) + gutsManager.exposedGuts = guts + + // WHEN the lockscreen or shade is no longer visible + setIsLockscreenOrShadeVisible(false) + testScope.testScheduler.runCurrent() + + // THEN the guts are closed + verify(guts).removeCallbacks(ArgumentMatchers.any()) + verify(guts) + .closeControls( + /* leavebehinds= */ ArgumentMatchers.eq(true), + /* controls= */ ArgumentMatchers.eq(true), + /* x= */ ArgumentMatchers.anyInt(), + /* y= */ ArgumentMatchers.anyInt(), + /* force= */ ArgumentMatchers.eq(true) + ) + } + + @Test + fun testLockscreenShadeVisible_notVisible_listContainerReset() { + // First, start out lockscreen or shade as visible + setIsLockscreenOrShadeVisible(true) + testScope.testScheduler.runCurrent() + + // WHEN the lockscreen or shade is no longer visible + setIsLockscreenOrShadeVisible(false) + testScope.testScheduler.runCurrent() + + // THEN the list container is reset + verify(notificationListContainer) + .resetExposedMenuView(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean()) + } + + @Test + fun testChangeDensityOrFontScale() { + val guts = Mockito.spy(NotificationGuts(mContext)) + Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock + -> + handler.post((invocation.arguments[0] as Runnable)) + null + } + + // Test doesn't support animation since the guts view is not attached. + Mockito.doNothing() + .`when`(guts) + .openControls( + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyBoolean(), + ArgumentMatchers.any(Runnable::class.java) + ) + val realRow = createTestNotificationRow() + val menuItem = createTestMenuItem(realRow) + val row = Mockito.spy(realRow) + Mockito.`when`(row!!.windowToken).thenReturn(Binder()) + Mockito.`when`(row.guts).thenReturn(guts) + Mockito.doNothing().`when`(row).ensureGutsInflated() + val realEntry = realRow!!.entry + val entry = Mockito.spy(realEntry) + Mockito.`when`(entry.row).thenReturn(row) + Mockito.`when`(entry.getGuts()).thenReturn(guts) + Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem)) + testableLooper.processAllMessages() + verify(guts) + .openControls( + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyBoolean(), + ArgumentMatchers.any(Runnable::class.java) + ) + + // called once by mGutsManager.bindGuts() in mGutsManager.openGuts() + verify(row).setGutsView(ArgumentMatchers.any()) + row.onDensityOrFontScaleChanged() + gutsManager.onDensityOrFontScaleChanged(entry) + testableLooper.processAllMessages() + gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false) + verify(guts) + .closeControls( + ArgumentMatchers.anyBoolean(), + ArgumentMatchers.anyBoolean(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyBoolean() + ) + + // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged() + verify(row, Mockito.times(2)).setGutsView(ArgumentMatchers.any()) + } + + @Test + fun testAppOpsSettingsIntent_camera() { + val ops = ArraySet<Int>() + ops.add(AppOpsManager.OP_CAMERA) + gutsManager.startAppOpsSettingsActivity("", 0, ops, null) + val captor = ArgumentCaptor.forClass(Intent::class.java) + verify(notificationActivityStarter, Mockito.times(1)) + .startNotificationGutsIntent( + captor.capture(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.any() + ) + assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action) + } + + @Test + fun testAppOpsSettingsIntent_mic() { + val ops = ArraySet<Int>() + ops.add(AppOpsManager.OP_RECORD_AUDIO) + gutsManager.startAppOpsSettingsActivity("", 0, ops, null) + val captor = ArgumentCaptor.forClass(Intent::class.java) + verify(notificationActivityStarter, Mockito.times(1)) + .startNotificationGutsIntent( + captor.capture(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.any() + ) + assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action) + } + + @Test + fun testAppOpsSettingsIntent_camera_mic() { + val ops = ArraySet<Int>() + ops.add(AppOpsManager.OP_CAMERA) + ops.add(AppOpsManager.OP_RECORD_AUDIO) + gutsManager.startAppOpsSettingsActivity("", 0, ops, null) + val captor = ArgumentCaptor.forClass(Intent::class.java) + verify(notificationActivityStarter, Mockito.times(1)) + .startNotificationGutsIntent( + captor.capture(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.any() + ) + assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action) + } + + @Test + fun testAppOpsSettingsIntent_overlay() { + val ops = ArraySet<Int>() + ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) + gutsManager.startAppOpsSettingsActivity("", 0, ops, null) + val captor = ArgumentCaptor.forClass(Intent::class.java) + verify(notificationActivityStarter, Mockito.times(1)) + .startNotificationGutsIntent( + captor.capture(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.any() + ) + assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.value.action) + } + + @Test + fun testAppOpsSettingsIntent_camera_mic_overlay() { + val ops = ArraySet<Int>() + ops.add(AppOpsManager.OP_CAMERA) + ops.add(AppOpsManager.OP_RECORD_AUDIO) + ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) + gutsManager.startAppOpsSettingsActivity("", 0, ops, null) + val captor = ArgumentCaptor.forClass(Intent::class.java) + verify(notificationActivityStarter, Mockito.times(1)) + .startNotificationGutsIntent( + captor.capture(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.any() + ) + assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action) + } + + @Test + fun testAppOpsSettingsIntent_camera_overlay() { + val ops = ArraySet<Int>() + ops.add(AppOpsManager.OP_CAMERA) + ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) + gutsManager.startAppOpsSettingsActivity("", 0, ops, null) + val captor = ArgumentCaptor.forClass(Intent::class.java) + verify(notificationActivityStarter, Mockito.times(1)) + .startNotificationGutsIntent( + captor.capture(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.any() + ) + assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action) + } + + @Test + fun testAppOpsSettingsIntent_mic_overlay() { + val ops = ArraySet<Int>() + ops.add(AppOpsManager.OP_RECORD_AUDIO) + ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW) + gutsManager.startAppOpsSettingsActivity("", 0, ops, null) + val captor = ArgumentCaptor.forClass(Intent::class.java) + verify(notificationActivityStarter, Mockito.times(1)) + .startNotificationGutsIntent( + captor.capture(), + ArgumentMatchers.anyInt(), + ArgumentMatchers.any() + ) + assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action) + } + + @Test + @Throws(Exception::class) + fun testInitializeNotificationInfoView_highPriority() { + val notificationInfoView = Mockito.mock(NotificationInfo::class.java) + val row = Mockito.spy(helper.createRow()) + val entry = row.entry + NotificationEntryHelper.modifyRanking(entry) + .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) + .setImportance(NotificationManager.IMPORTANCE_HIGH) + .build() + Mockito.`when`(row.getIsNonblockable()).thenReturn(false) + Mockito.`when`(highPriorityProvider.isHighPriority(entry)).thenReturn(true) + val statusBarNotification = entry.sbn + gutsManager.initializeNotificationInfo(row, notificationInfoView) + verify(notificationInfoView) + .bindNotification( + ArgumentMatchers.any(PackageManager::class.java), + ArgumentMatchers.any(INotificationManager::class.java), + ArgumentMatchers.eq(onUserInteractionCallback), + ArgumentMatchers.eq(channelEditorDialogController), + ArgumentMatchers.eq(statusBarNotification.packageName), + ArgumentMatchers.any(NotificationChannel::class.java), + ArgumentMatchers.eq(entry), + ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java), + ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java), + ArgumentMatchers.any(UiEventLogger::class.java), + ArgumentMatchers.eq(true), + ArgumentMatchers.eq(false), + ArgumentMatchers.eq(true), /* wasShownHighPriority */ + ArgumentMatchers.eq(assistantFeedbackController), + ArgumentMatchers.any(MetricsLogger::class.java) + ) + } + + @Test + @Throws(Exception::class) + fun testInitializeNotificationInfoView_PassesAlongProvisionedState() { + val notificationInfoView = Mockito.mock(NotificationInfo::class.java) + val row = Mockito.spy(helper.createRow()) + NotificationEntryHelper.modifyRanking(row.entry) + .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) + .build() + Mockito.`when`(row.getIsNonblockable()).thenReturn(false) + val statusBarNotification = row.entry.sbn + val entry = row.entry + gutsManager.initializeNotificationInfo(row, notificationInfoView) + verify(notificationInfoView) + .bindNotification( + ArgumentMatchers.any(PackageManager::class.java), + ArgumentMatchers.any(INotificationManager::class.java), + ArgumentMatchers.eq(onUserInteractionCallback), + ArgumentMatchers.eq(channelEditorDialogController), + ArgumentMatchers.eq(statusBarNotification.packageName), + ArgumentMatchers.any(NotificationChannel::class.java), + ArgumentMatchers.eq(entry), + ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java), + ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java), + ArgumentMatchers.any(UiEventLogger::class.java), + ArgumentMatchers.eq(true), + ArgumentMatchers.eq(false), + ArgumentMatchers.eq(false), /* wasShownHighPriority */ + ArgumentMatchers.eq(assistantFeedbackController), + ArgumentMatchers.any(MetricsLogger::class.java) + ) + } + + @Test + @Throws(Exception::class) + fun testInitializeNotificationInfoView_withInitialAction() { + val notificationInfoView = Mockito.mock(NotificationInfo::class.java) + val row = Mockito.spy(helper.createRow()) + NotificationEntryHelper.modifyRanking(row.entry) + .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) + .build() + Mockito.`when`(row.getIsNonblockable()).thenReturn(false) + val statusBarNotification = row.entry.sbn + val entry = row.entry + gutsManager.initializeNotificationInfo(row, notificationInfoView) + verify(notificationInfoView) + .bindNotification( + ArgumentMatchers.any(PackageManager::class.java), + ArgumentMatchers.any(INotificationManager::class.java), + ArgumentMatchers.eq(onUserInteractionCallback), + ArgumentMatchers.eq(channelEditorDialogController), + ArgumentMatchers.eq(statusBarNotification.packageName), + ArgumentMatchers.any(NotificationChannel::class.java), + ArgumentMatchers.eq(entry), + ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java), + ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java), + ArgumentMatchers.any(UiEventLogger::class.java), + ArgumentMatchers.eq(true), + ArgumentMatchers.eq(false), + ArgumentMatchers.eq(false), /* wasShownHighPriority */ + ArgumentMatchers.eq(assistantFeedbackController), + ArgumentMatchers.any(MetricsLogger::class.java) + ) + } + + private fun createTestNotificationRow(): ExpandableNotificationRow? { + val nb = + Notification.Builder(mContext, testNotificationChannel.id) + .setContentTitle("foo") + .setColorized(true) + .setColor(Color.RED) + .setFlag(Notification.FLAG_CAN_COLORIZE, true) + .setSmallIcon(R.drawable.sym_def_app_icon) + return try { + val row = helper.createRow(nb.build()) + NotificationEntryHelper.modifyRanking(row.entry) + .setChannel(testNotificationChannel) + .build() + row + } catch (e: Exception) { + org.junit.Assert.fail() + null + } + } + + private fun setIsLockscreenOrShadeVisible(isVisible: Boolean) { + val key = + if (isVisible) { + SceneKey.Lockscreen + } else { + SceneKey.Bouncer + } + sceneInteractor.changeScene(SceneModel(key), "test") + sceneInteractor.setTransitionState( + MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) + ) + testScope.runCurrent() + } + + private fun createTestMenuItem( + row: ExpandableNotificationRow? + ): NotificationMenuRowPlugin.MenuItem { + val menuRow: NotificationMenuRowPlugin = + NotificationMenuRow(mContext, peopleNotificationIdentifier) + menuRow.createMenu(row, row!!.entry.sbn) + val menuItem = menuRow.getLongpressMenuItem(mContext) + Assert.assertNotNull(menuItem) + return menuItem + } + + companion object { + private const val TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId" + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt index 0e0d4897d667..5b5819d649b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt @@ -125,22 +125,6 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { } @Test - fun satelliteManagerThrows_doesNotCrash() = - testScope.runTest { - setupDefaultRepo() - - whenever(satelliteManager.registerForNtnSignalStrengthChanged(any(), any())) - .thenThrow(SatelliteException(13)) - - val conn by collectLastValue(underTest.connectionState) - val strength by collectLastValue(underTest.signalStrength) - - // Flows have not emitted, we haven't crashed - assertThat(conn).isNull() - assertThat(strength).isNull() - } - - @Test fun connectionState_mapsFromSatelliteModemState() = testScope.runTest { setupDefaultRepo() diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index b58a41c89a4e..457acd214222 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -190,7 +190,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mWakefulnessLifecycle.dispatchFinishedWakingUp(); mThemeOverlayController.start(); - verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mMainExecutor)); + verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mBgExecutor)); verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null), eq(UserHandle.USER_ALL)); verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiver.capture(), any(), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt new file mode 100644 index 000000000000..e547da1b92dd --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.data.repository + +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow + +class FakeAccessibilityQsShortcutsRepository : AccessibilityQsShortcutsRepository { + + private val targetsPerUser = mutableMapOf<Int, MutableSharedFlow<Set<String>>>() + + override fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>> { + return getFlow(userId).asSharedFlow() + } + + /** + * Set the a11y qs shortcut targets. In real world, the A11y QS Shortcut targets are set by the + * Settings app not in SysUi + */ + suspend fun setA11yQsShortcutTargets(userId: Int, targets: Set<String>) { + getFlow(userId).emit(targets) + } + + private fun getFlow(userId: Int): MutableSharedFlow<Set<String>> = + targetsPerUser.getOrPut(userId) { MutableSharedFlow(replay = 1) } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt index 20fa545d54e0..cccd90832326 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt @@ -50,4 +50,11 @@ class FakeCommunalRepository( fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) { _isCommunalHubShowing.value = isCommunalHubShowing } + + private val _communalEnabledState: MutableStateFlow<Boolean> = MutableStateFlow(false) + override val communalEnabledState: StateFlow<Boolean> = _communalEnabledState + + fun setCommunalEnabledState(enabled: Boolean) { + _communalEnabledState.value = enabled + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt index 7cc5d6b6243a..e13fa5207b33 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt @@ -14,9 +14,66 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.shade +import android.view.WindowManager +import com.android.systemui.assist.AssistManager +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.log.LogBuffer +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.notification.row.NotificationGutsManager +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.windowRootViewVisibilityInteractor +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.policy.deviceProvisionedController +import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.util.mockito.mock +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.shadeControllerSceneImpl by + Kosmos.Fixture { + ShadeControllerSceneImpl( + scope = applicationCoroutineScope, + shadeInteractor = shadeInteractor, + sceneInteractor = sceneInteractor, + notificationStackScrollLayout = mock<NotificationStackScrollLayout>(), + deviceEntryInteractor = deviceEntryInteractor, + touchLog = mock<LogBuffer>(), + commandQueue = mock<CommandQueue>(), + statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>(), + notificationShadeWindowController = mock<NotificationShadeWindowController>(), + assistManagerLazy = { mock<AssistManager>() }, + ) + } -var Kosmos.shadeController by Kosmos.Fixture { mock<ShadeController>() } +val Kosmos.shadeControllerImpl by + Kosmos.Fixture { + ShadeControllerImpl( + mock<CommandQueue>(), + fakeExecutor, + mock<LogBuffer>(), + windowRootViewVisibilityInteractor, + mock<KeyguardStateController>(), + statusBarStateController, + statusBarKeyguardViewManager, + mock<StatusBarWindowController>(), + deviceProvisionedController, + mock<NotificationShadeWindowController>(), + mock<WindowManager>(), + { mock<ShadeViewController>() }, + { mock<AssistManager>() }, + { mock<NotificationGutsManager>() }, + ) + } +var Kosmos.shadeController: ShadeController by Kosmos.Fixture { shadeControllerImpl } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt index da956ec67696..da956ec67696 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java index dda7fadde2d7..4efcada96a14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java @@ -52,32 +52,38 @@ public class GroupEntryBuilder { return ge; } + /** Sets the group key. */ public GroupEntryBuilder setKey(String key) { mKey = key; return this; } + /** Sets the creation time. */ public GroupEntryBuilder setCreationTime(long creationTime) { mCreationTime = creationTime; return this; } + /** Sets the parent entry of the group. */ public GroupEntryBuilder setParent(@Nullable GroupEntry entry) { mParent = entry; return this; } + /** Sets the section the group belongs to. */ public GroupEntryBuilder setSection(@Nullable NotifSection section) { mNotifSection = section; return this; } + /** Sets the group summary. */ public GroupEntryBuilder setSummary( NotificationEntry summary) { mSummary = summary; return this; } + /** Sets the group children. */ public GroupEntryBuilder setChildren(List<NotificationEntry> children) { mChildren.clear(); mChildren.addAll(children); @@ -90,6 +96,7 @@ public class GroupEntryBuilder { return this; } + /** Get the group's internal children list. */ public static List<NotificationEntry> getRawChildren(GroupEntry groupEntry) { return groupEntry.getRawChildren(); } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt index d80ee758269f..cf800d04f816 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt @@ -23,6 +23,8 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.policy.headsUpManager @@ -34,5 +36,7 @@ val Kosmos.windowRootViewVisibilityInteractor by Fixture { headsUpManager = headsUpManager, powerInteractor = powerInteractor, activeNotificationsInteractor = activeNotificationsInteractor, + sceneInteractorProvider = { sceneInteractor }, + sceneContainerFlags = sceneContainerFlags, ) } diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt index f5e4af50fc29..e33fff117d41 100644 --- a/ravenwood/framework-minus-apex-ravenwood-policies.txt +++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt @@ -6,6 +6,9 @@ class :aidl stubclass # Keep all feature flag implementations class :feature_flags stubclass +# Keep all sysprops generated code implementations +class :sysprops stubclass + # Collections class android.util.ArrayMap stubclass class android.util.ArraySet stubclass @@ -112,6 +115,12 @@ class android.os.HandlerExecutor stubclass class android.os.PatternMatcher stubclass class android.os.ParcelUuid stubclass +# Logging related interfaces from modules-utils +class com.android.internal.logging.InstanceId stubclass +class com.android.internal.logging.InstanceIdSequence stubclass +class com.android.internal.logging.UiEvent stubclass +class com.android.internal.logging.UiEventLogger stubclass + # XML class com.android.internal.util.XmlPullParserWrapper stubclass class com.android.internal.util.XmlSerializerWrapper stubclass diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java index eacdc2f79254..91c522e82cce 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java @@ -19,8 +19,6 @@ package android.platform.test.ravenwood; import android.os.HandlerThread; import android.os.Looper; -import java.util.Objects; - public class RavenwoodRuleImpl { private static final String MAIN_THREAD_NAME = "RavenwoodMain"; @@ -31,6 +29,10 @@ public class RavenwoodRuleImpl { public static void init(RavenwoodRule rule) { android.os.Process.init$ravenwood(rule.mUid, rule.mPid); android.os.Binder.init$ravenwood(); + android.os.SystemProperties.init$ravenwood( + rule.mSystemProperties.getValues(), + rule.mSystemProperties.getKeyReadablePredicate(), + rule.mSystemProperties.getKeyWritablePredicate()); com.android.server.LocalServices.removeAllServicesForTest(); @@ -49,7 +51,8 @@ public class RavenwoodRuleImpl { com.android.server.LocalServices.removeAllServicesForTest(); - android.os.Process.reset$ravenwood(); + android.os.SystemProperties.reset$ravenwood(); android.os.Binder.reset$ravenwood(); + android.os.Process.reset$ravenwood(); } } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index 53da8ba14a2c..dd442f08321f 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -62,6 +62,8 @@ public class RavenwoodRule implements TestRule { boolean mProvideMainThread = false; + final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties(); + public RavenwoodRule() { } @@ -98,6 +100,40 @@ public class RavenwoodRule implements TestRule { return this; } + /** + * Configure the given system property as immutable for the duration of the test. + * Read access to the key is allowed, and write access will fail. When {@code value} is + * {@code null}, the value is left as undefined. + * + * All properties in the {@code debug.*} namespace are automatically mutable, with no + * developer action required. + * + * Has no effect under non-Ravenwood environments. + */ + public Builder setSystemPropertyImmutable(/* @NonNull */ String key, + /* @Nullable */ Object value) { + mRule.mSystemProperties.setValue(key, value); + mRule.mSystemProperties.setAccessReadOnly(key); + return this; + } + + /** + * Configure the given system property as mutable for the duration of the test. + * Both read and write access to the key is allowed, and its value will be reset between + * each test. When {@code value} is {@code null}, the value is left as undefined. + * + * All properties in the {@code debug.*} namespace are automatically mutable, with no + * developer action required. + * + * Has no effect under non-Ravenwood environments. + */ + public Builder setSystemPropertyMutable(/* @NonNull */ String key, + /* @Nullable */ Object value) { + mRule.mSystemProperties.setValue(key, value); + mRule.mSystemProperties.setAccessReadWrite(key); + return this; + } + public RavenwoodRule build() { return mRule; } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java new file mode 100644 index 000000000000..85ad4e444f24 --- /dev/null +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java @@ -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 android.platform.test.ravenwood; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; + +class RavenwoodSystemProperties { + private final Map<String, String> mValues = new HashMap<>(); + + /** Set of additional keys that should be considered readable */ + private final Set<String> mKeyReadable = new HashSet<>(); + private final Predicate<String> mKeyReadablePredicate = (key) -> { + final String root = getKeyRoot(key); + + if (root.startsWith("debug.")) return true; + + // This set is carefully curated to help identify situations where a test may + // accidentally depend on a default value of an obscure property whose owner hasn't + // decided how Ravenwood should behave. + if (root.startsWith("boot.")) return true; + if (root.startsWith("build.")) return true; + if (root.startsWith("product.")) return true; + if (root.startsWith("soc.")) return true; + if (root.startsWith("system.")) return true; + + switch (key) { + case "gsm.version.baseband": + case "no.such.thing": + case "ro.bootloader": + case "ro.debuggable": + case "ro.hardware": + case "ro.hw_timeout_multiplier": + case "ro.odm.build.media_performance_class": + case "ro.treble.enabled": + case "ro.vndk.version": + return true; + } + + return mKeyReadable.contains(key); + }; + + /** Set of additional keys that should be considered writable */ + private final Set<String> mKeyWritable = new HashSet<>(); + private final Predicate<String> mKeyWritablePredicate = (key) -> { + final String root = getKeyRoot(key); + + if (root.startsWith("debug.")) return true; + + return mKeyWritable.contains(key); + }; + + public RavenwoodSystemProperties() { + // TODO: load these values from build.prop generated files + setValueForPartitions("product.brand", "Android"); + setValueForPartitions("product.device", "Ravenwood"); + setValueForPartitions("product.manufacturer", "Android"); + setValueForPartitions("product.model", "Ravenwood"); + setValueForPartitions("product.name", "Ravenwood"); + + setValueForPartitions("product.cpu.abilist", "x86_64"); + setValueForPartitions("product.cpu.abilist32", ""); + setValueForPartitions("product.cpu.abilist64", "x86_64"); + + setValueForPartitions("build.date", "Thu Jan 01 00:00:00 GMT 2024"); + setValueForPartitions("build.date.utc", "1704092400"); + setValueForPartitions("build.id", "MAIN"); + setValueForPartitions("build.tags", "dev-keys"); + setValueForPartitions("build.type", "userdebug"); + setValueForPartitions("build.version.all_codenames", "REL"); + setValueForPartitions("build.version.codename", "REL"); + setValueForPartitions("build.version.incremental", "userdebug.ravenwood.20240101"); + setValueForPartitions("build.version.known_codenames", "REL"); + setValueForPartitions("build.version.release", "14"); + setValueForPartitions("build.version.release_or_codename", "VanillaIceCream"); + setValueForPartitions("build.version.sdk", "34"); + + setValue("ro.board.first_api_level", "1"); + setValue("ro.product.first_api_level", "1"); + + setValue("ro.soc.manufacturer", "Android"); + setValue("ro.soc.model", "Ravenwood"); + + setValue("ro.debuggable", "1"); + } + + Map<String, String> getValues() { + return new HashMap<>(mValues); + } + + Predicate<String> getKeyReadablePredicate() { + return mKeyReadablePredicate; + } + + Predicate<String> getKeyWritablePredicate() { + return mKeyWritablePredicate; + } + + private static final String[] PARTITIONS = { + "bootimage", + "odm", + "product", + "system", + "system_ext", + "vendor", + "vendor_dlkm", + }; + + /** + * Set the given property for all possible partitions where it could be defined. For + * example, the value of {@code ro.build.type} is typically also mirrored under + * {@code ro.system.build.type}, etc. + */ + private void setValueForPartitions(String key, String value) { + setValue("ro." + key, value); + for (String partition : PARTITIONS) { + setValue("ro." + partition + "." + key, value); + } + } + + public void setValue(String key, Object value) { + final String valueString = (value == null) ? null : String.valueOf(value); + if ((valueString == null) || valueString.isEmpty()) { + mValues.remove(key); + } else { + mValues.put(key, valueString); + } + } + + public void setAccessNone(String key) { + mKeyReadable.remove(key); + mKeyWritable.remove(key); + } + + public void setAccessReadOnly(String key) { + mKeyReadable.add(key); + mKeyWritable.remove(key); + } + + public void setAccessReadWrite(String key) { + mKeyReadable.add(key); + mKeyWritable.add(key); + } + + /** + * Return the "root" of the given property key, stripping away any modifier prefix such as + * {@code ro.} or {@code persist.}. + */ + private static String getKeyRoot(String key) { + if (key.startsWith("ro.")) { + return key.substring(3); + } else if (key.startsWith("persist.")) { + return key.substring(8); + } else { + return key; + } + } +} diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt index ab2546bab246..5700f000cbc5 100644 --- a/ravenwood/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/ravenwood-annotation-allowed-classes.txt @@ -1,6 +1,9 @@ # Only classes listed here can use the Ravenwood annotations. com.android.internal.util.ArrayUtils +com.android.internal.logging.MetricsLogger +com.android.internal.logging.testing.FakeMetricsLogger +com.android.internal.logging.testing.UiEventLoggerFake com.android.internal.os.BatteryStatsHistory com.android.internal.os.BatteryStatsHistory$TraceDelegate com.android.internal.os.BatteryStatsHistory$VarintParceler @@ -47,6 +50,7 @@ android.os.BatteryUsageStatsQuery android.os.Binder android.os.Binder$IdentitySupplier android.os.Broadcaster +android.os.Build android.os.BundleMerger android.os.ConditionVariable android.os.FileUtils @@ -65,8 +69,10 @@ android.os.PowerComponents android.os.Process android.os.ServiceSpecificException android.os.SystemClock +android.os.SystemProperties android.os.ThreadLocalWorkSource android.os.TimestampedValue +android.os.Trace android.os.UidBatteryConsumer android.os.UidBatteryConsumer$Builder android.os.UserHandle @@ -126,6 +132,8 @@ android.graphics.RectF android.content.ContentProvider +android.metrics.LogMaker + com.android.server.LocalServices com.android.server.power.stats.BatteryStatsImpl diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 993b2544f110..44682e2088f4 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -45,6 +45,13 @@ flag { } flag { + name: "fix_drag_pointer_when_ending_drag" + namespace: "accessibility" + description: "Send the correct pointer id when transitioning from dragging to delegating states." + bug: "300002193" +} + +flag { name: "pinch_zoom_zero_min_span" namespace: "accessibility" description: "Whether to set min span of ScaleGestureDetector to zero." diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index c4184854e690..3086ce1ceb40 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -1466,8 +1466,11 @@ public class TouchExplorer extends BaseEventStreamTransformation int policyFlags = mState.getLastReceivedPolicyFlags(); if (mState.isDragging()) { // Send an event to the end of the drag gesture. - mDispatcher.sendMotionEvent( - event, ACTION_UP, rawEvent, ALL_POINTER_ID_BITS, policyFlags); + int pointerIdBits = ALL_POINTER_ID_BITS; + if (Flags.fixDragPointerWhenEndingDrag()) { + pointerIdBits = 1 << mDraggingPointerId; + } + mDispatcher.sendMotionEvent(event, ACTION_UP, rawEvent, pointerIdBits, policyFlags); } mState.startDelegating(); // Deliver all pointers to the view hierarchy. diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index b6e114087f30..2168cb2043ed 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -583,6 +583,11 @@ public class VirtualDeviceManagerService extends SystemService { return associationInfo == null ? null : associationInfo.getDisplayName(); } + @Override // Binder call + public @NonNull List<String> getAllPersistentDeviceIds() { + return new ArrayList<>(mLocalService.getAllPersistentDeviceIds()); + } + // Binder call @Override public boolean isValidVirtualDeviceId(int deviceId) { diff --git a/services/core/Android.bp b/services/core/Android.bp index 9375fb14a6d7..a54a48a7e84e 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -214,6 +214,7 @@ java_library_static { "backup_flags_lib", "policy_flags_lib", "net_flags_lib", + "stats_flags_lib", ], javac_shard_size: 50, javacflags: [ diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 52a540855130..ca04e41769ee 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -472,6 +472,8 @@ import com.android.server.pm.pkg.SELinuxUtil; import com.android.server.pm.snapshot.PackageDataSnapshot; import com.android.server.power.stats.BatteryStatsImpl; import com.android.server.sdksandbox.SdkSandboxManagerLocal; +import com.android.server.stats.pull.StatsPullAtomService; +import com.android.server.stats.pull.StatsPullAtomServiceInternal; import com.android.server.uri.GrantUri; import com.android.server.uri.NeededUriGrants; import com.android.server.uri.UriGrantsManagerInternal; @@ -1308,6 +1310,8 @@ public class ActivityManagerService extends IActivityManager.Stub */ final BatteryStatsService mBatteryStatsService; + StatsPullAtomServiceInternal mStatsPullAtomServiceInternal; + /** * Information about component usage */ @@ -16554,6 +16558,21 @@ public class ActivityManagerService extends IActivityManager.Stub final @ProcessCapability int capability) { mBatteryStatsService.noteUidProcessState(uid, state); mAppOpsService.updateUidProcState(uid, state, capability); + if (StatsPullAtomService.ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) { + try { + if (mStatsPullAtomServiceInternal == null) { + mStatsPullAtomServiceInternal = LocalServices.getService( + StatsPullAtomServiceInternal.class); + } + if (mStatsPullAtomServiceInternal != null) { + mStatsPullAtomServiceInternal.noteUidProcessState(uid, state); + } else { + Slog.d(TAG, "StatsPullAtomService not ready yet"); + } + } catch (Exception e) { + Slog.e(TAG, "Exception during logging uid proc state change event", e); + } + } if (mTrackingAssociations) { for (int i1=0, N1=mAssociations.size(); i1<N1; i1++) { ArrayMap<ComponentName, SparseArray<ArrayMap<String, Association>>> targetComponents diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 57c52c2cf408..45f657d713ad 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -3754,6 +3754,11 @@ final class ActivityManagerShellCommand extends ShellCommand { } @Override + public void onProcessStarted(int pid, int processUid, int packageUid, String packageName, + String processName) { + } + + @Override public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) { } diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java index 1f98aba5bbd7..fb89b8e4f3b4 100644 --- a/services/core/java/com/android/server/am/AppFGSTracker.java +++ b/services/core/java/com/android/server/am/AppFGSTracker.java @@ -102,6 +102,11 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac } @Override + public void onProcessStarted(int pid, int processUid, int packageUid, String packageName, + String processName) { + } + + @Override public void onProcessDied(int pid, int uid) { } }; diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index fa5dbd2543d3..f5c34a5da1c1 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2852,6 +2852,7 @@ public final class ProcessList { ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT); } } + dispatchProcessStarted(app, pid); checkSlow(app.getStartTime(), "startProcess: done updating pids map"); return true; } @@ -4977,6 +4978,22 @@ public final class ProcessList { } } + void dispatchProcessStarted(ProcessRecord app, int pid) { + int i = mProcessObservers.beginBroadcast(); + while (i > 0) { + i--; + final IProcessObserver observer = mProcessObservers.getBroadcastItem(i); + if (observer != null) { + try { + observer.onProcessStarted(pid, app.uid, app.info.uid, + app.info.packageName, app.processName); + } catch (RemoteException e) { + } + } + } + mProcessObservers.finishBroadcast(); + } + void dispatchProcessDied(int pid, int uid) { int i = mProcessObservers.beginBroadcast(); while (i > 0) { diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index dc14c7aaa0b9..7aafda59298c 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -153,6 +153,7 @@ public class SettingsToPropertiesMapper { "machine_learning", "mainline_modularization", "mainline_sdk", + "make_pixel_haptics", "media_audio", "media_drm", "media_reliability", diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index 684d6a0fc596..cdd147a0ec37 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -177,6 +177,11 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } @Override + public void onProcessStarted(int pid, int processUid, int packageUid, String packageName, + String processName) { + } + + @Override public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) { } }; diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 99b45ec79571..cd295b521e89 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1047,11 +1047,9 @@ public class AudioDeviceBroker { private void initAudioHalBluetoothState() { synchronized (mBluetoothAudioStateLock) { mBluetoothScoOnApplied = false; - AudioSystem.setParameters("BT_SCO=off"); mBluetoothA2dpSuspendedApplied = false; - AudioSystem.setParameters("A2dpSuspended=false"); mBluetoothLeSuspendedApplied = false; - AudioSystem.setParameters("LeAudioSuspended=false"); + reapplyAudioHalBluetoothState(); } } @@ -1114,6 +1112,34 @@ public class AudioDeviceBroker { } } + @GuardedBy("mBluetoothAudioStateLock") + private void reapplyAudioHalBluetoothState() { + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "reapplyAudioHalBluetoothState() mBluetoothScoOnApplied: " + + mBluetoothScoOnApplied + ", mBluetoothA2dpSuspendedApplied: " + + mBluetoothA2dpSuspendedApplied + ", mBluetoothLeSuspendedApplied: " + + mBluetoothLeSuspendedApplied); + } + // Note: the order of parameters is important. + if (mBluetoothScoOnApplied) { + AudioSystem.setParameters("A2dpSuspended=true"); + AudioSystem.setParameters("LeAudioSuspended=true"); + AudioSystem.setParameters("BT_SCO=on"); + } else { + AudioSystem.setParameters("BT_SCO=off"); + if (mBluetoothA2dpSuspendedApplied) { + AudioSystem.setParameters("A2dpSuspended=true"); + } else { + AudioSystem.setParameters("A2dpSuspended=false"); + } + if (mBluetoothLeSuspendedApplied) { + AudioSystem.setParameters("LeAudioSuspended=true"); + } else { + AudioSystem.setParameters("LeAudioSuspended=false"); + } + } + } + /*package*/ void setBluetoothScoOn(boolean on, String eventSource) { if (AudioService.DEBUG_COMM_RTE) { Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource); @@ -1775,6 +1801,9 @@ public class AudioDeviceBroker { initRoutingStrategyIds(); updateActiveCommunicationDevice(); mDeviceInventory.onRestoreDevices(); + synchronized (mBluetoothAudioStateLock) { + reapplyAudioHalBluetoothState(); + } mBtHelper.onAudioServerDiedRestoreA2dp(); updateCommunicationRoute("MSG_RESTORE_DEVICES"); } diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 8fd2ee2bdc33..21e6bac53cde 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -439,6 +439,10 @@ public class AuthService extends SystemService { if (fingerprintService != null) { fingerprintService.registerAuthenticationStateListener(listener); } + final IFaceService faceService = mInjector.getFaceService(); + if (faceService != null) { + faceService.registerAuthenticationStateListener(listener); + } } @Override @@ -449,6 +453,10 @@ public class AuthService extends SystemService { if (fingerprintService != null) { fingerprintService.unregisterAuthenticationStateListener(listener); } + final IFaceService faceService = mInjector.getFaceService(); + if (faceService != null) { + faceService.unregisterAuthenticationStateListener(listener); + } } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java index 58635353c780..1ae4d6465c57 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java @@ -91,6 +91,40 @@ public class AuthenticationStateListeners implements IBinder.DeathRecipient { } } + /** + * Defines behavior in response to a successful authentication + * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested + * authentication + * @param userId The user Id for the requested authentication + */ + public void onAuthenticationSucceeded(int requestReason, int userId) { + for (AuthenticationStateListener listener: mAuthenticationStateListeners) { + try { + listener.onAuthenticationSucceeded(requestReason, userId); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception in notifying listener that authentication " + + "succeeded", e); + } + } + } + + /** + * Defines behavior in response to a failed authentication + * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested + * authentication + * @param userId The user Id for the requested authentication + */ + public void onAuthenticationFailed(int requestReason, int userId) { + for (AuthenticationStateListener listener: mAuthenticationStateListeners) { + try { + listener.onAuthenticationFailed(requestReason, userId); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception in notifying listener that authentication " + + "failed", e); + } + } + } + @Override public void binderDied() { // Do nothing, handled below diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 73f3999f40ce..321e951ec09b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -24,6 +24,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; +import android.hardware.biometrics.AuthenticationStateListener; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricService; @@ -63,6 +64,7 @@ import com.android.server.SystemService; import com.android.server.biometrics.Flags; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricStateCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutResetDispatcher; @@ -99,6 +101,8 @@ public class FaceService extends SystemService { private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal> mBiometricStateCallback; @NonNull + private final AuthenticationStateListeners mAuthenticationStateListeners; + @NonNull private final FaceProviderFunction mFaceProviderFunction; @NonNull private final Function<String, FaceProvider> mFaceProvider; @NonNull @@ -695,7 +699,8 @@ public class FaceService extends SystemService { for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) { providers.add( Face10.newInstance(getContext(), mBiometricStateCallback, - hidlSensor, mLockoutResetDispatcher)); + mAuthenticationStateListeners, hidlSensor, + mLockoutResetDispatcher)); } return providers; @@ -830,6 +835,24 @@ public class FaceService extends SystemService { public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) { mBiometricStateCallback.registerBiometricStateListener(listener); } + + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override + public void registerAuthenticationStateListener( + @NonNull AuthenticationStateListener listener) { + super.registerAuthenticationStateListener_enforcePermission(); + + mAuthenticationStateListeners.registerAuthenticationStateListener(listener); + } + + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override + public void unregisterAuthenticationStateListener( + @NonNull AuthenticationStateListener listener) { + super.unregisterAuthenticationStateListener_enforcePermission(); + + mAuthenticationStateListeners.unregisterAuthenticationStateListener(listener); + } } public FaceService(Context context) { @@ -848,6 +871,7 @@ public class FaceService extends SystemService { mLockoutResetDispatcher = new LockoutResetDispatcher(context); mLockPatternUtils = new LockPatternUtils(context); mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context)); + mAuthenticationStateListeners = new AuthenticationStateListeners(); mRegistry = new FaceServiceRegistry(mServiceWrapper, biometricServiceSupplier); mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() { @Override @@ -868,8 +892,8 @@ public class FaceService extends SystemService { try { final SensorProps[] props = face.getSensorProps(); return new FaceProvider(getContext(), - mBiometricStateCallback, props, name, mLockoutResetDispatcher, - BiometricContext.getInstance(getContext()), + mBiometricStateCallback, mAuthenticationStateListeners, props, name, + mLockoutResetDispatcher, BiometricContext.getInstance(getContext()), false /* resetLockoutRequiresChallenge */); } catch (RemoteException e) { Slog.e(TAG, "Remote exception in getSensorProps: " + fqName); @@ -881,7 +905,7 @@ public class FaceService extends SystemService { if (Flags.deHidl()) { mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction : ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider( - getContext(), mBiometricStateCallback, + getContext(), mBiometricStateCallback, mAuthenticationStateListeners, filteredSensorProps.second, filteredSensorProps.first, mLockoutResetDispatcher, BiometricContext.getInstance(getContext()), diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 22e399c6747b..f35de93af625 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.face.aidl; +import static android.adaptiveauth.Flags.reportBiometricAuthAttempts; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.NotificationManager; @@ -44,6 +46,7 @@ import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; @@ -77,6 +80,8 @@ public class FaceAuthenticationClient private ICancellationSignal mCancellationSignal; @Nullable private final SensorPrivacyManager mSensorPrivacyManager; + @NonNull + private final AuthenticationStateListeners mAuthenticationStateListeners; @FaceManager.FaceAcquired private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN; @@ -89,11 +94,13 @@ public class FaceAuthenticationClient @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull UsageStats usageStats, @NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication, - @Authenticators.Types int sensorStrength) { + @Authenticators.Types int sensorStrength, + @NonNull AuthenticationStateListeners authenticationStateListeners) { this(context, lazyDaemon, token, requestId, listener, operationId, restricted, options, cookie, requireConfirmation, logger, biometricContext, isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication, - context.getSystemService(SensorPrivacyManager.class), sensorStrength); + context.getSystemService(SensorPrivacyManager.class), sensorStrength, + authenticationStateListeners); } @VisibleForTesting @@ -107,7 +114,8 @@ public class FaceAuthenticationClient boolean isStrongBiometric, @NonNull UsageStats usageStats, @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication, SensorPrivacyManager sensorPrivacyManager, - @Authenticators.Types int biometricStrength) { + @Authenticators.Types int biometricStrength, + @NonNull AuthenticationStateListeners authenticationStateListeners) { super(context, lazyDaemon, token, listener, operationId, restricted, options, cookie, requireConfirmation, logger, biometricContext, isStrongBiometric, null /* taskStackListener */, lockoutTracker, @@ -118,6 +126,7 @@ public class FaceAuthenticationClient mNotificationManager = context.getSystemService(NotificationManager.class); mSensorPrivacyManager = sensorPrivacyManager; mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator(); + mAuthenticationStateListeners = authenticationStateListeners; final Resources resources = getContext().getResources(); mBiometricPromptIgnoreList = resources.getIntArray( @@ -262,6 +271,16 @@ public class FaceAuthenticationClient 0 /* error */, 0 /* vendorError */, getTargetUserId())); + + if (reportBiometricAuthAttempts()) { + if (authenticated) { + mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(), + getTargetUserId()); + } else { + mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(), + getTargetUserId()); + } + } } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index e4ecf1a61155..d01c2687b1ff 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -59,6 +59,7 @@ import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationClient; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; @@ -103,6 +104,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @NonNull private final BiometricStateCallback mBiometricStateCallback; @NonNull + private final AuthenticationStateListeners mAuthenticationStateListeners; + @NonNull private final String mHalInstanceName; @NonNull private final Handler mHandler; @@ -156,18 +159,20 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { public FaceProvider(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull SensorProps[] props, @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull BiometricContext biometricContext, boolean resetLockoutRequiresChallenge) { - this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher, - biometricContext, null /* daemon */, getHandler(), resetLockoutRequiresChallenge, - false /* testHalEnabled */); + this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName, + lockoutResetDispatcher, biometricContext, null /* daemon */, getHandler(), + resetLockoutRequiresChallenge, false /* testHalEnabled */); } @VisibleForTesting FaceProvider(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull SensorProps[] props, @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @@ -178,6 +183,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { boolean testHalEnabled) { mContext = context; mBiometricStateCallback = biometricStateCallback; + mAuthenticationStateListeners = authenticationStateListeners; mHalInstanceName = halInstanceName; mFaceSensors = new SensorList<>(ActivityManager.getService()); if (Flags.deHidl()) { @@ -610,7 +616,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mAuthenticationStatsCollector), mBiometricContext, isStrongBiometric, mUsageStats, lockoutTracker, - allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId)); + allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId), + mAuthenticationStateListeners); scheduleForSensor(sensorId, client, new ClientMonitorCallback() { @Override public void onClientStarted( diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index 53376669b387..48a676ce4937 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -64,6 +64,7 @@ import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.AuthenticationConsumer; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; @@ -119,6 +120,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { @NonNull private final FaceSensorPropertiesInternal mSensorProperties; @NonNull private final BiometricStateCallback mBiometricStateCallback; + @NonNull + private final AuthenticationStateListeners mAuthenticationStateListeners; @NonNull private final Context mContext; @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler; @NonNull private final Handler mHandler; @@ -350,6 +353,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { @VisibleForTesting Face10(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull FaceSensorPropertiesInternal sensorProps, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull Handler handler, @@ -358,6 +362,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { mSensorProperties = sensorProps; mContext = context; mBiometricStateCallback = biometricStateCallback; + mAuthenticationStateListeners = authenticationStateListeners; mSensorId = sensorProps.sensorId; mScheduler = scheduler; mHandler = handler; @@ -392,11 +397,12 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { public static Face10 newInstance(@NonNull Context context, @NonNull BiometricStateCallback biometricStateCallback, + @NonNull AuthenticationStateListeners authenticationStateListeners, @NonNull FaceSensorPropertiesInternal sensorProps, @NonNull LockoutResetDispatcher lockoutResetDispatcher) { final Handler handler = new Handler(Looper.getMainLooper()); - return new Face10(context, biometricStateCallback, sensorProps, lockoutResetDispatcher, - handler, new BiometricScheduler<>( + return new Face10(context, biometricStateCallback, authenticationStateListeners, + sensorProps, lockoutResetDispatcher, handler, new BiometricScheduler<>( BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityTracker */), BiometricContext.getInstance(context)); @@ -846,7 +852,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, mAuthenticationStatsCollector), mBiometricContext, isStrongBiometric, mUsageStats, mLockoutTracker, - allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId)); + allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId), + mAuthenticationStateListeners); mScheduler.scheduleClientMonitor(client); } @@ -860,7 +867,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, mAuthenticationStatsCollector), mBiometricContext, isStrongBiometric, mLockoutTracker, mUsageStats, - allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId)); + allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId), + mAuthenticationStateListeners); mScheduler.scheduleClientMonitor(client); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java index 8ab88923d01e..e44b26399549 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.face.hidl; +import static android.adaptiveauth.Flags.reportBiometricAuthAttempts; + import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; @@ -36,6 +38,7 @@ import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthenticationClient; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -65,6 +68,8 @@ class FaceAuthenticationClient private int mLastAcquire; private SensorPrivacyManager mSensorPrivacyManager; + @NonNull + private final AuthenticationStateListeners mAuthenticationStateListeners; FaceAuthenticationClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon, @@ -75,7 +80,8 @@ class FaceAuthenticationClient @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker, @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication, - @Authenticators.Types int sensorStrength) { + @Authenticators.Types int sensorStrength, + @NonNull AuthenticationStateListeners authenticationStateListeners) { super(context, lazyDaemon, token, listener, operationId, restricted, options, cookie, requireConfirmation, logger, biometricContext, isStrongBiometric, null /* taskStackListener */, @@ -84,6 +90,7 @@ class FaceAuthenticationClient setRequestId(requestId); mUsageStats = usageStats; mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); + mAuthenticationStateListeners = authenticationStateListeners; final Resources resources = getContext().getResources(); mBiometricPromptIgnoreList = resources.getIntArray( @@ -186,6 +193,16 @@ class FaceAuthenticationClient 0 /* error */, 0 /* vendorError */, getTargetUserId())); + + if (reportBiometricAuthAttempts()) { + if (authenticated) { + mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(), + getTargetUserId()); + } else { + mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(), + getTargetUserId()); + } + } } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index f7e812330ece..6912961ab94b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static android.adaptiveauth.Flags.reportBiometricAuthAttempts; + import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; import android.annotation.NonNull; @@ -232,8 +234,16 @@ public class FingerprintAuthenticationClient if (sidefpsControllerRefactor()) { mAuthenticationStateListeners.onAuthenticationStopped(); } + if (reportBiometricAuthAttempts()) { + mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(), + getTargetUserId()); + } } else { mState = STATE_STARTED_PAUSED_ATTEMPTED; + if (reportBiometricAuthAttempts()) { + mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(), + getTargetUserId()); + } } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java index 4c1d4d6d6d12..7a329e9d69e9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; +import static android.adaptiveauth.Flags.reportBiometricAuthAttempts; + import static com.android.systemui.shared.Flags.sidefpsControllerRefactor; import android.annotation.NonNull; @@ -142,6 +144,10 @@ class FingerprintAuthenticationClient if (sidefpsControllerRefactor()) { mAuthenticationStateListeners.onAuthenticationStopped(); } + if (reportBiometricAuthAttempts()) { + mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(), + getTargetUserId()); + } } else { mState = STATE_STARTED_PAUSED_ATTEMPTED; final @LockoutTracker.LockoutMode int lockoutMode = @@ -161,6 +167,10 @@ class FingerprintAuthenticationClient onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */); cancel(); } + if (reportBiometricAuthAttempts()) { + mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(), + getTargetUserId()); + } } } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 6ec6a123a4e7..77cb08bc02bd 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -204,6 +204,10 @@ public final class DeviceStateManagerService extends SystemService { } @Override + public void onProcessStarted(int pid, int processUid, int packageUid, String packageName, + String processName) {} + + @Override public void onProcessDied(int pid, int uid) {} @Override diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java index 403b421639cb..71a9f54c0b1c 100644 --- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java +++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java @@ -248,6 +248,7 @@ class GnssNetworkConnectivityHandler { SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class); TelephonyManager telManager = mContext.getSystemService(TelephonyManager.class); if (subManager != null && telManager != null) { + subManager = subManager.createForAllUserProfiles(); List<SubscriptionInfo> subscriptionInfoList = subManager.getActiveSubscriptionInfoList(); HashSet<Integer> activeSubIds = new HashSet<Integer>(); diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 550aed51c8e2..978f46808e3b 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -214,6 +214,11 @@ public final class MediaProjectionManagerService extends SystemService } @Override + public void onProcessStarted(int pid, int processUid, int packageUid, + String packageName, String processName) { + } + + @Override public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) { MediaProjectionManagerService.this.handleForegroundServicesChanged(pid, uid, serviceTypes); diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index 85c4ffe6ac67..f852b8173f30 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -57,6 +57,7 @@ import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; @@ -71,7 +72,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.EventLogTags; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; -import com.android.server.notification.Flags; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -81,6 +81,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; /** * NotificationManagerService helper for handling notification attention effects: @@ -100,6 +101,20 @@ public final class NotificationAttentionHelper { private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1; private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0; + @VisibleForTesting + static final Set<String> NOTIFICATION_AVALANCHE_TRIGGER_INTENTS = Set.of( + Intent.ACTION_AIRPLANE_MODE_CHANGED, + Intent.ACTION_BOOT_COMPLETED, + Intent.ACTION_USER_SWITCHED, + Intent.ACTION_MANAGED_PROFILE_AVAILABLE + ); + + @VisibleForTesting + static final Map<String, Pair<String, Boolean>> NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS = Map.of( + Intent.ACTION_AIRPLANE_MODE_CHANGED, new Pair<>("state", false), + Intent.ACTION_MANAGED_PROFILE_AVAILABLE, new Pair<>(Intent.EXTRA_QUIET_MODE, false) + ); + private final Context mContext; private final PackageManager mPackageManager; private final TelephonyManager mTelephonyManager; @@ -191,7 +206,7 @@ public final class NotificationAttentionHelper { mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume); if (Flags.politeNotifications()) { - mStrategy = getPolitenessStrategy(); + mStrategy = createPolitenessStrategy(); } else { mStrategy = null; } @@ -200,7 +215,7 @@ public final class NotificationAttentionHelper { loadUserSettings(); } - private PolitenessStrategy getPolitenessStrategy() { + private PolitenessStrategy createPolitenessStrategy() { if (Flags.crossAppPoliteNotifications()) { PolitenessStrategy appStrategy = new StrategyPerApp( mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1), @@ -209,11 +224,12 @@ public final class NotificationAttentionHelper { mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2), mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET)); - return new StrategyGlobal( + return new StrategyAvalanche( mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1), mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT), appStrategy); } else { return new StrategyPerApp( @@ -225,6 +241,11 @@ public final class NotificationAttentionHelper { } } + @VisibleForTesting + PolitenessStrategy getPolitenessStrategy() { + return mStrategy; + } + public void onSystemReady() { mSystemReady = true; @@ -259,6 +280,11 @@ public final class NotificationAttentionHelper { filter.addAction(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_USER_UNLOCKED); + if (Flags.crossAppPoliteNotifications()) { + for (String avalancheIntent : NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) { + filter.addAction(avalancheIntent); + } + } mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null); mContext.getContentResolver().registerContentObserver( @@ -1052,7 +1078,8 @@ public final class NotificationAttentionHelper { } } - abstract private static class PolitenessStrategy { + @VisibleForTesting + abstract static class PolitenessStrategy { static final int POLITE_STATE_DEFAULT = 0; static final int POLITE_STATE_POLITE = 1; static final int POLITE_STATE_MUTED = 2; @@ -1079,6 +1106,8 @@ public final class NotificationAttentionHelper { protected boolean mApplyPerPackage; protected final Map<String, Long> mLastUpdatedTimestampByPackage; + protected boolean mIsActive = true; + public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted) { mVolumeStates = new HashMap<>(); @@ -1218,6 +1247,10 @@ public final class NotificationAttentionHelper { } return nextState; } + + boolean isActive() { + return mIsActive; + } } // TODO b/270456865: Only one of the two strategies will be released. @@ -1289,55 +1322,60 @@ public final class NotificationAttentionHelper { } /** - * Global (cross-app) strategy. + * Avalanche (cross-app) strategy. */ - private static class StrategyGlobal extends PolitenessStrategy { + private static class StrategyAvalanche extends PolitenessStrategy { private static final String COMMON_KEY = "cross_app_common_key"; private final PolitenessStrategy mAppStrategy; private long mLastNotificationTimestamp = 0; - public StrategyGlobal(int timeoutPolite, int timeoutMuted, int volumePolite, - int volumeMuted, PolitenessStrategy appStrategy) { + private final int mTimeoutAvalanche; + private long mLastAvalancheTriggerTimestamp = 0; + + StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite, + int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy) { super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted); + mTimeoutAvalanche = timeoutAvalanche; mAppStrategy = appStrategy; if (DEBUG) { - Log.i(TAG, "StrategyGlobal: " + timeoutPolite + " " + timeoutMuted); + Log.i(TAG, "StrategyAvalanche: " + timeoutPolite + " " + timeoutMuted + " " + + timeoutAvalanche); } } @Override void onNotificationPosted(NotificationRecord record) { - if (shouldIgnoreNotification(record)) { - return; - } + if (isAvalancheActive()) { + if (shouldIgnoreNotification(record)) { + return; + } - long timeSinceLastNotif = + long timeSinceLastNotif = System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record); - final String key = getChannelKey(record); - @PolitenessState final int currState = getPolitenessState(record); - @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif); + final String key = getChannelKey(record); + @PolitenessState final int currState = getPolitenessState(record); + @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif); - if (DEBUG) { - Log.i(TAG, "StrategyGlobal onNotificationPosted time delta: " + timeSinceLastNotif - + " vol state: " + nextState + " key: " + key); - } + if (DEBUG) { + Log.i(TAG, + "StrategyAvalanche onNotificationPosted time delta: " + + timeSinceLastNotif + + " vol state: " + nextState + " key: " + key); + } - mVolumeStates.put(key, nextState); + mVolumeStates.put(key, nextState); + } mAppStrategy.onNotificationPosted(record); } @Override public float getSoundVolume(final NotificationRecord record) { - final @PolitenessState int globalVolState = getPolitenessState(record); - final @PolitenessState int appVolState = mAppStrategy.getPolitenessState(record); - - // Prioritize the most polite outcome - if (globalVolState > appVolState) { + if (isAvalancheActive()) { return super.getSoundVolume(record); } else { return mAppStrategy.getSoundVolume(record); @@ -1382,6 +1420,24 @@ public final class NotificationAttentionHelper { super.setApplyCooldownPerPackage(applyPerPackage); mAppStrategy.setApplyCooldownPerPackage(applyPerPackage); } + + boolean isAvalancheActive() { + mIsActive = (System.currentTimeMillis() - mLastAvalancheTriggerTimestamp + < mTimeoutAvalanche); + if (DEBUG) { + Log.i(TAG, "StrategyAvalanche: active " + mIsActive); + } + return mIsActive; + } + + @Override + boolean isActive() { + return isAvalancheActive(); + } + + void setTriggerTimeMs(long timestamp) { + mLastAvalancheTriggerTimestamp = timestamp; + } } //====================== Observers ============================= @@ -1415,6 +1471,30 @@ public final class NotificationAttentionHelper { || action.equals(Intent.ACTION_USER_UNLOCKED)) { loadUserSettings(); } + + if (Flags.crossAppPoliteNotifications()) { + if (NOTIFICATION_AVALANCHE_TRIGGER_INTENTS.contains(action)) { + boolean enableAvalancheStrategy = true; + // Some actions must also match extras, ie. airplane mode => disabled + Pair<String, Boolean> expectedExtras = + NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS.get(action); + if (expectedExtras != null) { + enableAvalancheStrategy = + intent.getBooleanExtra(expectedExtras.first, false) + == expectedExtras.second; + } + + if (DEBUG) { + Log.i(TAG, "Avalanche trigger intent received: " + action + + ". Enabling avalanche strategy: " + enableAvalancheStrategy); + } + + if (enableAvalancheStrategy && mStrategy instanceof StrategyAvalanche) { + ((StrategyAvalanche) mStrategy) + .setTriggerTimeMs(System.currentTimeMillis()); + } + } + } } }; diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index f311034a4dd2..ada79aed9d16 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -4217,8 +4217,10 @@ final class InstallPackageHelper { } } + final long firstInstallTime = Flags.fixSystemAppsFirstInstallTime() + ? System.currentTimeMillis() : 0; final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags, - scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null); + scanFlags | SCAN_UPDATE_SIGNATURE, firstInstallTime, user, null); return new Pair<>(scanResult, shouldHideSystemApp); } diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig index c8c16db15e0e..f5dfb5cc3afe 100644 --- a/services/core/java/com/android/server/power/feature/power_flags.aconfig +++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig @@ -4,7 +4,7 @@ package: "com.android.server.power.feature.flags" flag { name: "enable_early_screen_timeout_detector" - namespace: "power_manager" + namespace: "power" description: "Feature flag for Early Screen Timeout detector" bug: "309861917" is_fixed_read_only: true diff --git a/services/core/java/com/android/server/stats/Android.bp b/services/core/java/com/android/server/stats/Android.bp new file mode 100644 index 000000000000..e597c3a6b6e6 --- /dev/null +++ b/services/core/java/com/android/server/stats/Android.bp @@ -0,0 +1,12 @@ +aconfig_declarations { + name: "stats_flags", + package: "com.android.server.stats", + srcs: [ + "stats_flags.aconfig", + ], +} + +java_aconfig_library { + name: "stats_flags_lib", + aconfig_declarations: "stats_flags", +} diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java new file mode 100644 index 000000000000..0de73a5a30f6 --- /dev/null +++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java @@ -0,0 +1,285 @@ +/* + * 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.stats.pull; + +import android.app.ActivityManager; +import android.app.StatsManager; +import android.app.usage.NetworkStatsManager; +import android.net.NetworkStats; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Trace; +import android.util.ArrayMap; +import android.util.Slog; +import android.util.SparseIntArray; +import android.util.StatsEvent; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.FrameworkStatsLog; + +import java.util.List; +import java.util.Map; + +/** + * Aggregates Mobile Data Usage by process state per uid + */ +class AggregatedMobileDataStatsPuller { + private static final String TAG = "AggregatedMobileDataStatsPuller"; + + private static final boolean DEBUG = false; + + private static class UidProcState { + + private final int mUid; + private final int mState; + + UidProcState(int uid, int state) { + mUid = uid; + mState = state; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UidProcState key)) return false; + return mUid == key.mUid && mState == key.mState; + } + + @Override + public int hashCode() { + int result = mUid; + result = 31 * result + mState; + return result; + } + + public int getUid() { + return mUid; + } + + public int getState() { + return mState; + } + + } + + private static class MobileDataStats { + private long mRxPackets = 0; + private long mTxPackets = 0; + private long mRxBytes = 0; + private long mTxBytes = 0; + + public long getRxPackets() { + return mRxPackets; + } + + public long getTxPackets() { + return mTxPackets; + } + + public long getRxBytes() { + return mRxBytes; + } + + public long getTxBytes() { + return mTxBytes; + } + + public void addRxPackets(long rxPackets) { + mRxPackets += rxPackets; + } + + public void addTxPackets(long txPackets) { + mTxPackets += txPackets; + } + + public void addRxBytes(long rxBytes) { + mRxBytes += rxBytes; + } + + public void addTxBytes(long txBytes) { + mTxBytes += txBytes; + } + + public boolean isEmpty() { + return mRxPackets == 0 && mTxPackets == 0 && mRxBytes == 0 && mTxBytes == 0; + } + } + + private final Object mLock = new Object(); + @GuardedBy("mLock") + private final Map<UidProcState, MobileDataStats> mUidStats; + + private final SparseIntArray mUidPreviousState; + + private NetworkStats mLastMobileUidStats = new NetworkStats(0, -1); + + private final NetworkStatsManager mNetworkStatsManager; + + private final Handler mMobileDataStatsHandler; + + AggregatedMobileDataStatsPuller(NetworkStatsManager networkStatsManager) { + mUidStats = new ArrayMap<>(); + mUidPreviousState = new SparseIntArray(); + + mNetworkStatsManager = networkStatsManager; + + if (mNetworkStatsManager != null) { + updateNetworkStats(mNetworkStatsManager); + } + + HandlerThread mMobileDataStatsHandlerThread = new HandlerThread("MobileDataStatsHandler"); + mMobileDataStatsHandlerThread.start(); + mMobileDataStatsHandler = new Handler(mMobileDataStatsHandlerThread.getLooper()); + } + + public void noteUidProcessState(int uid, int state, long unusedElapsedRealtime, + long unusedUptime) { + mMobileDataStatsHandler.post( + () -> { + noteUidProcessStateImpl(uid, state); + }); + } + + public int pullDataBytesTransfer(List<StatsEvent> data) { + synchronized (mLock) { + return pullDataBytesTransferLocked(data); + } + } + + @GuardedBy("mLock") + private MobileDataStats getUidStatsForPreviousStateLocked(int uid) { + final int previousState = mUidPreviousState.get(uid, ActivityManager.PROCESS_STATE_UNKNOWN); + if (DEBUG && previousState == ActivityManager.PROCESS_STATE_UNKNOWN) { + Slog.d(TAG, "getUidStatsForPreviousStateLocked() no prev state info for uid " + + uid + ". Tracking stats with ActivityManager.PROCESS_STATE_UNKNOWN"); + } + + final UidProcState statsKey = new UidProcState(uid, previousState); + MobileDataStats stats; + if (mUidStats.containsKey(statsKey)) { + stats = mUidStats.get(statsKey); + } else { + stats = new MobileDataStats(); + mUidStats.put(statsKey, stats); + } + return stats; + } + + private void noteUidProcessStateImpl(int uid, int state) { + // noteUidProcessStateLocked can be called back to back several times while + // the updateNetworkStatsLocked loops over several stats for multiple uids + // and during the first call in a batch of proc state change event it can + // contain info for uid with unknown previous state yet which can happen due to a few + // reasons: + // - app was just started + // - app was started before the ActivityManagerService + // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN + if (mNetworkStatsManager != null) { + updateNetworkStats(mNetworkStatsManager); + } else { + Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager"); + } + mUidPreviousState.put(uid, state); + } + + private void updateNetworkStats(NetworkStatsManager networkStatsManager) { + if (DEBUG) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStats"); + } + } + + final NetworkStats latestStats = networkStatsManager.getMobileUidStats(); + if (isEmpty(latestStats)) { + if (DEBUG) { + Slog.w(TAG, "getMobileUidStats() failed"); + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } + return; + } + NetworkStats delta = latestStats.subtract(mLastMobileUidStats); + mLastMobileUidStats = latestStats; + + if (!isEmpty(delta)) { + updateNetworkStatsDelta(delta); + } else if (DEBUG) { + Slog.w(TAG, "updateNetworkStats() no delta"); + } + if (DEBUG) { + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } + } + + private void updateNetworkStatsDelta(NetworkStats delta) { + synchronized (mLock) { + for (NetworkStats.Entry entry : delta) { + if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) { + continue; + } + MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid()); + stats.addTxBytes(entry.getTxBytes()); + stats.addRxBytes(entry.getRxBytes()); + stats.addTxPackets(entry.getTxPackets()); + stats.addRxPackets(entry.getRxPackets()); + } + } + } + + @GuardedBy("mLock") + private int pullDataBytesTransferLocked(List<StatsEvent> pulledData) { + if (DEBUG) { + Slog.d(TAG, "pullDataBytesTransferLocked() start"); + } + for (Map.Entry<UidProcState, MobileDataStats> uidStats : mUidStats.entrySet()) { + if (!uidStats.getValue().isEmpty()) { + MobileDataStats stats = uidStats.getValue(); + pulledData.add(FrameworkStatsLog.buildStatsEvent( + FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE, + uidStats.getKey().getUid(), + ActivityManager.processStateAmToProto(uidStats.getKey().getState()), + stats.getRxBytes(), + stats.getRxPackets(), + stats.getTxBytes(), + stats.getTxPackets())); + } + } + if (DEBUG) { + Slog.d(TAG, + "pullDataBytesTransferLocked() done. results count " + pulledData.size()); + } + if (!pulledData.isEmpty()) { + return StatsManager.PULL_SUCCESS; + } + return StatsManager.PULL_SKIP; + } + + private static boolean isEmpty(NetworkStats stats) { + long totalRxPackets = 0; + long totalTxPackets = 0; + for (NetworkStats.Entry entry : stats) { + if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) { + continue; + } + totalRxPackets += entry.getRxPackets(); + totalTxPackets += entry.getTxPackets(); + // at least one non empty entry located + break; + } + final long totalPackets = totalRxPackets + totalTxPackets; + return totalPackets == 0; + } +} diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index e876241f385e..285bcc328c0c 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -59,6 +59,7 @@ import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STA import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY; import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; +import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller; import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines; @@ -409,6 +410,15 @@ public class StatsPullAtomService extends SystemService { @GuardedBy("mKeystoreLock") private IKeystoreMetrics mIKeystoreMetrics; + private AggregatedMobileDataStatsPuller mAggregatedMobileDataStatsPuller = null; + + /** + * Whether or not to enable the new puller with aggregation by process state per uid on a + * system server side. + */ + public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER = + addMobileBytesTransferByProcStatePuller(); + // Puller locks private final Object mDataBytesTransferLock = new Object(); private final Object mBluetoothBytesTransferLock = new Object(); @@ -469,6 +479,20 @@ public class StatsPullAtomService extends SystemService { mContext = context; } + private final class StatsPullAtomServiceInternalImpl extends StatsPullAtomServiceInternal { + + @Override + public void noteUidProcessState(int uid, int state) { + if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER + && mAggregatedMobileDataStatsPuller != null) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + mAggregatedMobileDataStatsPuller.noteUidProcessState(uid, state, elapsedRealtime, + uptime); + } + } + } + private native void initializeNativePullers(); /** @@ -486,6 +510,11 @@ public class StatsPullAtomService extends SystemService { } try { switch (atomTag) { + case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE: + if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER + && mAggregatedMobileDataStatsPuller != null) { + return mAggregatedMobileDataStatsPuller.pullDataBytesTransfer(data); + } case FrameworkStatsLog.WIFI_BYTES_TRANSFER: case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: case FrameworkStatsLog.MOBILE_BYTES_TRANSFER: @@ -776,7 +805,10 @@ public class StatsPullAtomService extends SystemService { @Override public void onStart() { - // no op + if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) { + LocalServices.addService(StatsPullAtomServiceInternal.class, + new StatsPullAtomServiceInternalImpl()); + } } @Override @@ -811,6 +843,9 @@ public class StatsPullAtomService extends SystemService { mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager); mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class); mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class); + + initMobileDataStatsPuller(); + // Initialize DiskIO mStoragedUidIoStatsReader = new StoragedUidIoStatsReader(); @@ -972,6 +1007,18 @@ public class StatsPullAtomService extends SystemService { registerCachedAppsHighWatermarkPuller(); } + private void initMobileDataStatsPuller() { + if (DEBUG) { + Slog.d(TAG, + "ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER = " + + ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER); + } + if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) { + mAggregatedMobileDataStatsPuller = + new AggregatedMobileDataStatsPuller(mNetworkStatsManager); + } + } + private void initAndRegisterNetworkStatsPullers() { if (DEBUG) { Slog.d(TAG, "Registering NetworkStats pullers with statsd"); @@ -1013,6 +1060,9 @@ public class StatsPullAtomService extends SystemService { registerWifiBytesTransferBackground(); registerMobileBytesTransfer(); registerMobileBytesTransferBackground(); + if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) { + registerMobileBytesTransferByProcState(); + } registerBytesTransferByTagAndMetered(); registerDataUsageBytesTransfer(); registerOemManagedBytesTransfer(); @@ -1021,6 +1071,13 @@ public class StatsPullAtomService extends SystemService { } } + private void registerMobileBytesTransferByProcState() { + final int tagId = FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE; + PullAtomMetadata metadata = + new PullAtomMetadata.Builder().setAdditiveFields(new int[] {3, 4, 5, 6}).build(); + mStatsManager.setPullAtomCallback(tagId, metadata, DIRECT_EXECUTOR, mStatsCallbackImpl); + } + private void initAndRegisterDeferredPullers() { mUwbManager = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB) ? mContext.getSystemService(UwbManager.class) : null; diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java new file mode 100644 index 000000000000..06adbfc65abc --- /dev/null +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java @@ -0,0 +1,31 @@ +/* + * 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.server.stats.pull; + +/** + * System-server internal interface to the {@link StatsPullAtomService}. + * + * @hide Only for use within the system server. + */ +public abstract class StatsPullAtomServiceInternal { + + /** + * @param state Process state from ActivityManager.java. + */ + public abstract void noteUidProcessState(int uid, int state); + +} diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig new file mode 100644 index 000000000000..5101a6982fe1 --- /dev/null +++ b/services/core/java/com/android/server/stats/stats_flags.aconfig @@ -0,0 +1,9 @@ +package: "com.android.server.stats" + +flag { + name: "add_mobile_bytes_transfer_by_proc_state_puller" + namespace: "statsd" + description: "Adds mobile_bytes_transfer_by_proc_state atom with system server side aggregation" + bug: "309512867" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java index 106be5f124a0..4cc2c025575e 100644 --- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java +++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java @@ -48,6 +48,7 @@ import com.android.server.pm.KnownPackages; import java.io.FileDescriptor; import java.util.Objects; import java.util.Set; +import java.util.function.Consumer; /** * System service for managing sensing {@link AmbientContextEvent}s on Wearables. @@ -191,9 +192,23 @@ public class WearableSensingManagerService extends } } + private void callPerUserServiceIfExist( + Consumer<WearableSensingManagerPerUserService> serviceConsumer, + RemoteCallback statusCallback) { + int userId = UserHandle.getCallingUserId(); + synchronized (mLock) { + WearableSensingManagerPerUserService service = getServiceForUserLocked(userId); + if (service == null) { + Slog.w(TAG, "Service not available for userId " + userId); + WearableSensingManagerPerUserService.notifyStatusCallback(statusCallback, + WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + serviceConsumer.accept(service); + } + } + private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub { - final WearableSensingManagerPerUserService mService = getServiceForUserLocked( - UserHandle.getCallingUserId()); @Override public void provideDataStream( @@ -210,7 +225,9 @@ public class WearableSensingManagerService extends WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); return; } - mService.onProvideDataStream(parcelFileDescriptor, callback); + callPerUserServiceIfExist( + service -> service.onProvideDataStream(parcelFileDescriptor, callback), + callback); } @Override @@ -229,7 +246,9 @@ public class WearableSensingManagerService extends WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); return; } - mService.onProvidedData(data, sharedMemory, callback); + callPerUserServiceIfExist( + service -> service.onProvidedData(data, sharedMemory, callback), + callback); } @Override diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 036f7b6841c2..3d492bbd4d37 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -141,6 +141,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; @@ -991,6 +992,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private CustomAppTransition mCustomOpenTransition; private CustomAppTransition mCustomCloseTransition; + /** Non-zero to pause dispatching configuration changes to the client. */ + int mPauseConfigurationDispatchCount = 0; + private final Runnable mPauseTimeoutRunnable = new Runnable() { @Override public void run() { @@ -9276,6 +9280,59 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + @Override + void dispatchConfigurationToChild(WindowState child, Configuration config) { + if (isConfigurationDispatchPaused()) { + return; + } + super.dispatchConfigurationToChild(child, config); + } + + /** + * Pauses dispatch of configuration changes to the client. This includes any + * configuration-triggered lifecycle changes, WindowState configs, and surface changes. If + * a lifecycle change comes from another source (eg. stop), it will still run but will use the + * paused configuration. + * + * The main way this works is by blocking calls to {@link #updateReportedConfigurationAndSend}. + * That method is responsible for evaluating whether the activity needs to be relaunched and + * sending configurations. + */ + void pauseConfigurationDispatch() { + ++mPauseConfigurationDispatchCount; + if (mPauseConfigurationDispatchCount == 1) { + ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Pausing configuration dispatch for " + + " %s", this); + } + } + + /** @return `true` if configuration actually changed. */ + boolean resumeConfigurationDispatch() { + --mPauseConfigurationDispatchCount; + if (mPauseConfigurationDispatchCount > 0) { + return false; + } + ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Resuming configuration dispatch for %s", this); + if (mPauseConfigurationDispatchCount < 0) { + Slog.wtf(TAG, "Trying to resume non-paused configuration dispatch"); + mPauseConfigurationDispatchCount = 0; + return false; + } + if (mLastReportedDisplayId == getDisplayId() + && getConfiguration().equals(mLastReportedConfiguration.getMergedConfiguration())) { + return false; + } + for (int i = getChildCount() - 1; i >= 0; --i) { + dispatchConfigurationToChild(getChildAt(i), getConfiguration()); + } + updateReportedConfigurationAndSend(); + return true; + } + + boolean isConfigurationDispatchPaused() { + return mPauseConfigurationDispatchCount > 0; + } + private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds, Rect containingBounds) { return applyAspectRatio(outBounds, containingAppBounds, containingBounds, @@ -9525,6 +9582,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return true; } + if (isConfigurationDispatchPaused()) { + return true; + } + + return updateReportedConfigurationAndSend(); + } + + boolean updateReportedConfigurationAndSend() { + if (isConfigurationDispatchPaused()) { + Slog.wtf(TAG, "trying to update reported(client) config while dispatch is paused"); + } ProtoLog.v(WM_DEBUG_CONFIGURATION, "Ensuring correct " + "configuration: %s", this); diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java index 7f785af1671e..a1799b473c95 100644 --- a/services/core/java/com/android/server/wm/DisplayFrames.java +++ b/services/core/java/com/android/server/wm/DisplayFrames.java @@ -92,35 +92,39 @@ public class DisplayFrames { mRotation = rotation; mWidth = w; mHeight = h; - final Rect unrestricted = mUnrestricted; - unrestricted.set(0, 0, w, h); - state.setDisplayFrame(unrestricted); + final Rect u = mUnrestricted; + u.set(0, 0, w, h); + state.setDisplayFrame(u); state.setDisplayCutout(displayCutout); state.setRoundedCorners(roundedCorners); state.setPrivacyIndicatorBounds(indicatorBounds); state.setDisplayShape(displayShape); state.getDisplayCutoutSafe(safe); - if (safe.left > unrestricted.left) { - state.getOrCreateSource(ID_DISPLAY_CUTOUT_LEFT, displayCutout()).setFrame( - unrestricted.left, unrestricted.top, safe.left, unrestricted.bottom); + if (safe.left > u.left) { + state.getOrCreateSource(ID_DISPLAY_CUTOUT_LEFT, displayCutout()) + .setFrame(u.left, u.top, safe.left, u.bottom) + .updateSideHint(u); } else { state.removeSource(ID_DISPLAY_CUTOUT_LEFT); } - if (safe.top > unrestricted.top) { - state.getOrCreateSource(ID_DISPLAY_CUTOUT_TOP, displayCutout()).setFrame( - unrestricted.left, unrestricted.top, unrestricted.right, safe.top); + if (safe.top > u.top) { + state.getOrCreateSource(ID_DISPLAY_CUTOUT_TOP, displayCutout()) + .setFrame(u.left, u.top, u.right, safe.top) + .updateSideHint(u); } else { state.removeSource(ID_DISPLAY_CUTOUT_TOP); } - if (safe.right < unrestricted.right) { - state.getOrCreateSource(ID_DISPLAY_CUTOUT_RIGHT, displayCutout()).setFrame( - safe.right, unrestricted.top, unrestricted.right, unrestricted.bottom); + if (safe.right < u.right) { + state.getOrCreateSource(ID_DISPLAY_CUTOUT_RIGHT, displayCutout()) + .setFrame(safe.right, u.top, u.right, u.bottom) + .updateSideHint(u); } else { state.removeSource(ID_DISPLAY_CUTOUT_RIGHT); } - if (safe.bottom < unrestricted.bottom) { - state.getOrCreateSource(ID_DISPLAY_CUTOUT_BOTTOM, displayCutout()).setFrame( - unrestricted.left, safe.bottom, unrestricted.right, unrestricted.bottom); + if (safe.bottom < u.bottom) { + state.getOrCreateSource(ID_DISPLAY_CUTOUT_BOTTOM, displayCutout()) + .setFrame(u.left, safe.bottom, u.right, u.bottom) + .updateSideHint(u); } else { state.removeSource(ID_DISPLAY_CUTOUT_BOTTOM); } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 9d5ddf3bf264..d9dda4aeb96a 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -62,6 +62,8 @@ import java.util.function.Consumer; */ class InsetsSourceProvider { + private static final Rect EMPTY_RECT = new Rect(); + protected final DisplayContent mDisplayContent; protected final @NonNull InsetsSource mSource; protected WindowContainer mWindowContainer; @@ -286,12 +288,15 @@ class InsetsSourceProvider { private void updateSourceFrameForServerVisibility() { // Make sure we set the valid source frame only when server visible is true, because the - // frame may not yet determined that server side doesn't think the window is ready to + // frame may not yet be determined that server side doesn't think the window is ready to // visible. (i.e. No surface, pending insets that were given during layout, etc..) - if (mServerVisible) { - mSource.setFrame(mSourceFrame); - } else { - mSource.setFrame(0, 0, 0, 0); + final Rect frame = mServerVisible ? mSourceFrame : EMPTY_RECT; + if (mSource.getFrame().equals(frame)) { + return; + } + mSource.setFrame(frame); + if (mWindowContainer != null) { + mSource.updateSideHint(mWindowContainer.getBounds()); } } @@ -631,7 +636,7 @@ class InsetsSourceProvider { } pw.print(prefix); pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching); - pw.print("mHasPendingPosition="); pw.print(mHasPendingPosition); + pw.print(" mHasPendingPosition="); pw.print(mHasPendingPosition); pw.println(); if (mWindowContainer != null) { pw.print(prefix + "mWindowContainer="); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 6371bb45ade9..0c6b174b2408 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -369,6 +369,15 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ private boolean mMoveToBottomIfClearWhenLaunch; + /** + * If {@code true}, transitions are allowed even if this TaskFragment is empty. If + * {@code false}, transitions will wait until this TaskFragment becomes non-empty or other + * conditions are met. Default to {@code false}. + * + * @see #isReadyToTransit + */ + private boolean mAllowTransitionWhenEmpty; + /** When set, will force the task to report as invisible. */ static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1; static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1; @@ -509,6 +518,19 @@ class TaskFragment extends WindowContainer<WindowContainer> { mIsolatedNav = isolatedNav; } + /** + * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true}, + * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions + * will wait until the TaskFragment becomes non-empty or other conditions are met. Default + * to {@code false}. + */ + void setAllowTransitionWhenEmpty(boolean allowTransitionWhenEmpty) { + if (!isEmbedded()) { + return; + } + mAllowTransitionWhenEmpty = allowTransitionWhenEmpty; + } + /** @see #mIsolatedNav */ boolean isIsolatedNav() { return isEmbedded() && mIsolatedNav; @@ -2827,8 +2849,9 @@ class TaskFragment extends WindowContainer<WindowContainer> { return true; } // We don't want to start the transition if the organized TaskFragment is empty, unless - // it is requested to be removed. - if (getTopNonFinishingActivity() != null || mIsRemovalRequested) { + // it is requested to be removed or the mAllowTransitionWhenEmpty flag is true. + if (getTopNonFinishingActivity() != null || mIsRemovalRequested + || mAllowTransitionWhenEmpty) { return true; } // Organizer shouldn't change embedded TaskFragment in PiP. diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index bdea1bc40f3a..286182eedf44 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -465,7 +465,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } final InsetsSource source = new InsetsSource(id, provider.getType()); - source.setFrame(provider.getArbitraryRectangle()); + source.setFrame(provider.getArbitraryRectangle()).updateSideHint(getBounds()); mLocalInsetsSources.put(id, source); mDisplayContent.getInsetsStateController().updateAboveInsetsState(true); } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 205ed977f316..4ba52e4c0fd7 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -2202,6 +2202,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } final TaskFragment taskFragment = new TaskFragment(mService, creationParams.getFragmentToken(), true /* createdByOrganizer */); + taskFragment.setAllowTransitionWhenEmpty(creationParams.getAllowTransitionWhenEmpty()); // Set task fragment organizer immediately, since it might have to be notified about further // actions. TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 56f2bc3d3e3b..7ad87ed8f094 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5187,6 +5187,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mSurfaceControl == null) { return; } + if (mActivityRecord != null && mActivityRecord.isConfigurationDispatchPaused()) { + // Don't update surface-position while dispatch paused. This is calculated from + // the server-side activity configuration so return early. + return; + } if ((mWmService.mWindowPlacerLocked.isLayoutDeferred() || isGoneForLayout()) && !mSurfacePlacementNeeded) { diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 5048cef3da1b..13e1ba785b87 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -639,9 +639,12 @@ class WindowToken extends WindowContainer<WindowState> { @Override void updateSurfacePosition(SurfaceControl.Transaction t) { + final ActivityRecord r = asActivityRecord(); + if (r != null && r.isConfigurationDispatchPaused()) { + return; + } super.updateSurfacePosition(t); if (!mTransitionController.isShellTransitionsEnabled() && isFixedRotationTransforming()) { - final ActivityRecord r = asActivityRecord(); final Task rootTask = r != null ? r.getRootTask() : null; // Don't transform the activity in PiP because the PiP task organizer will handle it. if (rootTask == null || !rootTask.inPinnedWindowingMode()) { diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt index a0fb0138e5e5..24d49523b9d1 100644 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt @@ -94,7 +94,9 @@ class DevicePermissionPolicy : SchemePolicy() { isSystemUpdated: Boolean ) { packageNames.forEachIndexed { _, packageName -> - val packageState = newState.externalState.packageStates[packageName]!! + // The package may still be removed even if it was once notified as installed. + val packageState = newState.externalState.packageStates[packageName] + ?: return@forEachIndexed trimPermissionStates(packageState.appId) } } @@ -245,6 +247,13 @@ class DevicePermissionPolicy : SchemePolicy() { flagMask: Int, flagValues: Int ): Boolean { + if (userId !in newState.userStates) { + // Despite that we check UserManagerInternal.exists() in PermissionService, we may still + // sometimes get race conditions between that check and the actual mutateState() call. + // This should rarely happen but at least we should not crash. + Slog.e(LOG_TAG, "Unable to update permission flags for missing user $userId") + return false + } val oldFlags = newState.userStates[userId]!! .appIdDevicePermissionFlags[appId] diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java new file mode 100644 index 000000000000..fcf761fb6607 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.os.Process.myPid; +import static android.os.Process.myUid; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManagerInternal; +import android.app.IApplicationThread; +import android.app.IProcessObserver; +import android.app.usage.UsageStatsManagerInternal; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManagerInternal; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.util.Log; + +import androidx.test.filters.MediumTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.DropBoxManagerInternal; +import com.android.server.LocalServices; +import com.android.server.am.ActivityManagerService.Injector; +import com.android.server.appop.AppOpsService; +import com.android.server.wm.ActivityTaskManagerInternal; +import com.android.server.wm.ActivityTaskManagerService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.util.Arrays; + + +/** + * Tests to verify that process events are dispatched to process observers. + */ +@MediumTest +@SuppressWarnings("GuardedBy") +public class ProcessObserverTest { + private static final String TAG = "ProcessObserverTest"; + + private static final String PACKAGE = "com.foo"; + + @Rule + public final ApplicationExitInfoTest.ServiceThreadRule + mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule(); + + private Context mContext; + private HandlerThread mHandlerThread; + + @Mock + private AppOpsService mAppOpsService; + @Mock + private DropBoxManagerInternal mDropBoxManagerInt; + @Mock + private PackageManagerInternal mPackageManagerInt; + @Mock + private UsageStatsManagerInternal mUsageStatsManagerInt; + @Mock + private ActivityManagerInternal mActivityManagerInt; + @Mock + private ActivityTaskManagerInternal mActivityTaskManagerInt; + @Mock + private BatteryStatsService mBatteryStatsService; + + private ActivityManagerService mRealAms; + private ActivityManagerService mAms; + + private ProcessList mRealProcessList = new ProcessList(); + private ProcessList mProcessList; + + final IProcessObserver mProcessObserver = mock(IProcessObserver.Stub.class); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + + LocalServices.removeServiceForTest(DropBoxManagerInternal.class); + LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt); + + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); + + LocalServices.removeServiceForTest(ActivityManagerInternal.class); + LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt); + + LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class); + LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt); + + doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); + doReturn(true).when(mActivityTaskManagerInt).attachApplication(any()); + doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any()); + + mRealAms = new ActivityManagerService( + new TestInjector(mContext), mServiceThreadRule.getThread()); + mRealAms.mConstants.loadDeviceConfigConstants(); + mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); + mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); + mRealAms.mAtmInternal = mActivityTaskManagerInt; + mRealAms.mPackageManagerInt = mPackageManagerInt; + mRealAms.mUsageStatsService = mUsageStatsManagerInt; + mRealAms.mProcessesReady = true; + mAms = spy(mRealAms); + mRealProcessList.mService = mAms; + mProcessList = spy(mRealProcessList); + + doReturn(mProcessObserver).when(mProcessObserver).asBinder(); + mProcessList.registerProcessObserver(mProcessObserver); + + doAnswer((invocation) -> { + Log.v(TAG, "Intercepting isProcStartValidLocked() for " + + Arrays.toString(invocation.getArguments())); + return null; + }).when(mProcessList).isProcStartValidLocked(any(), anyLong()); + } + + @After + public void tearDown() throws Exception { + mHandlerThread.quit(); + } + + private class TestInjector extends Injector { + TestInjector(Context context) { + super(context); + } + + @Override + public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile, + Handler handler) { + return mAppOpsService; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return mHandlerThread.getThreadHandler(); + } + + @Override + public ProcessList getProcessList(ActivityManagerService service) { + return mRealProcessList; + } + + @Override + public BatteryStatsService getBatteryStatsService() { + return mBatteryStatsService; + } + } + + private ProcessRecord makeActiveProcessRecord(String packageName) + throws Exception { + final ApplicationInfo ai = makeApplicationInfo(packageName); + return makeActiveProcessRecord(ai); + } + + private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai) + throws Exception { + final IApplicationThread thread = mock(IApplicationThread.class); + final IBinder threadBinder = new Binder(); + doReturn(threadBinder).when(thread).asBinder(); + doAnswer((invocation) -> { + Log.v(TAG, "Intercepting bindApplication() for " + + Arrays.toString(invocation.getArguments())); + if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) { + mRealAms.finishAttachApplication(0); + } + return null; + }).when(thread).bindApplication( + any(), any(), + any(), any(), anyBoolean(), + any(), any(), + any(), any(), + any(), + any(), anyInt(), + anyBoolean(), anyBoolean(), + anyBoolean(), anyBoolean(), any(), + any(), any(), any(), + any(), any(), + any(), any(), + any(), + anyLong(), anyLong()); + final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid)); + r.setPid(myPid()); + r.setStartUid(myUid()); + r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST)); + r.makeActive(thread, mAms.mProcessStats); + doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(), + anyBoolean()); + return r; + } + + static ApplicationInfo makeApplicationInfo(String packageName) { + final ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = packageName; + ai.processName = packageName; + ai.uid = myUid(); + return ai; + } + + /** + * Verify that a process start event is dispatched to process observers. + */ + @Test + public void testNormal() throws Exception { + ProcessRecord app = startProcess(); + verify(mProcessObserver).onProcessStarted( + app.getPid(), app.uid, app.info.uid, PACKAGE, PACKAGE); + } + + private ProcessRecord startProcess() throws Exception { + final ProcessRecord app = makeActiveProcessRecord(PACKAGE); + final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE); + mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false, + /* expectedStartSeq */ 0, /* procAttached */ false); + app.getThread().bindApplication(PACKAGE, appInfo, + null, null, false, + null, + null, + null, null, + null, + null, 0, + false, false, + true, false, + null, + null, null, + null, + null, null, null, + null, null, + 0, 0); + return app; + } + + // TODO: [b/302724778] Remove manual JNI load + static { + System.loadLibrary("mockingservicestestjni"); + } +} diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp index 654d7a8de168..f49f6383b3c8 100644 --- a/services/tests/powerstatstests/Android.bp +++ b/services/tests/powerstatstests/Android.bp @@ -44,6 +44,7 @@ android_test { "servicestests-utils", "platform-test-annotations", "flag-junit", + "ravenwood-junit", ], libs: [ diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index ca162e0b46e1..ba2b53854cd7 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java @@ -32,6 +32,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.UidBatteryConsumer; import android.os.UserBatteryConsumer; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.SparseArray; import androidx.test.InstrumentationRegistry; @@ -57,7 +58,8 @@ public class BatteryUsageStatsRule implements TestRule { private final PowerProfile mPowerProfile; private final MockClock mMockClock = new MockClock(); - private final MockBatteryStatsImpl mBatteryStats; + private final File mHistoryDir; + private MockBatteryStatsImpl mBatteryStats; private Handler mHandler; private BatteryUsageStats mBatteryUsageStats; @@ -66,6 +68,10 @@ public class BatteryUsageStatsRule implements TestRule { private SparseArray<int[]> mCpusByPolicy = new SparseArray<>(); private SparseArray<int[]> mFreqsByPolicy = new SparseArray<>(); + private int mDisplayCount = -1; + private int mPerUidModemModel = -1; + private NetworkStats mNetworkStats; + public BatteryUsageStatsRule() { this(0, null); } @@ -78,16 +84,38 @@ public class BatteryUsageStatsRule implements TestRule { mHandler = mock(Handler.class); mPowerProfile = spy(new PowerProfile()); mMockClock.currentTime = currentTime; - mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir, mHandler); - mBatteryStats.setPowerProfile(mPowerProfile); + mHistoryDir = historyDir; + + if (!RavenwoodRule.isUnderRavenwood()) { + lateInitBatteryStats(); + } mCpusByPolicy.put(0, new int[]{0, 1, 2, 3}); mCpusByPolicy.put(4, new int[]{4, 5, 6, 7}); mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000}); mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000}); + } + + private void lateInitBatteryStats() { + if (mBatteryStats != null) return; + + mBatteryStats = new MockBatteryStatsImpl(mMockClock, mHistoryDir, mHandler); + mBatteryStats.setPowerProfile(mPowerProfile); mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy)); mBatteryStats.onSystemReady(); + + if (mDisplayCount != -1) { + mBatteryStats.setDisplayCountLocked(mDisplayCount); + } + if (mPerUidModemModel != -1) { + synchronized (mBatteryStats) { + mBatteryStats.setPerUidModemModel(mPerUidModemModel); + } + } + if (mNetworkStats != null) { + mBatteryStats.setNetworkStats(mNetworkStats); + } } public MockClock getMockClock() { @@ -112,7 +140,10 @@ public class BatteryUsageStatsRule implements TestRule { } mCpusByPolicy.put(policy, relatedCpus); mFreqsByPolicy.put(policy, frequencies); - mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy)); + if (mBatteryStats != null) { + mBatteryStats.setCpuScalingPolicies( + new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy)); + } return this; } @@ -174,13 +205,19 @@ public class BatteryUsageStatsRule implements TestRule { public BatteryUsageStatsRule setNumDisplays(int value) { when(mPowerProfile.getNumDisplays()).thenReturn(value); - mBatteryStats.setDisplayCountLocked(value); + mDisplayCount = value; + if (mBatteryStats != null) { + mBatteryStats.setDisplayCountLocked(mDisplayCount); + } return this; } public BatteryUsageStatsRule setPerUidModemModel(int perUidModemModel) { - synchronized (mBatteryStats) { - mBatteryStats.setPerUidModemModel(perUidModemModel); + mPerUidModemModel = perUidModemModel; + if (mBatteryStats != null) { + synchronized (mBatteryStats) { + mBatteryStats.setPerUidModemModel(mPerUidModemModel); + } } return this; } @@ -210,7 +247,10 @@ public class BatteryUsageStatsRule implements TestRule { } public void setNetworkStats(NetworkStats networkStats) { - mBatteryStats.setNetworkStats(networkStats); + mNetworkStats = networkStats; + if (mBatteryStats != null) { + mBatteryStats.setNetworkStats(mNetworkStats); + } } @Override @@ -225,6 +265,7 @@ public class BatteryUsageStatsRule implements TestRule { } private void before() { + lateInitBatteryStats(); HandlerThread bgThread = new HandlerThread("bg thread"); bgThread.start(); mHandler = new Handler(bgThread.getLooper()); diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index be68e9c3c01f..8958fac87bb6 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -31,6 +31,10 @@ android_test { "test-apps/SuspendTestApp/src/**/*.java", ], + + kotlincflags: [ + "-Werror", + ], static_libs: [ "frameworks-base-testutils", "services.accessibility", diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java index 88b2ed4f79c9..071db68704af 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java @@ -16,6 +16,7 @@ package com.android.server.biometrics; +import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS; import static android.Manifest.permission.MANAGE_BIOMETRIC; import static android.Manifest.permission.TEST_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; @@ -491,6 +492,22 @@ public class AuthServiceTest { } @Test + public void testRegisterAuthenticationStateListener_callsFaceService() throws Exception { + mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS); + setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */); + + mAuthService = new AuthService(mContext, mInjector); + mAuthService.onStart(); + + final AuthenticationStateListener listener = mock(AuthenticationStateListener.class); + + mAuthService.mImpl.registerAuthenticationStateListener(listener); + + waitForIdle(); + verify(mFaceService).registerAuthenticationStateListener(eq(listener)); + } + + @Test public void testRegisterKeyguardCallback_callsBiometricServiceRegisterKeyguardCallback() throws Exception { setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java index 3a3dd6ea2746..f8b5b04294cd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java @@ -16,6 +16,7 @@ package com.android.server.biometrics.sensors.face.aidl; +import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT; import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT; @@ -49,6 +50,7 @@ import android.os.IBinder; import android.os.PowerManager; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.TestableContext; import androidx.test.filters.SmallTest; @@ -58,6 +60,7 @@ import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.OperationContextExt; import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutTracker; @@ -81,6 +84,8 @@ import java.util.function.Consumer; @SmallTest public class FaceAuthenticationClientTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final int USER_ID = 12; private static final long OP_ID = 32; private static final int WAKE_REASON = WakeReason.LIFT; @@ -105,6 +110,8 @@ public class FaceAuthenticationClientTest { @Mock private ClientMonitorCallback mCallback; @Mock + private AuthenticationStateListeners mAuthenticationStateListeners; + @Mock private AidlResponseHandler mAidlResponseHandler; @Mock private ActivityTaskManager mActivityTaskManager; @@ -264,6 +271,29 @@ public class FaceAuthenticationClientTest { verify(mHal, never()).authenticate(anyInt()); } + @Test + public void testAuthenticationStateListeners_onAuthenticationSucceeded() + throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS); + final FaceAuthenticationClient client = createClient(); + client.start(mCallback); + client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */), + true /* authenticated */, new ArrayList<>()); + + verify(mAuthenticationStateListeners).onAuthenticationSucceeded(anyInt(), anyInt()); + } + + @Test + public void testAuthenticationStateListeners_onAuthenticationFailed() throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS); + final FaceAuthenticationClient client = createClient(); + client.start(mCallback); + client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */), + false /* authenticated */, new ArrayList<>()); + + verify(mAuthenticationStateListeners).onAuthenticationFailed(anyInt(), anyInt()); + } + private FaceAuthenticationClient createClient() throws RemoteException { return createClient(2 /* version */, mClientMonitorCallbackConverter, false /* allowBackgroundAuthentication */, @@ -311,7 +341,8 @@ public class FaceAuthenticationClientTest { false /* requireConfirmation */, mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, mUsageStats, lockoutTracker, allowBackgroundAuthentication, - null /* sensorPrivacyManager */, 0 /* biometricStrength */) { + null /* sensorPrivacyManager */, 0 /* biometricStrength */, + mAuthenticationStateListeners) { @Override protected ActivityTaskManager getActivityTaskManager() { return mActivityTaskManager; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java index 772ec8b73393..7648bd17f53c 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java @@ -51,6 +51,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.server.biometrics.Flags; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; @@ -89,6 +90,8 @@ public class FaceProviderTest { private BiometricContext mBiometricContext; @Mock private BiometricStateCallback mBiometricStateCallback; + @Mock + private AuthenticationStateListeners mAuthenticationStateListeners; private final TestLooper mLooper = new TestLooper(); private SensorProps[] mSensorProps; @@ -119,8 +122,8 @@ public class FaceProviderTest { mLockoutResetDispatcher = new LockoutResetDispatcher(mContext); mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback, - mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext, - mDaemon, new Handler(mLooper.getLooper()), + mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher, + mBiometricContext, mDaemon, new Handler(mLooper.getLooper()), false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */); } @@ -154,7 +157,7 @@ public class FaceProviderTest { final HidlFaceSensorConfig[] hidlFaceSensorConfig = new HidlFaceSensorConfig[]{faceSensorConfig}; mFaceProvider = new FaceProvider(mContext, - mBiometricStateCallback, hidlFaceSensorConfig, TAG, + mBiometricStateCallback, mAuthenticationStateListeners, hidlFaceSensorConfig, TAG, mLockoutResetDispatcher, mBiometricContext, mDaemon, new Handler(mLooper.getLooper()), true /* resetLockoutRequiresChallenge */, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java index e558c4d64180..78c1e08ba832 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java @@ -44,6 +44,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.sensors.AuthenticationStateListeners; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.BiometricStateCallback; import com.android.server.biometrics.sensors.LockoutResetDispatcher; @@ -81,6 +82,8 @@ public class Face10Test { private BiometricContext mBiometricContext; @Mock private BiometricStateCallback mBiometricStateCallback; + @Mock + private AuthenticationStateListeners mAuthenticationStateListeners; private final Handler mHandler = new Handler(Looper.getMainLooper()); private LockoutResetDispatcher mLockoutResetDispatcher; @@ -116,8 +119,8 @@ public class Face10Test { Face10.sSystemClock = Clock.fixed( Instant.ofEpochMilli(100), ZoneId.of("America/Los_Angeles")); - mFace10 = new Face10(mContext, mBiometricStateCallback, sensorProps, - mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext); + mFace10 = new Face10(mContext, mBiometricStateCallback, mAuthenticationStateListeners, + sensorProps, mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext); mBinder = new Binder(); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 774ea5bc6b16..4ed6f74d30fa 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -16,6 +16,7 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED; import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR; @@ -451,6 +452,29 @@ public class FingerprintAuthenticationClientTest { } @Test + public void testAuthenticationStateListeners_onAuthenticationSucceeded() + throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS); + final FingerprintAuthenticationClient client = createClient(); + client.start(mCallback); + client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, + 2 /* deviceId */), true /* authenticated */, new ArrayList<>()); + + verify(mAuthenticationStateListeners).onAuthenticationSucceeded(anyInt(), anyInt()); + } + + @Test + public void testAuthenticationStateListeners_onAuthenticationFailed() throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS); + final FingerprintAuthenticationClient client = createClient(); + client.start(mCallback); + client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, + 2 /* deviceId */), false /* authenticated */, new ArrayList<>()); + + verify(mAuthenticationStateListeners).onAuthenticationFailed(anyInt(), anyInt()); + } + + @Test public void cancelsAuthWhenNotInForeground() throws Exception { final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo(); topTask.topActivity = new ComponentName("other", "thing"); diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt index 10f27ca02aaa..72fa949301cc 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt +++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt @@ -80,7 +80,7 @@ class OverlayActorEnforcerTests { @BeforeClass @JvmStatic fun checkAllCasesUniquelyNamed() { - val duplicateCaseNames = CASES.mapIndexed { caseIndex, testCase -> + val duplicateCaseNames = CASES.mapIndexed { _, testCase -> testCase.failures.map { makeTestName(testCase, it.first, Params.Type.FAILURE) } + testCase.allowed.map { diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt index 150822bdff6b..c07c4d7618b7 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt @@ -18,12 +18,13 @@ package com.android.server.systemconfig import android.content.Context import android.util.Xml -import androidx.test.InstrumentationRegistry +import androidx.test.platform.app.InstrumentationRegistry import com.android.server.SystemConfig import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThrows import org.junit.Rule import org.junit.Test -import org.junit.rules.ExpectedException import org.junit.rules.TemporaryFolder class SystemConfigNamedActorTest { @@ -37,14 +38,11 @@ class SystemConfigNamedActorTest { private const val PACKAGE_TWO = "com.test.actor.two" } - private val context: Context = InstrumentationRegistry.getContext() + private val context: Context = InstrumentationRegistry.getInstrumentation().context @get:Rule val tempFolder = TemporaryFolder(context.filesDir) - @get:Rule - val expected = ExpectedException.none() - private var uniqueCounter = 0 @Test @@ -193,11 +191,9 @@ class SystemConfigNamedActorTest { </config> """.write() - expected.expect(IllegalStateException::class.java) - expected.expectMessage("Defining $ACTOR_ONE as $PACKAGE_ONE " + + val exc = assertThrows(IllegalStateException::class.java) { assertPermissions() } + assertEquals(exc.message, "Defining $ACTOR_ONE as $PACKAGE_ONE " + "for the android namespace is not allowed") - - assertPermissions() } @Test @@ -217,11 +213,9 @@ class SystemConfigNamedActorTest { </config> """.write() - expected.expect(IllegalStateException::class.java) - expected.expectMessage("Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" + + val exc = assertThrows(IllegalStateException::class.java) { assertPermissions() } + assertEquals(exc.message, "Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" + " defined as both $PACKAGE_ONE and $PACKAGE_TWO") - - assertPermissions() } private fun String.write() = tempFolder.root.resolve("${uniqueCounter++}.xml") @@ -230,5 +224,5 @@ class SystemConfigNamedActorTest { private fun assertPermissions() = SystemConfig(false).apply { val parser = Xml.newPullParser() readPermissions(parser, tempFolder.root, 0) - }. let { assertThat(it.namedActors) } + }.let { assertThat(it.namedActors) } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java index bfd2df2d2b7d..e75afccfdfdf 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -27,12 +27,13 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.media.AudioAttributes.USAGE_NOTIFICATION; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.any; @@ -43,6 +44,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.after; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -59,7 +61,10 @@ import android.app.Notification.Builder; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.graphics.Color; @@ -80,6 +85,7 @@ import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; +import android.util.Pair; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; @@ -100,6 +106,7 @@ import com.android.server.pm.PackageManagerService; import java.util.List; import java.util.Objects; +import java.util.Set; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -132,6 +139,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { KeyguardManager mKeyguardManager; @Mock private UserManager mUserManager; + @Mock + private PackageManager mPackageManager; NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( 1 << 30); @@ -171,11 +180,14 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { private static final int CUSTOM_LIGHT_OFF = 10000; private static final int MAX_VIBRATION_DELAY = 1000; private static final float DEFAULT_VOLUME = 1.0f; + private BroadcastReceiver mAvalancheBroadcastReceiver; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); getContext().addMockSystemService(Vibrator.class, mVibrator); + getContext().addMockSystemService(PackageManager.class, mPackageManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false); when(mAudioManager.isAudioFocusExclusive()).thenReturn(false); when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer); @@ -214,8 +226,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { private void initAttentionHelper(TestableFlagResolver flagResolver) { mAttentionHelper = new NotificationAttentionHelper(getContext(), mock(LightsManager.class), - mAccessibilityManager, getContext().getPackageManager(), mUserManager, mUsageStats, - mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver); + mAccessibilityManager, mPackageManager, mUserManager, mUsageStats, + mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver); + mAttentionHelper.onSystemReady(); mAttentionHelper.setVibratorHelper(spy(new VibratorHelper(getContext()))); mAttentionHelper.setAudioManager(mAudioManager); mAttentionHelper.setSystemReady(true); @@ -226,6 +239,29 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { mAttentionHelper.setScreenOn(false); mAttentionHelper.setInCallStateOffHook(false); mAttentionHelper.mNotificationPulseEnabled = true; + + if (Flags.crossAppPoliteNotifications()) { + // Capture BroadcastReceiver for avalanche triggers + ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + ArgumentCaptor<IntentFilter> intentFilterCaptor = + ArgumentCaptor.forClass(IntentFilter.class); + verify(getContext(), atLeastOnce()).registerReceiverAsUser( + broadcastReceiverCaptor.capture(), + any(), intentFilterCaptor.capture(), any(), any()); + List<BroadcastReceiver> broadcastReceivers = broadcastReceiverCaptor.getAllValues(); + List<IntentFilter> intentFilters = intentFilterCaptor.getAllValues(); + + assertThat(broadcastReceivers.size()).isAtLeast(1); + assertThat(intentFilters.size()).isAtLeast(1); + for (int i = 0; i < intentFilters.size(); i++) { + final IntentFilter filter = intentFilters.get(i); + if (filter.hasAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { + mAvalancheBroadcastReceiver = broadcastReceivers.get(i); + } + } + assertThat(mAvalancheBroadcastReceiver).isNotNull(); + } } // @@ -2040,7 +2076,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { } @Test - public void testBeepVolume_politeNotif_GlobalStrategy() throws Exception { + public void testBeepVolume_politeNotif_AvalancheStrategy() throws Exception { mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); TestableFlagResolver flagResolver = new TestableFlagResolver(); @@ -2048,6 +2084,11 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); initAttentionHelper(flagResolver); + // Trigger avalanche trigger intent + final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", false); + mAvalancheBroadcastReceiver.onReceive(getContext(), intent); + NotificationRecord r = getBeepyNotification(); // set up internal state @@ -2078,7 +2119,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { } @Test - public void testBeepVolume_politeNotif_GlobalStrategy_ChannelHasUserSound() throws Exception { + public void testBeepVolume_politeNotif_AvalancheStrategy_ChannelHasUserSound() + throws Exception { mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); TestableFlagResolver flagResolver = new TestableFlagResolver(); @@ -2086,6 +2128,11 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); initAttentionHelper(flagResolver); + // Trigger avalanche trigger intent + final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", false); + mAvalancheBroadcastReceiver.onReceive(getContext(), intent); + NotificationRecord r = getBeepyNotification(); // set up internal state @@ -2364,6 +2411,82 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } + @Test + public void testAvalancheStrategyTriggers() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + final int avalancheTimeoutMs = 100; + flagResolver.setFlagOverride(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT, avalancheTimeoutMs); + initAttentionHelper(flagResolver); + + // Trigger avalanche trigger intents + for (String intentAction + : NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) { + // Set the action and extras to trigger the avalanche strategy + Intent intent = new Intent(intentAction); + Pair<String, Boolean> extras = + NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS + .get(intentAction); + if (extras != null) { + intent.putExtra(extras.first, extras.second); + } + mAvalancheBroadcastReceiver.onReceive(getContext(), intent); + assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isTrue(); + + // Wait for avalanche timeout + Thread.sleep(avalancheTimeoutMs + 1); + + // Check that avalanche strategy is inactive + assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse(); + } + } + + @Test + public void testAvalancheStrategyTriggers_disabledExtras() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + initAttentionHelper(flagResolver); + + for (String intentAction + : NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) { + Intent intent = new Intent(intentAction); + Pair<String, Boolean> extras = + NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS + .get(intentAction); + // Test only for intents with extras + if (extras != null) { + // Set the action extras to NOT trigger the avalanche strategy + intent.putExtra(extras.first, !extras.second); + mAvalancheBroadcastReceiver.onReceive(getContext(), intent); + // Check that avalanche strategy is inactive + assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse(); + } + } + } + + @Test + public void testAvalancheStrategyTriggers_nonAvalancheIntents() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + initAttentionHelper(flagResolver); + + // Broadcast intents that are not avalanche triggers + final Set<String> notAvalancheTriggerIntents = Set.of( + Intent.ACTION_USER_ADDED, + Intent.ACTION_SCREEN_ON, + Intent.ACTION_POWER_CONNECTED + ); + for (String intentAction : notAvalancheTriggerIntents) { + Intent intent = new Intent(intentAction); + mAvalancheBroadcastReceiver.onReceive(getContext(), intent); + // Check that avalanche strategy is inactive + assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse(); + } + } + static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> { private final int mRepeatIndex; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 2a89b02482b3..31d6fa3e91f8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -3722,6 +3722,68 @@ public class ActivityRecordTests extends WindowTestsBase { assertFalse(ar.moveFocusableActivityToTop("test")); } + @Test + public void testPauseConfigDispatch() throws RemoteException { + final Task task = new TaskBuilder(mSupervisor) + .setDisplay(mDisplayContent).setCreateActivity(true).build(); + final ActivityRecord activity = task.getTopNonFinishingActivity(); + final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams( + TYPE_BASE_APPLICATION); + attrs.setTitle("AppWindow"); + final TestWindowState appWindow = createWindowState(attrs, activity); + activity.addWindow(appWindow); + + clearInvocations(mClientLifecycleManager); + clearInvocations(activity); + + Configuration ro = activity.getRequestedOverrideConfiguration(); + ro.windowConfiguration.setBounds(new Rect(20, 0, 120, 200)); + activity.onRequestedOverrideConfigurationChanged(ro); + activity.ensureActivityConfiguration(); + mWm.mRoot.performSurfacePlacement(); + + // policy will center the bounds, so just check for matching size here. + assertEquals(100, activity.getWindowConfiguration().getBounds().width()); + assertEquals(100, appWindow.getWindowConfiguration().getBounds().width()); + // No scheduled transactions since it asked for a restart. + verify(mClientLifecycleManager, times(1)).scheduleTransaction(any()); + verify(activity, times(1)).setLastReportedConfiguration(any(), any()); + assertTrue(appWindow.mResizeReported); + + // act like everything drew and went idle + appWindow.mResizeReported = false; + makeLastConfigReportedToClient(appWindow, true); + + // Now pause dispatch and try to resize + activity.pauseConfigurationDispatch(); + + ro.windowConfiguration.setBounds(new Rect(20, 0, 150, 200)); + activity.onRequestedOverrideConfigurationChanged(ro); + activity.ensureActivityConfiguration(); + mWm.mRoot.performSurfacePlacement(); + + // Activity should get new config (core-side) + assertEquals(130, activity.getWindowConfiguration().getBounds().width()); + // But windows should not get new config. + assertEquals(100, appWindow.getWindowConfiguration().getBounds().width()); + // The client shouldn't receive any changes + verify(mClientLifecycleManager, times(1)).scheduleTransaction(any()); + // and lastReported shouldn't be set. + verify(activity, times(1)).setLastReportedConfiguration(any(), any()); + // There should be no resize reported to client. + assertFalse(appWindow.mResizeReported); + + // Now resume dispatch + activity.resumeConfigurationDispatch(); + mWm.mRoot.performSurfacePlacement(); + + // Windows and client should now receive updates + verify(activity, times(2)).setLastReportedConfiguration(any(), any()); + verify(mClientLifecycleManager, times(2)).scheduleTransaction(any()); + assertEquals(130, appWindow.getWindowConfiguration().getBounds().width()); + assertTrue(appWindow.mResizeReported); + } + private ICompatCameraControlCallback getCompatCameraControlCallback() { return new ICompatCameraControlCallback.Stub() { @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 6759eef2066d..752dc5e8e7f6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -4794,6 +4794,7 @@ public class SizeCompatTests extends WindowTestsBase { new WindowManager.LayoutParams(TYPE_STATUS_BAR); final Binder owner = new Binder(); attrs.gravity = android.view.Gravity.TOP; + attrs.height = STATUS_BAR_HEIGHT; attrs.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; attrs.setFitInsetsTypes(0 /* types */); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 7c7e562426c2..245b2c5a7d6b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -444,6 +444,10 @@ public class TaskFragmentTest extends WindowTestsBase { // Not ready if the task is still visible when the TaskFragment becomes empty. doReturn(true).when(task).isVisibleRequested(); assertFalse(taskFragment.isReadyToTransit()); + + // Ready if the mAllowTransitionWhenEmpty flag is true. + taskFragment.setAllowTransitionWhenEmpty(true); + assertTrue(taskFragment.isReadyToTransit()); } @Test diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java index fe699af86f1d..a14078697c71 100644 --- a/telecomm/java/android/telecom/CallControl.java +++ b/telecomm/java/android/telecom/CallControl.java @@ -21,7 +21,6 @@ import static android.telecom.CallException.TRANSACTION_EXCEPTION_KEY; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.SuppressLint; import android.os.Binder; import android.os.Bundle; @@ -31,7 +30,6 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.text.TextUtils; -import com.android.internal.telecom.ClientTransactionalServiceRepository; import com.android.internal.telecom.ICallControl; import com.android.server.telecom.flags.Flags; @@ -52,20 +50,13 @@ import java.util.concurrent.Executor; @SuppressLint("NotCloseable") public final class CallControl { private static final String TAG = CallControl.class.getSimpleName(); - private static final String INTERFACE_ERROR_MSG = "Call Control is not available"; private final String mCallId; private final ICallControl mServerInterface; - private final PhoneAccountHandle mPhoneAccountHandle; - private final ClientTransactionalServiceRepository mRepository; /** @hide */ - public CallControl(@NonNull String callId, @Nullable ICallControl serverInterface, - @NonNull ClientTransactionalServiceRepository repository, - @NonNull PhoneAccountHandle pah) { + public CallControl(@NonNull String callId, @NonNull ICallControl serverInterface) { mCallId = callId; mServerInterface = serverInterface; - mRepository = repository; - mPhoneAccountHandle = pah; } /** @@ -97,16 +88,14 @@ public final class CallControl { */ public void setActive(@CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver<Void, CallException> callback) { - if (mServerInterface != null) { - try { - mServerInterface.setActive(mCallId, - new CallControlResultReceiver("setActive", executor, callback)); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + try { + mServerInterface.setActive(mCallId, + new CallControlResultReceiver("setActive", executor, callback)); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } else { - throw new IllegalStateException(INTERFACE_ERROR_MSG); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); } } @@ -134,16 +123,12 @@ public final class CallControl { validateVideoState(videoState); Objects.requireNonNull(executor); Objects.requireNonNull(callback); - if (mServerInterface != null) { - try { - mServerInterface.answer(videoState, mCallId, - new CallControlResultReceiver("answer", executor, callback)); + try { + mServerInterface.answer(videoState, mCallId, + new CallControlResultReceiver("answer", executor, callback)); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } else { - throw new IllegalStateException(INTERFACE_ERROR_MSG); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); } } @@ -165,16 +150,14 @@ public final class CallControl { */ public void setInactive(@CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver<Void, CallException> callback) { - if (mServerInterface != null) { - try { - mServerInterface.setInactive(mCallId, - new CallControlResultReceiver("setInactive", executor, callback)); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + try { + mServerInterface.setInactive(mCallId, + new CallControlResultReceiver("setInactive", executor, callback)); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } else { - throw new IllegalStateException(INTERFACE_ERROR_MSG); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); } } @@ -213,15 +196,11 @@ public final class CallControl { Objects.requireNonNull(executor); Objects.requireNonNull(callback); validateDisconnectCause(disconnectCause); - if (mServerInterface != null) { - try { - mServerInterface.disconnect(mCallId, disconnectCause, - new CallControlResultReceiver("disconnect", executor, callback)); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } else { - throw new IllegalStateException(INTERFACE_ERROR_MSG); + try { + mServerInterface.disconnect(mCallId, disconnectCause, + new CallControlResultReceiver("disconnect", executor, callback)); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); } } @@ -245,15 +224,13 @@ public final class CallControl { */ public void startCallStreaming(@CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver<Void, CallException> callback) { - if (mServerInterface != null) { - try { - mServerInterface.startCallStreaming(mCallId, - new CallControlResultReceiver("startCallStreaming", executor, callback)); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } else { - throw new IllegalStateException(INTERFACE_ERROR_MSG); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + try { + mServerInterface.startCallStreaming(mCallId, + new CallControlResultReceiver("startCallStreaming", executor, callback)); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); } } @@ -281,15 +258,11 @@ public final class CallControl { Objects.requireNonNull(callEndpoint); Objects.requireNonNull(executor); Objects.requireNonNull(callback); - if (mServerInterface != null) { - try { - mServerInterface.requestCallEndpointChange(callEndpoint, - new CallControlResultReceiver("endpointChange", executor, callback)); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } else { - throw new IllegalStateException(INTERFACE_ERROR_MSG); + try { + mServerInterface.requestCallEndpointChange(callEndpoint, + new CallControlResultReceiver("requestCallEndpointChange", executor, callback)); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); } } @@ -313,20 +286,16 @@ public final class CallControl { * passed that details why the operation failed. */ @FlaggedApi(Flags.FLAG_SET_MUTE_STATE) - public void setMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor, + public void requestMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor, @NonNull OutcomeReceiver<Void, CallException> callback) { Objects.requireNonNull(executor); Objects.requireNonNull(callback); - if (mServerInterface != null) { - try { - mServerInterface.setMuteState(isMuted, - new CallControlResultReceiver("setMuteState", executor, callback)); + try { + mServerInterface.setMuteState(isMuted, + new CallControlResultReceiver("requestMuteState", executor, callback)); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } else { - throw new IllegalStateException(INTERFACE_ERROR_MSG); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); } } @@ -352,14 +321,10 @@ public final class CallControl { public void sendEvent(@NonNull String event, @NonNull Bundle extras) { Objects.requireNonNull(event); Objects.requireNonNull(extras); - if (mServerInterface != null) { - try { - mServerInterface.sendEvent(mCallId, event, extras); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } else { - throw new IllegalStateException(INTERFACE_ERROR_MSG); + try { + mServerInterface.sendEvent(mCallId, event, extras); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); } } diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index a089f5c9d641..63db29713825 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -580,6 +580,9 @@ public final class PhoneAccount implements Parcelable { mExtras = phoneAccount.getExtras(); mGroupId = phoneAccount.getGroupId(); mSupportedAudioRoutes = phoneAccount.getSupportedAudioRoutes(); + if (phoneAccount.hasSimultaneousCallingRestriction()) { + mSimultaneousCallingRestriction = phoneAccount.getSimultaneousCallingRestriction(); + } } /** diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java index 71e9184b7c54..467e89c78810 100644 --- a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java +++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java @@ -208,8 +208,7 @@ public class ClientTransactionalServiceWrapper { if (resultCode == TELECOM_TRANSACTION_SUCCESS) { // create the interface object that the client will interact with - CallControl control = new CallControl(callId, callControl, mRepository, - mPhoneAccountHandle); + CallControl control = new CallControl(callId, callControl); // give the client the object via the OR that was passed into addCall pendingControl.onResult(control); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 1badf674c8ce..a73c46b12c53 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9430,16 +9430,6 @@ public class CarrierConfigManager { "missed_incoming_call_sms_originator_string_array"; /** - * String array of Apn Type configurations. - * The entries should be of form "APN_TYPE_NAME:priority". - * priority is an integer that is sorted from highest to lowest. - * example: cbs:5 - * - * @hide - */ - public static final String KEY_APN_PRIORITY_STRING_ARRAY = "apn_priority_string_array"; - - /** * Network capability priority for determine the satisfy order in telephony. The priority is * from the lowest 0 to the highest 100. The long-lived network shall have the lowest priority. * This allows other short-lived requests like MMS requests to be established. Emergency request @@ -10755,17 +10745,14 @@ public class CarrierConfigManager { TimeUnit.DAYS.toMillis(1)); sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY, new String[0]); - sDefaults.putStringArray(KEY_APN_PRIORITY_STRING_ARRAY, new String[] { - "enterprise:0", "default:1", "mms:2", "supl:2", "dun:2", "hipri:3", "fota:2", - "ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3" - }); // Do not modify the priority unless you know what you are doing. This will have significant // impacts on the order of data network setup. sDefaults.putStringArray( KEY_TELEPHONY_NETWORK_CAPABILITY_PRIORITIES_STRING_ARRAY, new String[] { "eims:90", "supl:80", "mms:70", "xcap:70", "cbs:50", "mcx:50", "fota:50", - "ims:40", "dun:30", "enterprise:20", "internet:20" + "ims:40", "rcs:40", "dun:30", "enterprise:20", "internet:20", + "prioritize_bandwidth:20", "prioritize_latency:20" }); sDefaults.putStringArray( KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY, new String[] { @@ -10777,9 +10764,10 @@ public class CarrierConfigManager { // registration state changes) retry can still happen. "permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|" + "-3|65543|65547|2252|2253|2254, retry_interval=2500", - "capabilities=mms|supl|cbs, retry_interval=2000", - "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|" - + "5000|10000|15000|20000|40000|60000|120000|240000|" + "capabilities=mms|supl|cbs|rcs, retry_interval=2000", + "capabilities=internet|enterprise|dun|ims|fota|xcap|mcx|" + + "prioritize_bandwidth|prioritize_latency, retry_interval=" + + "2500|3000|5000|10000|15000|20000|40000|60000|120000|240000|" + "600000|1200000|1800000, maximum_retries=20" }); sDefaults.putStringArray( diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 70047a6feb9c..a1ac477d3519 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -34,7 +34,6 @@ import android.os.ICancellationSignal; import android.os.OutcomeReceiver; import android.os.RemoteException; import android.os.ResultReceiver; -import android.os.ServiceSpecificException; import android.telephony.SubscriptionManager; import android.telephony.TelephonyCallback; import android.telephony.TelephonyFrameworkInitializer; @@ -336,6 +335,12 @@ public final class SatelliteManager { @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_MODEM_BUSY = 22; + /** + * Telephony process is not currently available or satellite is not supported. + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23; + /** @hide */ @IntDef(prefix = {"SATELLITE_RESULT_"}, value = { SATELLITE_RESULT_SUCCESS, @@ -360,7 +365,8 @@ public final class SatelliteManager { SATELLITE_RESULT_NOT_AUTHORIZED, SATELLITE_RESULT_NOT_SUPPORTED, SATELLITE_RESULT_REQUEST_IN_PROGRESS, - SATELLITE_RESULT_MODEM_BUSY + SATELLITE_RESULT_MODEM_BUSY, + SATELLITE_RESULT_ILLEGAL_STATE }) @Retention(RetentionPolicy.SOURCE) public @interface SatelliteResult {} @@ -510,7 +516,7 @@ public final class SatelliteManager { } } catch (RemoteException ex) { Rlog.e(TAG, "requestSatelliteEnabled() RemoteException: ", ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -526,7 +532,6 @@ public final class SatelliteManager { * will return a {@link SatelliteException} with the {@link SatelliteResult}. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -561,11 +566,12 @@ public final class SatelliteManager { }; telephony.requestIsSatelliteEnabled(mSubId, receiver); } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { loge("requestIsSatelliteEnabled() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -581,7 +587,6 @@ public final class SatelliteManager { * will return a {@link SatelliteException} with the {@link SatelliteResult}. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -616,11 +621,12 @@ public final class SatelliteManager { }; telephony.requestIsDemoModeEnabled(mSubId, receiver); } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { loge("requestIsDemoModeEnabled() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -639,8 +645,6 @@ public final class SatelliteManager { * service is supported on the device and {@code false} otherwise. * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} * will return a {@link SatelliteException} with the {@link SatelliteResult}. - * - * @throws IllegalStateException if the Telephony process is not currently available. */ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor, @@ -674,11 +678,12 @@ public final class SatelliteManager { }; telephony.requestIsSatelliteSupported(mSubId, receiver); } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { loge("requestIsSatelliteSupported() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -693,7 +698,6 @@ public final class SatelliteManager { * will return a {@link SatelliteException} with the {@link SatelliteResult}. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -729,11 +733,12 @@ public final class SatelliteManager { }; telephony.requestSatelliteCapabilities(mSubId, receiver); } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { loge("requestSatelliteCapabilities() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -959,7 +964,6 @@ public final class SatelliteManager { * @param callback The callback to notify of satellite transmission updates. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -1009,11 +1013,12 @@ public final class SatelliteManager { telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback, internalCallback); } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { loge("startSatelliteTransmissionUpdates() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -1029,7 +1034,6 @@ public final class SatelliteManager { * @param resultListener Listener for the {@link SatelliteResult} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -1063,11 +1067,12 @@ public final class SatelliteManager { () -> resultListener.accept(SATELLITE_RESULT_INVALID_ARGUMENTS))); } } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { loge("stopSatelliteTransmissionUpdates() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -1112,11 +1117,12 @@ public final class SatelliteManager { cancelRemote = telephony.provisionSatelliteService(mSubId, token, provisionData, errorCallback); } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { loge("provisionSatelliteService() RemoteException=" + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } if (cancellationSignal != null) { cancellationSignal.setRemote(cancelRemote); @@ -1138,7 +1144,6 @@ public final class SatelliteManager { * @param resultListener Listener for the {@link SatelliteResult} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -1161,11 +1166,12 @@ public final class SatelliteManager { }; telephony.deprovisionSatelliteService(mSubId, token, errorCallback); } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { loge("deprovisionSatelliteService() RemoteException=" + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -1208,7 +1214,7 @@ public final class SatelliteManager { } } catch (RemoteException ex) { loge("registerForSatelliteProvisionStateChanged() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } return SATELLITE_RESULT_REQUEST_FAILED; } @@ -1244,7 +1250,7 @@ public final class SatelliteManager { } } catch (RemoteException ex) { loge("unregisterForSatelliteProvisionStateChanged() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -1260,7 +1266,6 @@ public final class SatelliteManager { * will return a {@link SatelliteException} with the {@link SatelliteResult}. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -1295,11 +1300,12 @@ public final class SatelliteManager { }; telephony.requestIsSatelliteProvisioned(mSubId, receiver); } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { loge("requestIsSatelliteProvisioned() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -1340,7 +1346,7 @@ public final class SatelliteManager { } } catch (RemoteException ex) { loge("registerForSatelliteModemStateChanged() RemoteException:" + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } return SATELLITE_RESULT_REQUEST_FAILED; } @@ -1376,7 +1382,7 @@ public final class SatelliteManager { } } catch (RemoteException ex) { loge("unregisterForSatelliteModemStateChanged() RemoteException:" + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -1436,7 +1442,7 @@ public final class SatelliteManager { } } catch (RemoteException ex) { loge("registerForSatelliteDatagram() RemoteException:" + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } return SATELLITE_RESULT_REQUEST_FAILED; } @@ -1471,7 +1477,7 @@ public final class SatelliteManager { } } catch (RemoteException ex) { loge("unregisterForSatelliteDatagram() RemoteException:" + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -1488,7 +1494,6 @@ public final class SatelliteManager { * @param resultListener Listener for the {@link SatelliteResult} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -1509,11 +1514,12 @@ public final class SatelliteManager { }; telephony.pollPendingSatelliteDatagrams(mSubId, internalCallback); } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { loge("pollPendingSatelliteDatagrams() RemoteException:" + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -1541,7 +1547,6 @@ public final class SatelliteManager { * @param resultListener Listener for the {@link SatelliteResult} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -1566,11 +1571,12 @@ public final class SatelliteManager { telephony.sendSatelliteDatagram(mSubId, datagramType, datagram, needFullScreenPointingUI, internalCallback); } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { loge("sendSatelliteDatagram() RemoteException:" + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -1587,7 +1593,6 @@ public final class SatelliteManager { * will return a {@link SatelliteException} with the {@link SatelliteResult}. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -1624,12 +1629,13 @@ public final class SatelliteManager { telephony.requestIsSatelliteCommunicationAllowedForCurrentLocation(mSubId, receiver); } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { loge("requestIsSatelliteCommunicationAllowedForCurrentLocation() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -1645,7 +1651,6 @@ public final class SatelliteManager { * will return a {@link SatelliteException} with the {@link SatelliteResult}. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -1681,11 +1686,12 @@ public final class SatelliteManager { }; telephony.requestTimeForNextSatelliteVisibility(mSubId, receiver); } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { loge("requestTimeForNextSatelliteVisibility() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -1713,7 +1719,7 @@ public final class SatelliteManager { } } catch (RemoteException ex) { loge("informDeviceAlignedToSatellite() RemoteException:" + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -1727,7 +1733,7 @@ public final class SatelliteManager { * <ul> * <li>Users want to enable it.</li> * <li>There is no satellite communication restriction, which is added by - * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}</li> + * {@link #addSatelliteAttachRestrictionForCarrier(int, int, Executor, Consumer)}</li> * <li>The carrier config {@link * android.telephony.CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} is set to * {@code true}.</li> @@ -1739,7 +1745,6 @@ public final class SatelliteManager { * @param resultListener Listener for the {@link SatelliteResult} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. * @throws IllegalArgumentException if the subscription is invalid. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @@ -1799,7 +1804,6 @@ public final class SatelliteManager { * @param resultListener Listener for the {@link SatelliteResult} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. * @throws IllegalArgumentException if the subscription is invalid. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @@ -1824,11 +1828,12 @@ public final class SatelliteManager { }; telephony.addSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback); } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { loge("addSatelliteAttachRestrictionForCarrier() RemoteException:" + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -1842,7 +1847,6 @@ public final class SatelliteManager { * @param resultListener Listener for the {@link SatelliteResult} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. * @throws IllegalArgumentException if the subscription is invalid. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @@ -1867,17 +1871,18 @@ public final class SatelliteManager { }; telephony.removeSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback); } else { - throw new IllegalStateException("telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { loge("removeSatelliteAttachRestrictionForCarrier() RemoteException:" + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } /** * Get reasons for disallowing satellite attach, as requested by - * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)} + * {@link #addSatelliteAttachRestrictionForCarrier(int, int, Executor, Consumer)} * * @param subId The subscription ID of the carrier. * @return Set of reasons for disallowing satellite communication. @@ -1910,7 +1915,7 @@ public final class SatelliteManager { } } catch (RemoteException ex) { loge("getSatelliteAttachRestrictionReasonsForCarrier() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } return new HashSet<>(); } @@ -1932,11 +1937,12 @@ public final class SatelliteManager { * The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no * signal strength data available. * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} will return a - * {@link SatelliteException} with the {@link SatelliteResult}. + * {@link SatelliteException} with the {@link SatelliteResult}, or return a + * {@link IllegalStateException} if the Telephony process is not currently available or + * satellite is not supported, or return a {@link RuntimeException} when remote procedure call + * has failed. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available or - * satellite is not supported. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -1972,11 +1978,12 @@ public final class SatelliteManager { }; telephony.requestNtnSignalStrength(mSubId, receiver); } else { - throw new IllegalStateException("Telephony service is null."); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { loge("requestNtnSignalStrength() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -1997,12 +2004,11 @@ public final class SatelliteManager { * * @throws SecurityException if the caller doesn't have required permission. * @throws IllegalStateException if the Telephony process is not currently available. - * @throws SatelliteException if the callback registration operation fails. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void registerForNtnSignalStrengthChanged(@NonNull @CallbackExecutor Executor executor, - @NonNull NtnSignalStrengthCallback callback) throws SatelliteException { + @NonNull NtnSignalStrengthCallback callback) { Objects.requireNonNull(executor); Objects.requireNonNull(callback); @@ -2024,12 +2030,9 @@ public final class SatelliteManager { } else { throw new IllegalStateException("Telephony service is null."); } - } catch (ServiceSpecificException ex) { - logd("registerForNtnSignalStrengthChanged() registration fails: " + ex.errorCode); - throw new SatelliteException(ex.errorCode); } catch (RemoteException ex) { loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -2072,7 +2075,7 @@ public final class SatelliteManager { } } catch (RemoteException ex) { loge("unregisterForNtnSignalStrengthChanged() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -2113,7 +2116,7 @@ public final class SatelliteManager { } } catch (RemoteException ex) { loge("registerForSatelliteCapabilitiesChanged() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } return SATELLITE_RESULT_REQUEST_FAILED; } @@ -2149,7 +2152,7 @@ public final class SatelliteManager { } } catch (RemoteException ex) { loge("unregisterForSatelliteCapabilitiesChanged() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } } @@ -2177,7 +2180,7 @@ public final class SatelliteManager { } } catch (RemoteException ex) { loge("getAllSatellitePlmnsForCarrier() RemoteException: " + ex); - ex.rethrowFromSystemServer(); + ex.rethrowAsRuntimeException(); } return new ArrayList<>(); } diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java index 1ec1d5f307e1..2f6a361e3609 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java +++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java @@ -15,42 +15,181 @@ */ package com.android.hoststubgen.nativesubstitution; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; + +import java.util.Map; +import java.util.Objects; +import java.util.function.Predicate; + public class SystemProperties_host { + private static final Object sLock = new Object(); + + /** Active system property values */ + @GuardedBy("sLock") + private static Map<String, String> sValues; + /** Predicate tested to determine if a given key can be read. */ + @GuardedBy("sLock") + private static Predicate<String> sKeyReadablePredicate; + /** Predicate tested to determine if a given key can be written. */ + @GuardedBy("sLock") + private static Predicate<String> sKeyWritablePredicate; + /** Callback to trigger when values are changed */ + @GuardedBy("sLock") + private static Runnable sChangeCallback; + + /** + * Reverse mapping that provides a way back to an original key from the + * {@link System#identityHashCode(Object)} of {@link String#intern}. + */ + @GuardedBy("sLock") + private static SparseArray<String> sKeyHandles = new SparseArray<>(); + + public static void native_init$ravenwood(Map<String, String> values, + Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate, + Runnable changeCallback) { + synchronized (sLock) { + sValues = Objects.requireNonNull(values); + sKeyReadablePredicate = Objects.requireNonNull(keyReadablePredicate); + sKeyWritablePredicate = Objects.requireNonNull(keyWritablePredicate); + sChangeCallback = Objects.requireNonNull(changeCallback); + sKeyHandles.clear(); + } + } + + public static void native_reset$ravenwood() { + synchronized (sLock) { + sValues = null; + sKeyReadablePredicate = null; + sKeyWritablePredicate = null; + sChangeCallback = null; + sKeyHandles.clear(); + } + } + + public static void native_set(String key, String val) { + synchronized (sLock) { + Objects.requireNonNull(key); + Preconditions.requireNonNullViaRavenwoodRule(sValues); + if (!sKeyWritablePredicate.test(key)) { + throw new IllegalArgumentException( + "Write access to system property '" + key + "' denied via RavenwoodRule"); + } + if (key.startsWith("ro.") && sValues.containsKey(key)) { + throw new IllegalArgumentException( + "System property '" + key + "' already defined once; cannot redefine"); + } + if ((val == null) || val.isEmpty()) { + sValues.remove(key); + } else { + sValues.put(key, val); + } + sChangeCallback.run(); + } + } + public static String native_get(String key, String def) { - throw new RuntimeException("Not implemented yet"); + synchronized (sLock) { + Objects.requireNonNull(key); + Preconditions.requireNonNullViaRavenwoodRule(sValues); + if (!sKeyReadablePredicate.test(key)) { + throw new IllegalArgumentException( + "Read access to system property '" + key + "' denied via RavenwoodRule"); + } + return sValues.getOrDefault(key, def); + } } + public static int native_get_int(String key, int def) { - throw new RuntimeException("Not implemented yet"); + try { + return Integer.parseInt(native_get(key, "")); + } catch (NumberFormatException ignored) { + return def; + } } + public static long native_get_long(String key, long def) { - throw new RuntimeException("Not implemented yet"); + try { + return Long.parseLong(native_get(key, "")); + } catch (NumberFormatException ignored) { + return def; + } } + public static boolean native_get_boolean(String key, boolean def) { - throw new RuntimeException("Not implemented yet"); + return parseBoolean(native_get(key, ""), def); } public static long native_find(String name) { - throw new RuntimeException("Not implemented yet"); + synchronized (sLock) { + Preconditions.requireNonNullViaRavenwoodRule(sValues); + if (sValues.containsKey(name)) { + name = name.intern(); + final int handle = System.identityHashCode(name); + sKeyHandles.put(handle, name); + return handle; + } else { + return 0; + } + } } + public static String native_get(long handle) { - throw new RuntimeException("Not implemented yet"); + synchronized (sLock) { + return native_get(sKeyHandles.get((int) handle), ""); + } } + public static int native_get_int(long handle, int def) { - throw new RuntimeException("Not implemented yet"); + synchronized (sLock) { + return native_get_int(sKeyHandles.get((int) handle), def); + } } + public static long native_get_long(long handle, long def) { - throw new RuntimeException("Not implemented yet"); + synchronized (sLock) { + return native_get_long(sKeyHandles.get((int) handle), def); + } } + public static boolean native_get_boolean(long handle, boolean def) { - throw new RuntimeException("Not implemented yet"); - } - public static void native_set(String key, String def) { - throw new RuntimeException("Not implemented yet"); + synchronized (sLock) { + return native_get_boolean(sKeyHandles.get((int) handle), def); + } } + public static void native_add_change_callback() { - throw new RuntimeException("Not implemented yet"); + // Ignored; callback always registered via init above } + public static void native_report_sysprop_change() { - throw new RuntimeException("Not implemented yet"); + // Report through callback always registered via init above + synchronized (sLock) { + Preconditions.requireNonNullViaRavenwoodRule(sValues); + sChangeCallback.run(); + } + } + + private static boolean parseBoolean(String val, boolean def) { + // Matches system/libbase/include/android-base/parsebool.h + if (val == null) return def; + switch (val) { + case "1": + case "on": + case "true": + case "y": + case "yes": + return true; + case "0": + case "false": + case "n": + case "no": + case "off": + return false; + default: + return def; + } } } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt index 8ca4732f57c4..76bac9286a1f 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt @@ -24,6 +24,7 @@ class AndroidHeuristicsFilter( private val classes: ClassNodes, val aidlPolicy: FilterPolicyWithReason?, val featureFlagsPolicy: FilterPolicyWithReason?, + val syspropsPolicy: FilterPolicyWithReason?, fallback: OutputFilter ) : DelegatingFilter(fallback) { override fun getPolicyForClass(className: String): FilterPolicyWithReason { @@ -33,6 +34,9 @@ class AndroidHeuristicsFilter( if (featureFlagsPolicy != null && classes.isFeatureFlagsClass(className)) { return featureFlagsPolicy } + if (syspropsPolicy != null && classes.isSyspropsClass(className)) { + return syspropsPolicy + } return super.getPolicyForClass(className) } } @@ -57,3 +61,13 @@ private fun ClassNodes.isFeatureFlagsClass(className: String): Boolean { || className.endsWith("/FeatureFlagsImpl") || className.endsWith("/FakeFeatureFlagsImpl"); } + +/** + * @return if a given class "seems like" a sysprops class. + */ +private fun ClassNodes.isSyspropsClass(className: String): Boolean { + // Matches template classes defined here: + // https://cs.android.com/android/platform/superproject/main/+/main:system/tools/sysprop/ + return className.startsWith("android/sysprop/") + && className.endsWith("Properties") +} diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt index d38a6e34e09f..7fdd944770c6 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt @@ -64,6 +64,7 @@ fun createFilterFromTextPolicyFile( var aidlPolicy: FilterPolicyWithReason? = null var featureFlagsPolicy: FilterPolicyWithReason? = null + var syspropsPolicy: FilterPolicyWithReason? = null try { BufferedReader(FileReader(filename)).use { reader -> @@ -141,6 +142,14 @@ fun createFilterFromTextPolicyFile( featureFlagsPolicy = policy.withReason("$FILTER_REASON (feature flags)") } + SpecialClass.Sysprops -> { + if (syspropsPolicy != null) { + throw ParseException( + "Policy for sysprops already defined") + } + syspropsPolicy = + policy.withReason("$FILTER_REASON (sysprops)") + } } } } @@ -205,10 +214,10 @@ fun createFilterFromTextPolicyFile( } var ret: OutputFilter = imf - if (aidlPolicy != null || featureFlagsPolicy != null) { + if (aidlPolicy != null || featureFlagsPolicy != null || syspropsPolicy != null) { log.d("AndroidHeuristicsFilter enabled") ret = AndroidHeuristicsFilter( - classes, aidlPolicy, featureFlagsPolicy, imf) + classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, imf) } return ret } @@ -218,6 +227,7 @@ private enum class SpecialClass { NotSpecial, Aidl, FeatureFlags, + Sysprops, } private fun resolveSpecialClass(className: String): SpecialClass { @@ -227,6 +237,7 @@ private fun resolveSpecialClass(className: String): SpecialClass { when (className.lowercase()) { ":aidl" -> return SpecialClass.Aidl ":feature_flags" -> return SpecialClass.FeatureFlags + ":sysprops" -> return SpecialClass.Sysprops } throw ParseException("Invalid special class name \"$className\"") } |