diff options
58 files changed, 1270 insertions, 646 deletions
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp index 0104ee14fec4..ace56d42ddd1 100644 --- a/apex/jobscheduler/service/Android.bp +++ b/apex/jobscheduler/service/Android.bp @@ -20,6 +20,7 @@ java_library { ], libs: [ + "androidx.annotation_annotation", "app-compat-annotations", "error_prone_annotations", "framework", diff --git a/core/api/system-current.txt b/core/api/system-current.txt index fb2a4ac944a9..4b04d10a6e2c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4912,7 +4912,6 @@ package android.hardware.camera2.extension { method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat(); method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.util.Size getSize(); method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.view.Surface getSurface(); - method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int); method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setDynamicRangeProfile(long); } @@ -4923,6 +4922,7 @@ package android.hardware.camera2.extension { @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionConfiguration { ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionConfiguration(int, int, @NonNull java.util.List<android.hardware.camera2.extension.ExtensionOutputConfiguration>, @Nullable android.hardware.camera2.CaptureRequest); + method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int); } @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionOutputConfiguration { @@ -6261,7 +6261,7 @@ package android.hardware.radio { method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener); method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener); method public void close(); - method @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier); + method @Deprecated @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier); method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramInfos(@NonNull android.hardware.radio.ProgramSelector.Identifier); method public void registerListCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.ListCallback); method public void registerListCallback(@NonNull android.hardware.radio.ProgramList.ListCallback); @@ -6313,7 +6313,7 @@ package android.hardware.radio { field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5 field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5 field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa - field public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb + field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9 field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3 field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; // 0xf @@ -6321,8 +6321,8 @@ package android.hardware.radio { field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4 field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0 field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2 - field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd - field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc + field @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd + field @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8 @@ -6375,7 +6375,7 @@ package android.hardware.radio { field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8 field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7 field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9 - field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2 + field @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; // 0x2 field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_AM = 11; // 0xb field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_FM = 10; // 0xa field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3 diff --git a/core/java/Android.bp b/core/java/Android.bp index ab1c9a4ef48d..4f96206bfd08 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -167,6 +167,9 @@ java_library { "com/android/internal/logging/UiEventLoggerImpl.java", ":statslog-framework-java-gen", ], + libs: [ + "androidx.annotation_annotation", + ], static_libs: ["modules-utils-uieventlogger-interface"], } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index cf3b465906fa..ae5cacd18aa2 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4076,6 +4076,13 @@ public final class ActivityThread extends ClientTransactionHandler ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq); mNetworkBlockSeq = INVALID_PROC_STATE_SEQ; } catch (RemoteException ignored) {} + if (Flags.clearDnsCacheOnNetworkRulesUpdate()) { + // InetAddress will cache UnknownHostException failures. If the rules got + // updated and the app has network access now, we need to clear the negative + // cache to ensure valid dns queries can work immediately. + // TODO: b/329133769 - Clear only the negative cache once it is available. + InetAddress.clearDnsCache(); + } } } } diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java index 4ce983f6019b..3e7d66563c5e 100644 --- a/core/java/android/app/GrammaticalInflectionManager.java +++ b/core/java/android/app/GrammaticalInflectionManager.java @@ -125,7 +125,10 @@ public class GrammaticalInflectionManager { /** * Get the current grammatical gender of privileged application from the encrypted file. * - * @return the value of grammatical gender + * @return the value of system grammatical gender only if the calling app has the permission, + * otherwise throwing an exception. + * + * @throws SecurityException if the caller does not have the required permission. * * @see Configuration#getGrammaticalGender */ diff --git a/core/java/android/app/network-policy.aconfig b/core/java/android/app/network-policy.aconfig new file mode 100644 index 000000000000..88f386f6025d --- /dev/null +++ b/core/java/android/app/network-policy.aconfig @@ -0,0 +1,11 @@ +package: "android.app" + +flag { + namespace: "backstage_power" + name: "clear_dns_cache_on_network_rules_update" + description: "Clears the DNS cache when the network rules update" + bug: "237556596" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 749f218b0e6a..083d49f36b03 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -180,6 +180,16 @@ public final class CameraExtensionCharacteristics { EXTENSION_HDR, EXTENSION_NIGHT}; + /** + * List of synthetic CameraCharacteristics keys that are supported in the extensions. + */ + private static final List<CameraCharacteristics.Key> + SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS = + Arrays.asList( + CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES, + CameraCharacteristics.REQUEST_AVAILABLE_COLOR_SPACE_PROFILES + ); + private final Context mContext; private final String mCameraId; private final Map<String, CameraCharacteristics> mCharacteristicsMap; @@ -874,11 +884,17 @@ public final class CameraExtensionCharacteristics { Class<CameraCharacteristics.Key<?>> keyTyped = (Class<CameraCharacteristics.Key<?>>) key; - // Do not include synthetic keys. Including synthetic keys leads to undefined - // behavior. This causes inclusion of capabilities that may not be supported in - // camera extensions. ret.addAll(chars.getAvailableKeyList(CameraCharacteristics.class, keyTyped, keys, /*includeSynthetic*/ false)); + + // Add synthetic keys to the available key list if they are part of the supported + // synthetic camera characteristic key list + for (CameraCharacteristics.Key charKey : + SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS) { + if (chars.get(charKey) != null) { + ret.add(charKey); + } + } } } catch (RemoteException e) { Log.e(TAG, "Failed to query the extension for all available keys! Extension " @@ -990,6 +1006,7 @@ public final class CameraExtensionCharacteristics { case ImageFormat.YUV_420_888: case ImageFormat.JPEG: case ImageFormat.JPEG_R: + case ImageFormat.YCBCR_P010: break; default: throw new IllegalArgumentException("Unsupported format: " + format); @@ -1021,8 +1038,9 @@ public final class CameraExtensionCharacteristics { return generateJpegSupportedSizes( extenders.second.getSupportedPostviewResolutions(sz), streamMap); - } else if (format == ImageFormat.JPEG_R) { - // Jpeg_R/UltraHDR is currently not supported in the basic extension case + } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) { + // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic + // extension case return new ArrayList<>(); } else { throw new IllegalArgumentException("Unsupported format: " + format); @@ -1118,16 +1136,16 @@ public final class CameraExtensionCharacteristics { * * <p>Device-specific extensions currently support at most three * multi-frame capture surface formats. ImageFormat.JPEG will be supported by all - * extensions while ImageFormat.YUV_420_888 and ImageFormat.JPEG_R may or may not be - * supported.</p> + * extensions while ImageFormat.YUV_420_888, ImageFormat.JPEG_R, or ImageFormat.YCBCR_P010 + * may or may not be supported.</p> * * @param extension the extension type * @param format device-specific extension output format * @return non-modifiable list of available sizes or an empty list if the format is not * supported. * @throws IllegalArgumentException in case of format different from ImageFormat.JPEG, - * ImageFormat.YUV_420_888, ImageFormat.JPEG_R; or - * unsupported extension. + * ImageFormat.YUV_420_888, ImageFormat.JPEG_R, + * ImageFormat.YCBCR_P010; or unsupported extension. */ public @NonNull List<Size> getExtensionSupportedSizes(@Extension int extension, int format) { @@ -1151,6 +1169,7 @@ public final class CameraExtensionCharacteristics { case ImageFormat.YUV_420_888: case ImageFormat.JPEG: case ImageFormat.JPEG_R: + case ImageFormat.YCBCR_P010: break; default: throw new IllegalArgumentException("Unsupported format: " + format); @@ -1183,8 +1202,9 @@ public final class CameraExtensionCharacteristics { } else { return generateSupportedSizes(null, format, streamMap); } - } else if (format == ImageFormat.JPEG_R) { - // Jpeg_R/UltraHDR is currently not supported in the basic extension case + } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) { + // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the + // basic extension case return new ArrayList<>(); } else { throw new IllegalArgumentException("Unsupported format: " + format); @@ -1213,7 +1233,8 @@ public final class CameraExtensionCharacteristics { * @return the range of estimated minimal and maximal capture latency in milliseconds * or null if no capture latency info can be provided * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG}, - * {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R}; + * {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R} + * {@link ImageFormat#YCBCR_P010}; * or unsupported extension. */ public @Nullable Range<Long> getEstimatedCaptureLatencyRangeMillis(@Extension int extension, @@ -1222,6 +1243,7 @@ public final class CameraExtensionCharacteristics { case ImageFormat.YUV_420_888: case ImageFormat.JPEG: case ImageFormat.JPEG_R: + case ImageFormat.YCBCR_P010: //No op break; default: @@ -1269,8 +1291,8 @@ public final class CameraExtensionCharacteristics { // specific and cannot be estimated accurately enough. return null; } - if (format == ImageFormat.JPEG_R) { - // JpegR/UltraHDR is not supported for basic extensions + if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) { + // JpegR/UltraHDR + YCBCR_P010 is not supported for basic extensions return null; } diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java index 4895f38d7328..8fa09a802aa4 100644 --- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java +++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java @@ -61,7 +61,6 @@ public abstract class AdvancedExtender { private CameraUsageTracker mCameraUsageTracker; private static final String TAG = "AdvancedExtender"; - /** * Initialize a camera extension advanced extender instance. * @@ -263,6 +262,13 @@ public abstract class AdvancedExtender { * * <p>For example, an extension may limit the zoom ratio range. In this case, an OEM can return * a new zoom ratio range for the key {@link CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE}. + * + * <p> Currently, the only synthetic keys supported for override are + * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES} and + * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES}. To enable them, an OEM + * should override the respective native keys + * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP} and + * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP}. */ @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET) @NonNull diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl index 509bcb8e3d23..5567bed7f128 100644 --- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl +++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl @@ -27,6 +27,7 @@ parcelable CameraOutputConfig int imageFormat; int capacity; long usage; + long dynamicRangeProfile; const int TYPE_SURFACE = 0; const int TYPE_IMAGEREADER = 1; diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java index 53f56bc9f896..001b79499b1a 100644 --- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java +++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java @@ -133,15 +133,4 @@ public final class CameraOutputSurface { @DynamicRangeProfiles.Profile long dynamicRangeProfile) { mOutputSurface.dynamicRangeProfile = dynamicRangeProfile; } - - /** - * Set the color space. The default colorSpace - * will be - * {@link android.hardware.camera2.params.ColorSpaceProfiles.UNSPECIFIED} - * unless explicitly set using this method. - */ - @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT) - public void setColorSpace(int colorSpace) { - mOutputSurface.colorSpace = colorSpace; - } } diff --git a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl index 84ca2b63fbcf..c4f653cdfed7 100644 --- a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl +++ b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl @@ -25,4 +25,5 @@ parcelable CameraSessionConfig CameraMetadataNative sessionParameter; int sessionTemplateId; int sessionType; + int colorSpace; } diff --git a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java index 96c88e660e10..84b7a7fc1349 100644 --- a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java +++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.params.ColorSpaceProfiles; import android.os.IBinder; import com.android.internal.camera.flags.Flags; @@ -48,6 +49,7 @@ public class ExtensionConfiguration { private final int mSessionTemplateId; private final List<ExtensionOutputConfiguration> mOutputs; private final CaptureRequest mSessionParameters; + private int mColorSpace; /** * Initialize an extension configuration instance @@ -72,6 +74,18 @@ public class ExtensionConfiguration { mSessionTemplateId = sessionTemplateId; mOutputs = outputs; mSessionParameters = sessionParams; + mColorSpace = ColorSpaceProfiles.UNSPECIFIED; + } + + /** + * Set the color space using the ordinal value of a + * {@link android.graphics.ColorSpace.Named}. + * The default will be -1, indicating an unspecified ColorSpace, + * unless explicitly set using this method. + */ + @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT) + public void setColorSpace(int colorSpace) { + mColorSpace = colorSpace; } @FlaggedApi(Flags.FLAG_CONCERT_MODE) @@ -84,6 +98,11 @@ public class ExtensionConfiguration { ret.sessionTemplateId = mSessionTemplateId; ret.sessionType = mSessionType; ret.outputConfigs = new ArrayList<>(mOutputs.size()); + if (Flags.extension10Bit()) { + ret.colorSpace = mColorSpace; + } else { + ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED; + } for (ExtensionOutputConfiguration outputConfig : mOutputs) { ret.outputConfigs.add(outputConfig.getOutputConfig()); } diff --git a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java index 9dc6d7bf94b3..3a67d6192f5e 100644 --- a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java +++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java @@ -20,6 +20,7 @@ import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.hardware.camera2.params.DynamicRangeProfiles; import com.android.internal.camera.flags.Flags; @@ -79,6 +80,11 @@ public class ExtensionOutputConfiguration { config.outputId = new OutputConfigId(); config.outputId.id = mOutputConfigId; config.surfaceGroupId = mSurfaceGroupId; + if (Flags.extension10Bit()) { + config.dynamicRangeProfile = surface.getDynamicRangeProfile(); + } else { + config.dynamicRangeProfile = DynamicRangeProfiles.STANDARD; + } } @Nullable CameraOutputConfig getOutputConfig() { diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index a7d6caf9d9df..5b7f8bb00f25 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.content.Context; +import android.graphics.ColorSpace; import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.hardware.SyncFence; @@ -49,6 +50,7 @@ import android.hardware.camera2.extension.ParcelCaptureResult; import android.hardware.camera2.extension.ParcelImage; import android.hardware.camera2.extension.ParcelTotalCaptureResult; import android.hardware.camera2.extension.Request; +import android.hardware.camera2.params.ColorSpaceProfiles; import android.hardware.camera2.params.DynamicRangeProfiles; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.OutputConfiguration; @@ -62,6 +64,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; +import android.util.IntArray; import android.util.Log; import android.util.Size; import android.view.Surface; @@ -97,6 +100,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes private Surface mClientRepeatingRequestSurface; private Surface mClientCaptureSurface; private Surface mClientPostviewSurface; + private OutputConfiguration mClientRepeatingRequestOutputConfig; + private OutputConfiguration mClientCaptureOutputConfig; + private OutputConfiguration mClientPostviewOutputConfig; private CameraCaptureSession mCaptureSession = null; private ISessionProcessorImpl mSessionProcessor = null; private final InitializeSessionHandler mInitializeHandler; @@ -142,8 +148,19 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes for (OutputConfiguration c : config.getOutputConfigurations()) { if (c.getDynamicRangeProfile() != DynamicRangeProfiles.STANDARD) { - throw new IllegalArgumentException("Unsupported dynamic range profile: " + - c.getDynamicRangeProfile()); + if (Flags.extension10Bit() && Flags.cameraExtensionsCharacteristicsGet()) { + DynamicRangeProfiles dynamicProfiles = extensionChars.get( + config.getExtension(), + CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES); + if (dynamicProfiles == null || !dynamicProfiles.getSupportedProfiles() + .contains(c.getDynamicRangeProfile())) { + throw new IllegalArgumentException("Unsupported dynamic range profile: " + + c.getDynamicRangeProfile()); + } + } else { + throw new IllegalArgumentException("Unsupported dynamic range profile: " + + c.getDynamicRangeProfile()); + } } if (c.getStreamUseCase() != CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT) { @@ -157,12 +174,26 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes config.getExtension(), SurfaceTexture.class); Surface repeatingRequestSurface = CameraExtensionUtils.getRepeatingRequestSurface( config.getOutputConfigurations(), supportedPreviewSizes); + OutputConfiguration repeatingRequestOutputConfig = null; if (repeatingRequestSurface != null) { + for (OutputConfiguration outputConfig : config.getOutputConfigurations()) { + if (outputConfig.getSurface() == repeatingRequestSurface) { + repeatingRequestOutputConfig = outputConfig; + } + } suitableSurfaceCount++; } HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>(); - for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) { + + IntArray supportedCaptureOutputFormats = + new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length); + supportedCaptureOutputFormats.addAll( + CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS); + if (Flags.extension10Bit()) { + supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010); + } + for (int format : supportedCaptureOutputFormats.toArray()) { List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes( config.getExtension(), format); if (supportedSizes != null) { @@ -171,7 +202,13 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes } Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface( config.getOutputConfigurations(), supportedCaptureSizes); + OutputConfiguration burstCaptureOutputConfig = null; if (burstCaptureSurface != null) { + for (OutputConfiguration outputConfig : config.getOutputConfigurations()) { + if (outputConfig.getSurface() == burstCaptureSurface) { + burstCaptureOutputConfig = outputConfig; + } + } suitableSurfaceCount++; } @@ -180,13 +217,14 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes } Surface postviewSurface = null; + OutputConfiguration postviewOutputConfig = config.getPostviewOutputConfiguration(); if (burstCaptureSurface != null && config.getPostviewOutputConfiguration() != null) { CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo = CameraExtensionUtils.querySurface(burstCaptureSurface); Size burstCaptureSurfaceSize = new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight); HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>(); - for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) { + for (int format : supportedCaptureOutputFormats.toArray()) { List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes( config.getExtension(), burstCaptureSurfaceSize, format); if (supportedSizesPostview != null) { @@ -207,8 +245,8 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes extender.init(cameraId, characteristicsMapNative); CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(ctx, - extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface, - burstCaptureSurface, postviewSurface, config.getStateCallback(), + extender, cameraDevice, characteristicsMapNative, repeatingRequestOutputConfig, + burstCaptureOutputConfig, postviewOutputConfig, config.getStateCallback(), config.getExecutor(), sessionId, token, config.getExtension()); ret.mStatsAggregator.setClientName(ctx.getOpPackageName()); @@ -223,8 +261,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes @NonNull IAdvancedExtenderImpl extender, @NonNull CameraDeviceImpl cameraDevice, Map<String, CameraMetadataNative> characteristicsMap, - @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface, - @Nullable Surface postviewSurface, + @Nullable OutputConfiguration repeatingRequestOutputConfig, + @Nullable OutputConfiguration burstCaptureOutputConfig, + @Nullable OutputConfiguration postviewOutputConfig, @NonNull StateCallback callback, @NonNull Executor executor, int sessionId, @NonNull IBinder token, @@ -235,9 +274,18 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes mCharacteristicsMap = characteristicsMap; mCallbacks = callback; mExecutor = executor; - mClientRepeatingRequestSurface = repeatingRequestSurface; - mClientCaptureSurface = burstCaptureSurface; - mClientPostviewSurface = postviewSurface; + mClientRepeatingRequestOutputConfig = repeatingRequestOutputConfig; + mClientCaptureOutputConfig = burstCaptureOutputConfig; + mClientPostviewOutputConfig = postviewOutputConfig; + if (repeatingRequestOutputConfig != null) { + mClientRepeatingRequestSurface = repeatingRequestOutputConfig.getSurface(); + } + if (burstCaptureOutputConfig != null) { + mClientCaptureSurface = burstCaptureOutputConfig.getSurface(); + } + if (postviewOutputConfig != null) { + mClientPostviewSurface = postviewOutputConfig.getSurface(); + } mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); @@ -262,9 +310,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes return; } - OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestSurface); - OutputSurface captureSurface = initializeParcelable(mClientCaptureSurface); - OutputSurface postviewSurface = initializeParcelable(mClientPostviewSurface); + OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestOutputConfig); + OutputSurface captureSurface = initializeParcelable(mClientCaptureOutputConfig); + OutputSurface postviewSurface = initializeParcelable(mClientPostviewOutputConfig); mSessionProcessor = mAdvancedExtender.getSessionProcessor(); CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mToken, @@ -300,6 +348,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes cameraOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR); cameraOutput.setReadoutTimestampEnabled(false); cameraOutput.setPhysicalCameraId(output.physicalCameraId); + cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile); outputList.add(cameraOutput); mCameraConfigMap.put(cameraOutput.getSurface(), output); } @@ -314,7 +363,10 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes SessionConfiguration sessionConfiguration = new SessionConfiguration(sessionType, outputList, new CameraExtensionUtils.HandlerExecutor(mHandler), new SessionStateHandler()); - + if (sessionConfig.colorSpace != ColorSpaceProfiles.UNSPECIFIED) { + sessionConfiguration.setColorSpace( + ColorSpace.Named.values()[sessionConfig.colorSpace]); + } if ((sessionConfig.sessionParameter != null) && (!sessionConfig.sessionParameter.isEmpty())) { CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest( @@ -362,21 +414,38 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes return ret; } - private static OutputSurface initializeParcelable(Surface s) { + private static OutputSurface initializeParcelable(OutputConfiguration o) { OutputSurface ret = new OutputSurface(); - if (s != null) { + + if (o != null && o.getSurface() != null) { + Surface s = o.getSurface(); ret.surface = s; ret.size = new android.hardware.camera2.extension.Size(); Size surfaceSize = SurfaceUtils.getSurfaceSize(s); ret.size.width = surfaceSize.getWidth(); ret.size.height = surfaceSize.getHeight(); ret.imageFormat = SurfaceUtils.getSurfaceFormat(s); + + if (Flags.extension10Bit()) { + ret.dynamicRangeProfile = o.getDynamicRangeProfile(); + ColorSpace colorSpace = o.getColorSpace(); + if (colorSpace != null) { + ret.colorSpace = colorSpace.getId(); + } else { + ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED; + } + } else { + ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD; + ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED; + } } else { ret.surface = null; ret.size = new android.hardware.camera2.extension.Size(); ret.size.width = -1; ret.size.height = -1; ret.imageFormat = ImageFormat.UNKNOWN; + ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD; + ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED; } return ret; diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 725b4139bb95..5b32f33777fa 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -58,12 +58,15 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; +import android.util.IntArray; import android.util.Log; import android.util.LongSparseArray; import android.util.Pair; import android.util.Size; import android.view.Surface; +import com.android.internal.camera.flags.Flags; + import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; @@ -183,7 +186,14 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>(); - for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) { + IntArray supportedCaptureOutputFormats = + new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length); + supportedCaptureOutputFormats.addAll( + CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS); + if (Flags.extension10Bit()) { + supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010); + } + for (int format : supportedCaptureOutputFormats.toArray()) { List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes( config.getExtension(), format); if (supportedSizes != null) { @@ -207,7 +217,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { Size burstCaptureSurfaceSize = new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight); HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>(); - for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) { + for (int format : supportedCaptureOutputFormats.toArray()) { List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes( config.getExtension(), burstCaptureSurfaceSize, format); if (supportedSizesPostview != null) { diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java index a8066aa74f95..f0c6e2e4e123 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java @@ -29,10 +29,13 @@ import android.hardware.camera2.utils.SurfaceUtils; import android.media.Image; import android.media.ImageWriter; import android.os.Handler; +import android.util.IntArray; import android.util.Log; import android.util.Size; import android.view.Surface; +import com.android.internal.camera.flags.Flags; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -130,9 +133,16 @@ public final class CameraExtensionUtils { public static Surface getBurstCaptureSurface( @NonNull List<OutputConfiguration> outputConfigs, @NonNull HashMap<Integer, List<Size>> supportedCaptureSizes) { + IntArray supportedCaptureOutputFormats = + new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length); + supportedCaptureOutputFormats.addAll( + CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS); + if (Flags.extension10Bit()) { + supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010); + } for (OutputConfiguration config : outputConfigs) { SurfaceInfo surfaceInfo = querySurface(config.getSurface()); - for (int supportedFormat : SUPPORTED_CAPTURE_OUTPUT_FORMATS) { + for (int supportedFormat : supportedCaptureOutputFormats.toArray()) { if (surfaceInfo.mFormat == supportedFormat) { Size captureSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight); if (supportedCaptureSizes.containsKey(supportedFormat)) { diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java index 905d911248ca..76888f338615 100644 --- a/core/java/android/hardware/devicestate/DeviceState.java +++ b/core/java/android/hardware/devicestate/DeviceState.java @@ -543,11 +543,13 @@ public final class DeviceState { int identifier = source.readInt(); String name = source.readString8(); ArraySet<@DeviceStateProperties Integer> systemProperties = new ArraySet<>(); - for (int i = 0; i < source.readInt(); i++) { + int systemPropertySize = source.readInt(); + for (int i = 0; i < systemPropertySize; i++) { systemProperties.add(source.readInt()); } ArraySet<@DeviceStateProperties Integer> physicalProperties = new ArraySet<>(); - for (int j = 0; j < source.readInt(); j++) { + int physicalPropertySize = source.readInt(); + for (int j = 0; j < physicalPropertySize; j++) { physicalProperties.add(source.readInt()); } return new DeviceState.Configuration(identifier, name, systemProperties, diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java index a3a2a2e6fd16..c5167dbc7d4c 100644 --- a/core/java/android/hardware/radio/ProgramList.java +++ b/core/java/android/hardware/radio/ProgramList.java @@ -304,7 +304,11 @@ public final class ProgramList implements AutoCloseable { * * @param id primary identifier of a program to fetch * @return the program info, or null if there is no such program on the list + * + * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs + * with the given primary identifier */ + @Deprecated public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) { Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries; synchronized (mLock) { diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java index a968c6f0ad05..0740374ad8e2 100644 --- a/core/java/android/hardware/radio/ProgramSelector.java +++ b/core/java/android/hardware/radio/ProgramSelector.java @@ -312,15 +312,23 @@ public final class ProgramSelector implements Parcelable { public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; /** * 1: AM, 2:FM + * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead */ + @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; /** * 32bit primary identifier for SiriusXM Satellite Radio. + * + * @deprecated SiriusXM Satellite Radio is not supported */ + @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; /** * 0-999 range + * + * @deprecated SiriusXM Satellite Radio is not supported */ + @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; /** * 44bit compound primary identifier for Digital Audio Broadcasting and diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index 61cf8901c454..da6c68646820 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -166,7 +166,12 @@ public class RadioManager { * analog handover state managed from the HAL implementation side. * * <p>Some radio technologies may not support this, i.e. DAB. + * + * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM} + * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet} + * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}. */ + @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; /** * Forces the digital playback for the supporting radio technology. diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index ce74848705e4..82e613e18d41 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -77,4 +77,12 @@ flag { description: "Properties to allow apps and activities to opt-in to cover display rendering" bug: "312530526" is_fixed_read_only: true +} + +flag { + namespace: "windowing_sdk" + name: "enable_wm_extensions_for_all_flag" + description: "Whether to enable WM Extensions for all devices" + bug: "306666082" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java index 76772b7a528b..08977265667c 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java @@ -16,6 +16,12 @@ package android.hardware.devicestate; +import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY; +import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY; +import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED; +import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN; +import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; @@ -33,6 +39,7 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.List; +import java.util.Set; /** * Unit tests for {@link DeviceStateInfo}. @@ -44,11 +51,25 @@ import java.util.List; public final class DeviceStateInfoTest { private static final DeviceState DEVICE_STATE_0 = new DeviceState( - new DeviceState.Configuration.Builder(0, "STATE_0").build()); + new DeviceState.Configuration.Builder(0, "STATE_0") + .setSystemProperties( + Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS, + PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY)) + .setPhysicalProperties( + Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)) + .build()); private static final DeviceState DEVICE_STATE_1 = new DeviceState( - new DeviceState.Configuration.Builder(1, "STATE_1").build()); + new DeviceState.Configuration.Builder(1, "STATE_1") + .setSystemProperties( + Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY)) + .setPhysicalProperties( + Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN)) + .build()); private static final DeviceState DEVICE_STATE_2 = new DeviceState( - new DeviceState.Configuration.Builder(2, "STATE_2").build()); + new DeviceState.Configuration.Builder(2, "STATE_2") + .setSystemProperties( + Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY)) + .build()); @Test public void create() { diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java index 68de21f76dc8..78d4324ebb1a 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java @@ -93,4 +93,22 @@ public final class DeviceStateTest { Assert.assertEquals(originalState, new DeviceState(stateConfiguration)); } + + @Test + public void writeToParcel_noPhysicalProperties() { + final DeviceState originalState = new DeviceState( + new DeviceState.Configuration.Builder(0, "TEST_STATE") + .setSystemProperties(Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS, + PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST)) + .build()); + + final Parcel parcel = Parcel.obtain(); + originalState.getConfiguration().writeToParcel(parcel, 0 /* flags */); + parcel.setDataPosition(0); + + final DeviceState.Configuration stateConfiguration = + DeviceState.Configuration.CREATOR.createFromParcel(parcel); + + Assert.assertEquals(originalState, new DeviceState(stateConfiguration)); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 8b2ec0a35685..8d489e106ae1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -846,8 +846,10 @@ public abstract class WMShellBaseModule { static ShellController provideShellController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, + DisplayInsetsController displayInsetsController, @ShellMainThread ShellExecutor mainExecutor) { - return new ShellController(context, shellInit, shellCommandHandler, mainExecutor); + return new ShellController(context, shellInit, shellCommandHandler, + displayInsetsController, mainExecutor); } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 9dd4c193a006..ec907fd9bd12 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -29,6 +29,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; @@ -2776,7 +2777,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, + " with " + taskInfo.taskId + " before startAnimation()."); record.addRecord(stage, true, taskInfo.taskId); } - } else if (isClosingType(change.getMode())) { + } else if (change.getMode() == TRANSIT_CLOSE) { if (stage.containsTask(taskInfo.taskId)) { record.addRecord(stage, false, taskInfo.taskId); Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java new file mode 100644 index 000000000000..a94f80241d4f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.sysui; + +import android.graphics.Rect; + +/** + * Callbacks for when the Display IME changes. + */ +public interface DisplayImeChangeListener { + /** + * Called when the ime bounds change. + */ + default void onImeBoundsChanged(int displayId, Rect bounds) {} + + /** + * Called when the IME visibility change. + */ + default void onImeVisibilityChanged(int displayId, boolean isShowing) {} +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java index a7843e218a8a..2f6edc226c45 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java @@ -30,21 +30,28 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.UserInfo; import android.content.res.Configuration; +import android.graphics.Rect; import android.os.Bundle; import android.util.ArrayMap; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.SurfaceControlRegistry; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ExternalThread; import java.io.PrintWriter; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; import java.util.function.Supplier; /** @@ -57,6 +64,7 @@ public class ShellController { private final ShellInit mShellInit; private final ShellCommandHandler mShellCommandHandler; private final ShellExecutor mMainExecutor; + private final DisplayInsetsController mDisplayInsetsController; private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl(); private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners = @@ -65,6 +73,8 @@ public class ShellController { new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners = new CopyOnWriteArrayList<>(); + private final ConcurrentHashMap<DisplayImeChangeListener, Executor> mDisplayImeChangeListeners = + new ConcurrentHashMap<>(); private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers = new ArrayMap<>(); @@ -73,20 +83,53 @@ public class ShellController { private Configuration mLastConfiguration; + private OnInsetsChangedListener mInsetsChangeListener = new OnInsetsChangedListener() { + private InsetsState mInsetsState = new InsetsState(); + + @Override + public void insetsChanged(InsetsState insetsState) { + if (mInsetsState == insetsState) { + return; + } + + InsetsSource oldSource = mInsetsState.peekSource(InsetsSource.ID_IME); + boolean wasVisible = (oldSource != null && oldSource.isVisible()); + Rect oldFrame = wasVisible ? oldSource.getFrame() : null; + + InsetsSource newSource = insetsState.peekSource(InsetsSource.ID_IME); + boolean isVisible = (newSource != null && newSource.isVisible()); + Rect newFrame = isVisible ? newSource.getFrame() : null; + + if (wasVisible != isVisible) { + onImeVisibilityChanged(isVisible); + } + + if (newFrame != null && !newFrame.equals(oldFrame)) { + onImeBoundsChanged(newFrame); + } + + mInsetsState = insetsState; + } + }; + public ShellController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, + DisplayInsetsController displayInsetsController, ShellExecutor mainExecutor) { mContext = context; mShellInit = shellInit; mShellCommandHandler = shellCommandHandler; + mDisplayInsetsController = displayInsetsController; mMainExecutor = mainExecutor; shellInit.addInitCallback(this::onInit, this); } private void onInit() { mShellCommandHandler.addDumpCallback(this::dump, this); + mDisplayInsetsController.addInsetsChangedListener( + mContext.getDisplayId(), mInsetsChangeListener); } /** @@ -259,6 +302,25 @@ public class ShellController { } } + @VisibleForTesting + void onImeBoundsChanged(Rect bounds) { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime bounds changed"); + mDisplayImeChangeListeners.forEach( + (DisplayImeChangeListener listener, Executor executor) -> + executor.execute(() -> listener.onImeBoundsChanged( + mContext.getDisplayId(), bounds))); + } + + @VisibleForTesting + void onImeVisibilityChanged(boolean isShowing) { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime visibility changed: isShowing=%b", + isShowing); + mDisplayImeChangeListeners.forEach( + (DisplayImeChangeListener listener, Executor executor) -> + executor.execute(() -> listener.onImeVisibilityChanged( + mContext.getDisplayId(), isShowing))); + } + private void handleInit() { SurfaceControlRegistry.createProcessInstance(mContext); mShellInit.init(); @@ -329,6 +391,19 @@ public class ShellController { } @Override + public void addDisplayImeChangeListener(DisplayImeChangeListener listener, + Executor executor) { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Adding new DisplayImeChangeListener"); + mDisplayImeChangeListeners.put(listener, executor); + } + + @Override + public void removeDisplayImeChangeListener(DisplayImeChangeListener listener) { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Removing DisplayImeChangeListener"); + mDisplayImeChangeListeners.remove(listener); + } + + @Override public boolean handleCommand(String[] args, PrintWriter pw) { try { boolean[] result = new boolean[1]; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java index bc5dd11ef54e..bd1c64a0d182 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java @@ -25,6 +25,7 @@ import androidx.annotation.NonNull; import java.io.PrintWriter; import java.util.List; +import java.util.concurrent.Executor; /** * General interface for notifying the Shell of common SysUI events like configuration or keyguard @@ -65,6 +66,18 @@ public interface ShellInterface { default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {} /** + * Registers a DisplayImeChangeListener to monitor for changes on Ime + * position and visibility. + */ + default void addDisplayImeChangeListener(DisplayImeChangeListener listener, + Executor executor) {} + + /** + * Removes a registered DisplayImeChangeListener. + */ + default void removeDisplayImeChangeListener(DisplayImeChangeListener listener) {} + + /** * Handles a shell command. */ default boolean handleCommand(final String[] args, PrintWriter pw) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt index ae39fbcb4eed..4a4c5e860bb2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt @@ -37,6 +37,7 @@ import com.android.wm.shell.WindowManagerShellWrapper import com.android.wm.shell.bubbles.bar.BubbleBarLayerView import com.android.wm.shell.bubbles.properties.BubbleProperties import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.FloatingContentCoordinator import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue @@ -94,7 +95,8 @@ class BubbleViewInfoTest : ShellTestCase() { val windowManager = context.getSystemService(WindowManager::class.java) val shellInit = ShellInit(mainExecutor) val shellCommandHandler = ShellCommandHandler() - val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor) + val shellController = ShellController(context, shellInit, shellCommandHandler, + mock<DisplayInsetsController>(), mainExecutor) bubblePositioner = BubblePositioner(context, windowManager) val bubbleData = BubbleData( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 3384509f1da9..d38fc6cb6418 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -129,7 +129,7 @@ public class PipControllerTest extends ShellTestCase { }).when(mMockExecutor).execute(any()); mShellInit = spy(new ShellInit(mMockExecutor)); mShellController = spy(new ShellController(mContext, mShellInit, mMockShellCommandHandler, - mMockExecutor)); + mMockDisplayInsetsController, mMockExecutor)); mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler, mShellController, mMockDisplayController, mMockPipAnimationController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 10e9e11e9004..41a4e8d503c9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -58,6 +58,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; @@ -96,6 +97,8 @@ public class RecentTasksControllerTest extends ShellTestCase { private DesktopModeTaskRepository mDesktopModeTaskRepository; @Mock private ActivityTaskManager mActivityTaskManager; + @Mock + private DisplayInsetsController mDisplayInsetsController; private ShellTaskOrganizer mShellTaskOrganizer; private RecentTasksController mRecentTasksController; @@ -110,7 +113,7 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class)); mShellInit = spy(new ShellInit(mMainExecutor)); mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler, - mMainExecutor)); + mDisplayInsetsController, mMainExecutor)); mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit, mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager, Optional.of(mDesktopModeTaskRepository), mMainExecutor); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index 315d97ed333b..3c387f0d7c34 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -123,7 +123,7 @@ public class SplitScreenControllerTests extends ShellTestCase { assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext)); MockitoAnnotations.initMocks(this); mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler, - mMainExecutor)); + mDisplayInsetsController, mMainExecutor)); mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit, mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue, mRootTDAOrganizer, mDisplayController, mDisplayImeController, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java index 012c40811811..ff76a2f13527 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java @@ -40,6 +40,7 @@ import com.android.internal.util.function.TriConsumer; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -65,6 +66,7 @@ public class StartingWindowControllerTests extends ShellTestCase { private @Mock Context mContext; private @Mock DisplayManager mDisplayManager; + private @Mock DisplayInsetsController mDisplayInsetsController; private @Mock ShellCommandHandler mShellCommandHandler; private @Mock ShellTaskOrganizer mTaskOrganizer; private @Mock ShellExecutor mMainExecutor; @@ -83,7 +85,7 @@ public class StartingWindowControllerTests extends ShellTestCase { doReturn(super.mContext.getResources()).when(mContext).getResources(); mShellInit = spy(new ShellInit(mMainExecutor)); mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler, - mMainExecutor)); + mDisplayInsetsController, mMainExecutor)); mController = new StartingWindowController(mContext, mShellInit, mShellController, mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool); mShellInit.init(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java index 7c520c34b29d..6292018ba35d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.mock; import android.content.Context; import android.content.pm.UserInfo; import android.content.res.Configuration; +import android.graphics.Rect; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -35,8 +36,8 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ExternalInterfaceBinder; -import com.android.wm.shell.common.ShellExecutor; import org.junit.After; import org.junit.Before; @@ -63,12 +64,15 @@ public class ShellControllerTest extends ShellTestCase { private ShellCommandHandler mShellCommandHandler; @Mock private Context mTestUserContext; + @Mock + private DisplayInsetsController mDisplayInsetsController; private TestShellExecutor mExecutor; private ShellController mController; private TestConfigurationChangeListener mConfigChangeListener; private TestKeyguardChangeListener mKeyguardChangeListener; private TestUserChangeListener mUserChangeListener; + private TestDisplayImeChangeListener mDisplayImeChangeListener; @Before @@ -77,8 +81,10 @@ public class ShellControllerTest extends ShellTestCase { mKeyguardChangeListener = new TestKeyguardChangeListener(); mConfigChangeListener = new TestConfigurationChangeListener(); mUserChangeListener = new TestUserChangeListener(); + mDisplayImeChangeListener = new TestDisplayImeChangeListener(); mExecutor = new TestShellExecutor(); - mController = new ShellController(mContext, mShellInit, mShellCommandHandler, mExecutor); + mController = new ShellController(mContext, mShellInit, mShellCommandHandler, + mDisplayInsetsController, mExecutor); mController.onConfigurationChanged(getConfigurationCopy()); } @@ -130,6 +136,45 @@ public class ShellControllerTest extends ShellTestCase { } @Test + public void testAddDisplayImeChangeListener_ensureCallback() { + mController.asShell().addDisplayImeChangeListener( + mDisplayImeChangeListener, mExecutor); + + final Rect bounds = new Rect(10, 20, 30, 40); + mController.onImeBoundsChanged(bounds); + mController.onImeVisibilityChanged(true); + mExecutor.flushAll(); + + assertTrue(mDisplayImeChangeListener.boundsChanged == 1); + assertTrue(bounds.equals(mDisplayImeChangeListener.lastBounds)); + assertTrue(mDisplayImeChangeListener.visibilityChanged == 1); + assertTrue(mDisplayImeChangeListener.lastVisibility); + } + + @Test + public void testDoubleAddDisplayImeChangeListener_ensureSingleCallback() { + mController.asShell().addDisplayImeChangeListener( + mDisplayImeChangeListener, mExecutor); + mController.asShell().addDisplayImeChangeListener( + mDisplayImeChangeListener, mExecutor); + + mController.onImeVisibilityChanged(true); + mExecutor.flushAll(); + assertTrue(mDisplayImeChangeListener.visibilityChanged == 1); + } + + @Test + public void testAddRemoveDisplayImeChangeListener_ensureNoCallback() { + mController.asShell().addDisplayImeChangeListener( + mDisplayImeChangeListener, mExecutor); + mController.asShell().removeDisplayImeChangeListener(mDisplayImeChangeListener); + + mController.onImeVisibilityChanged(true); + mExecutor.flushAll(); + assertTrue(mDisplayImeChangeListener.visibilityChanged == 0); + } + + @Test public void testAddUserChangeListener_ensureCallback() { mController.addUserChangeListener(mUserChangeListener); @@ -457,4 +502,23 @@ public class ShellControllerTest extends ShellTestCase { lastUserProfiles = profiles; } } + + private static class TestDisplayImeChangeListener implements DisplayImeChangeListener { + public int boundsChanged = 0; + public Rect lastBounds; + public int visibilityChanged = 0; + public boolean lastVisibility = false; + + @Override + public void onImeBoundsChanged(int displayId, Rect bounds) { + boundsChanged++; + lastBounds = bounds; + } + + @Override + public void onImeVisibilityChanged(int displayId, boolean isShowing) { + visibilityChanged++; + lastVisibility = isShowing; + } + } } diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index 156be389fe84..f33bcb7f9643 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -11,7 +11,7 @@ flag { name: "location_bypass" namespace: "location" description: "Enable location bypass appops behavior" - bug: "301150056" + bug: "329151785" } flag { diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt index 0e3949365646..cfdcaff4d34c 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt @@ -26,23 +26,10 @@ import java.util.concurrent.ConcurrentHashMap /** Manager of [BackupRestoreStorage]. */ class BackupRestoreStorageManager private constructor(private val application: Application) { - private val storages = ConcurrentHashMap<String, BackupRestoreStorage>() + private val storageWrappers = ConcurrentHashMap<String, StorageWrapper>() private val executor = MoreExecutors.directExecutor() - private val observer = Observer { reason -> notifyBackupManager(null, reason) } - - private val keyedObserver = - KeyedObserver<Any?> { key, reason -> notifyBackupManager(key, reason) } - - private fun notifyBackupManager(key: Any?, reason: Int) { - // prefer not triggering backup immediately after restore - if (reason == ChangeReason.RESTORE) return - // TODO: log storage name - Log.d(LOG_TAG, "Notify BackupManager data changed for change: key=$key") - BackupManager.dataChanged(application.packageName) - } - /** * Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper]. * @@ -52,7 +39,8 @@ class BackupRestoreStorageManager private constructor(private val application: A */ fun addBackupAgentHelpers(backupAgentHelper: BackupAgentHelper) { val fileStorages = mutableListOf<BackupRestoreFileStorage>() - for ((keyPrefix, storage) in storages) { + for ((keyPrefix, storageWrapper) in storageWrappers) { + val storage = storageWrapper.storage if (storage is BackupRestoreFileStorage) { fileStorages.add(storage) } else { @@ -70,15 +58,8 @@ class BackupRestoreStorageManager private constructor(private val application: A * The observers of the storages will be notified. */ fun onRestoreFinished() { - for (storage in storages.values) { - storage.notifyRestoreFinished() - } - } - - private fun BackupRestoreStorage.notifyRestoreFinished() { - when (this) { - is KeyedObservable<*> -> notifyChange(ChangeReason.RESTORE) - is Observable -> notifyChange(ChangeReason.RESTORE) + for (storageWrapper in storageWrappers.values) { + storageWrapper.notifyRestoreFinished() } } @@ -99,50 +80,82 @@ class BackupRestoreStorageManager private constructor(private val application: A fun add(storage: BackupRestoreStorage) { if (storage is BackupRestoreFileStorage) storage.checkFilePaths() val name = storage.name - val oldStorage = storages.put(name, storage) + val oldStorage = storageWrappers.put(name, StorageWrapper(storage))?.storage if (oldStorage != null) { throw IllegalStateException( "Storage name '$name' conflicts:\n\told: $oldStorage\n\tnew: $storage" ) } - storage.addObserver() - } - - private fun BackupRestoreStorage.addObserver() { - when (this) { - is KeyedObservable<*> -> addObserver(keyedObserver, executor) - is Observable -> addObserver(observer, executor) - else -> - throw IllegalArgumentException( - "$this does not implement either KeyedObservable or Observable" - ) - } } /** Removes all the storages. */ fun removeAll() { - for ((name, _) in storages) remove(name) + for ((name, _) in storageWrappers) remove(name) } /** Removes storage with given name. */ fun remove(name: String): BackupRestoreStorage? { - val storage = storages.remove(name) - storage?.removeObserver() - return storage - } - - private fun BackupRestoreStorage.removeObserver() { - when (this) { - is KeyedObservable<*> -> removeObserver(keyedObserver) - is Observable -> removeObserver(observer) - } + val storageWrapper = storageWrappers.remove(name) + storageWrapper?.removeObserver() + return storageWrapper?.storage } /** Returns storage with given name. */ - fun get(name: String): BackupRestoreStorage? = storages[name] + fun get(name: String): BackupRestoreStorage? = storageWrappers[name]?.storage /** Returns storage with given name, exception is raised if not found. */ - fun getOrThrow(name: String): BackupRestoreStorage = storages[name]!! + fun getOrThrow(name: String): BackupRestoreStorage = storageWrappers[name]!!.storage + + private inner class StorageWrapper(val storage: BackupRestoreStorage) : + Observer, KeyedObserver<Any?> { + init { + when (storage) { + is KeyedObservable<*> -> storage.addObserver(this, executor) + is Observable -> storage.addObserver(this, executor) + else -> + throw IllegalArgumentException( + "$this does not implement either KeyedObservable or Observable" + ) + } + } + + override fun onChanged(reason: Int) = onKeyChanged(null, reason) + + override fun onKeyChanged(key: Any?, reason: Int) { + notifyBackupManager(key, reason) + } + + private fun notifyBackupManager(key: Any?, reason: Int) { + val name = storage.name + // prefer not triggering backup immediately after restore + if (reason == ChangeReason.RESTORE) { + Log.d( + LOG_TAG, + "Notify BackupManager dataChanged ignored for restore: storage=$name key=$key" + ) + return + } + Log.d( + LOG_TAG, + "Notify BackupManager dataChanged: storage=$name key=$key reason=$reason" + ) + BackupManager.dataChanged(application.packageName) + } + + fun removeObserver() { + when (storage) { + is KeyedObservable<*> -> storage.removeObserver(this) + is Observable -> storage.removeObserver(this) + } + } + + fun notifyRestoreFinished() { + when (storage) { + is KeyedObservable<*> -> storage.notifyChange(ChangeReason.RESTORE) + is Observable -> storage.notifyChange(ChangeReason.RESTORE) + } + } + } companion object { @Volatile private var instance: BackupRestoreStorageManager? = null diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt index 56b0bf74574f..91fa8c3c7587 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt @@ -23,6 +23,7 @@ import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.AudioStreamModel import com.android.settingslib.volume.shared.model.RingerMode import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -43,6 +44,9 @@ class AudioVolumeInteractor( streamModel.copy(volume = processVolume(streamModel, ringerMode, isZenMuted)) } + val ringerMode: StateFlow<RingerMode> + get() = audioRepository.ringerMode + suspend fun setVolume(audioStream: AudioStream, volume: Int) = audioRepository.setVolume(audioStream, volume) diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index da06830357e8..85bdb295cbb2 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -617,3 +617,14 @@ flag { description: "enables new focus outline for qs tiles when focused on with physical keyboard" bug: "312899524" } + +flag { + name: "edgeback_gesture_handler_get_running_tasks_background" + namespace: "systemui" + description: "Decide whether to get the running tasks from activity manager in EdgebackGestureHandler" + " class on the background thread." + bug: "325041960" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index 96520b21cc72..7acb4d5498db 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -19,61 +19,31 @@ package com.android.systemui.keyguard.ui.composable import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.android.compose.animation.scene.Edge -import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneScope -import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene import dagger.Lazy import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.stateIn /** The lock screen scene shows when the device is locked. */ @SysUISingleton class LockscreenScene @Inject constructor( - @Application private val applicationScope: CoroutineScope, viewModel: LockscreenSceneViewModel, private val lockscreenContent: Lazy<LockscreenContent>, ) : ComposableScene { override val key = Scenes.Lockscreen override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = - combine( - viewModel.upDestinationSceneKey, - viewModel.leftDestinationSceneKey, - viewModel.downFromTopEdgeDestinationSceneKey, - ) { upKey, leftKey, downFromTopEdgeKey -> - destinationScenes( - up = upKey, - left = leftKey, - downFromTopEdge = downFromTopEdgeKey, - ) - } - .stateIn( - scope = applicationScope, - started = SharingStarted.Eagerly, - initialValue = - destinationScenes( - up = viewModel.upDestinationSceneKey.value, - left = viewModel.leftDestinationSceneKey.value, - downFromTopEdge = viewModel.downFromTopEdgeDestinationSceneKey.value, - ) - ) + viewModel.destinationScenes @Composable override fun SceneScope.Content( @@ -84,22 +54,6 @@ constructor( modifier = modifier, ) } - - private fun destinationScenes( - up: SceneKey?, - left: SceneKey?, - downFromTopEdge: SceneKey?, - ): Map<UserAction, UserActionResult> { - return buildMap { - up?.let { this[Swipe(SwipeDirection.Up)] = UserActionResult(up) } - left?.let { this[Swipe(SwipeDirection.Left)] = UserActionResult(left) } - downFromTopEdge?.let { - this[Swipe(fromSource = Edge.Top, direction = SwipeDirection.Down)] = - UserActionResult(downFromTopEdge) - } - this[Swipe(direction = SwipeDirection.Down)] = UserActionResult(Scenes.Shade) - } - } } @Composable diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt index 8ac84ff819eb..b1fbe35eccd8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.panel.component.anc.ui.composable import android.content.Context +import android.view.ContextThemeWrapper import android.view.View import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.MaterialTheme @@ -73,15 +74,16 @@ constructor( AndroidView<SliceView>( modifier = Modifier.fillMaxWidth(), factory = { context: Context -> - SliceView(context).apply { - mode = SliceView.MODE_LARGE - isScrollable = false - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - setShowTitleItems(true) - addOnLayoutChangeListener( - OnWidthChangedLayoutListener(viewModel::changeSliceWidth) - ) - } + SliceView(ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel)) + .apply { + mode = SliceView.MODE_LARGE + isScrollable = false + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + setShowTitleItems(true) + addOnLayoutChangeListener( + OnWidthChangedLayoutListener(viewModel::changeSliceWidth) + ) + } }, update = { sliceView: SliceView -> sliceView.slice = slice } ) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index 18a62dca3769..3e0aee54ea34 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -22,6 +22,7 @@ import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -30,6 +31,7 @@ import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import com.android.compose.PlatformSlider import com.android.compose.PlatformSliderColors import com.android.systemui.common.ui.compose.Icon @@ -54,7 +56,7 @@ fun VolumeSlider( if (isDragging) { Text(text = value.toInt().toString()) } else { - state.icon?.let { Icon(icon = it) } + state.icon?.let { Icon(modifier = Modifier.size(24.dp), icon = it) } } }, colors = sliderColors, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 19950a5fb89d..2fd2ef1f3240 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -19,9 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import android.platform.test.annotations.EnableFlags -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.compose.animation.scene.Edge +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel @@ -31,86 +34,129 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.kosmos.testScope -import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.shadeInteractor -import com.android.systemui.shade.domain.startable.shadeStartable +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith +import platform.test.runner.parameterized.Parameter +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) class LockscreenSceneViewModelTest : SysuiTestCase() { + companion object { + @Parameters( + name = + "canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," + + " isSingleShade={3}, isCommunalAvailable={4}" + ) + @JvmStatic + fun combinations() = buildList { + repeat(32) { combination -> + add( + arrayOf( + /* canSwipeToEnter= */ combination and 1 != 0, + /* downWithTwoPointers= */ combination and 2 != 0, + /* downFromEdge= */ combination and 4 != 0, + /* isSingleShade= */ combination and 8 != 0, + /* isCommunalAvailable= */ combination and 16 != 0, + ) + ) + } + } + + @JvmStatic + @BeforeClass + fun setUp() { + val combinationStrings = + combinations().map { array -> + check(array.size == 5) + "${array[4]},${array[3]},${array[2]},${array[1]},${array[0]}" + } + val uniqueCombinations = combinationStrings.toSet() + assertThat(combinationStrings).hasSize(uniqueCombinations.size) + } + + private fun expectedDownDestination( + downFromEdge: Boolean, + isSingleShade: Boolean, + ): SceneKey { + return if (downFromEdge && isSingleShade) Scenes.QuickSettings else Scenes.Shade + } + } + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val sceneInteractor by lazy { kosmos.sceneInteractor } + @JvmField @Parameter(0) var canSwipeToEnter: Boolean = false + @JvmField @Parameter(1) var downWithTwoPointers: Boolean = false + @JvmField @Parameter(2) var downFromEdge: Boolean = false + @JvmField @Parameter(3) var isSingleShade: Boolean = true + @JvmField @Parameter(4) var isCommunalAvailable: Boolean = false + private val underTest by lazy { createLockscreenSceneViewModel() } @Test - fun upTransitionSceneKey_canSwipeToUnlock_gone() = + @EnableFlags(Flags.FLAG_COMMUNAL_HUB) + fun destinationScenes() = testScope.runTest { - val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - kosmos.fakeAuthenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.None - ) kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) - kosmos.fakeDeviceEntryRepository.setUnlocked(true) - sceneInteractor.changeScene(Scenes.Lockscreen, "reason") - - assertThat(upTransitionSceneKey).isEqualTo(Scenes.Gone) - } - - @Test - fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() = - testScope.runTest { - val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin + if (canSwipeToEnter) { + AuthenticationMethodModel.None + } else { + AuthenticationMethodModel.Pin + } ) - kosmos.fakeDeviceEntryRepository.setUnlocked(false) + kosmos.fakeDeviceEntryRepository.setUnlocked(canSwipeToEnter) sceneInteractor.changeScene(Scenes.Lockscreen, "reason") + kosmos.shadeRepository.setShadeMode( + if (isSingleShade) { + ShadeMode.Single + } else { + ShadeMode.Split + } + ) + kosmos.setCommunalAvailable(isCommunalAvailable) - assertThat(upTransitionSceneKey).isEqualTo(Scenes.Bouncer) - } - - @EnableFlags(FLAG_COMMUNAL_HUB) - @Test - fun leftTransitionSceneKey_communalIsAvailable_communal() = - testScope.runTest { - val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey) - assertThat(leftDestinationSceneKey).isNull() + val destinationScenes by collectLastValue(underTest.destinationScenes) - kosmos.setCommunalAvailable(true) - runCurrent() - assertThat(leftDestinationSceneKey).isEqualTo(Scenes.Communal) - } + assertThat( + destinationScenes + ?.get( + Swipe( + SwipeDirection.Down, + fromSource = Edge.Top.takeIf { downFromEdge }, + pointerCount = if (downWithTwoPointers) 2 else 1, + ) + ) + ?.toScene + ) + .isEqualTo( + expectedDownDestination( + downFromEdge = downFromEdge, + isSingleShade = isSingleShade, + ) + ) - @Test - fun downFromTopEdgeDestinationSceneKey_whenNotSplitShade_quickSettings() = - testScope.runTest { - overrideResource(R.bool.config_use_split_notification_shade, false) - kosmos.shadeStartable.start() - val sceneKey by collectLastValue(underTest.downFromTopEdgeDestinationSceneKey) - assertThat(sceneKey).isEqualTo(Scenes.QuickSettings) - } + assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene) + .isEqualTo(if (canSwipeToEnter) Scenes.Gone else Scenes.Bouncer) - @Test - fun downFromTopEdgeDestinationSceneKey_whenSplitShade_null() = - testScope.runTest { - overrideResource(R.bool.config_use_split_notification_shade, true) - kosmos.shadeStartable.start() - val sceneKey by collectLastValue(underTest.downFromTopEdgeDestinationSceneKey) - assertThat(sceneKey).isNull() + assertThat(destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene) + .isEqualTo(Scenes.Communal.takeIf { isCommunalAvailable }) } private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 42c33544416d..af9abcda73fe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -26,7 +26,6 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.internal.R import com.android.internal.util.EmergencyAffordanceManager import com.android.internal.util.emergencyAffordanceManager @@ -317,8 +316,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() = testScope.runTest { - val upDestinationSceneKey by - collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) + val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes) + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition( to = upDestinationSceneKey, @@ -337,8 +336,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { testScope.runTest { setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) - val upDestinationSceneKey by - collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) + val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes) + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone) emulateUserDrivenTransition( to = upDestinationSceneKey, @@ -356,7 +355,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { emulateUserDrivenTransition(to = Scenes.Shade) assertCurrentScene(Scenes.Shade) - val upDestinationSceneKey = destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Lockscreen) emulateUserDrivenTransition( to = upDestinationSceneKey, @@ -379,7 +378,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { emulateUserDrivenTransition(to = Scenes.Shade) assertCurrentScene(Scenes.Shade) - val upDestinationSceneKey = destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone) emulateUserDrivenTransition( to = upDestinationSceneKey, @@ -447,8 +446,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() = testScope.runTest { unlockDevice() - val upDestinationSceneKey by - collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) + val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes) + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone) } @@ -469,8 +468,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() = testScope.runTest { setAuthMethod(AuthenticationMethodModel.Password) - val upDestinationSceneKey by - collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) + val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes) + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition( to = upDestinationSceneKey, @@ -487,8 +486,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun bouncerActionButtonClick_opensEmergencyServicesDialer() = testScope.runTest { setAuthMethod(AuthenticationMethodModel.Password) - val upDestinationSceneKey by - collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) + val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes) + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition(to = upDestinationSceneKey) @@ -507,8 +506,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { testScope.runTest { setAuthMethod(AuthenticationMethodModel.Password) startPhoneCall() - val upDestinationSceneKey by - collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) + val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes) + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition(to = upDestinationSceneKey) diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index f51e1098f333..7341015e8690 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -304,6 +304,15 @@ <!-- An explanation text that the password needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] --> <string name="kg_prompt_reason_timeout_password">For additional security, use password instead</string> + <!-- An explanation text that the pin needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] --> + <string name="kg_prompt_added_security_pin">PIN required for additional security</string> + + <!-- An explanation text that the pattern needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] --> + <string name="kg_prompt_added_security_pattern">Pattern required for additional security</string> + + <!-- An explanation text that the password needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] --> + <string name="kg_prompt_added_security_password">Password required for additional security</string> + <!-- An explanation text that the credential needs to be entered because a device admin has locked the device. [CHAR LIMIT=80] --> <string name="kg_prompt_reason_device_admin">Device locked by admin</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 4e7809ade792..59516be65a5e 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -965,6 +965,10 @@ <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item> </style> + <style name="Widget.SliceView.VolumePanel"> + <item name="hideHeaderRow">true</item> + </style> + <style name="Theme.VolumePanelActivity.Popup" parent="@style/Theme.SystemUI.Dialog"> <item name="android:dialogCornerRadius">44dp</item> <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainerHigh diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 84c8ea708031..26e91b62d19a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -122,7 +122,7 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { case PROMPT_REASON_USER_REQUEST: return R.string.kg_prompt_after_user_lockdown_password; case PROMPT_REASON_PREPARE_FOR_UPDATE: - return R.string.kg_prompt_reason_timeout_password; + return R.string.kg_prompt_added_security_password; case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: return R.string.kg_prompt_reason_timeout_password; case PROMPT_REASON_TRUSTAGENT_EXPIRED: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index bf8900da887a..caa74780538e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -323,7 +323,7 @@ public class KeyguardPatternViewController resId = R.string.kg_prompt_after_user_lockdown_pattern; break; case PROMPT_REASON_PREPARE_FOR_UPDATE: - resId = R.string.kg_prompt_reason_timeout_pattern; + resId = R.string.kg_prompt_added_security_pattern; break; case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: resId = R.string.kg_prompt_reason_timeout_pattern; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index bcab6f054dd6..fbe9edfd6680 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -134,7 +134,7 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView case PROMPT_REASON_USER_REQUEST: return R.string.kg_prompt_after_user_lockdown_pin; case PROMPT_REASON_PREPARE_FOR_UPDATE: - return R.string.kg_prompt_reason_timeout_pin; + return R.string.kg_prompt_added_security_pin; case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: return R.string.kg_prompt_reason_timeout_pin; case PROMPT_REASON_TRUSTAGENT_EXPIRED: diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt index c25e748f8668..3092defc8b0e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt @@ -23,10 +23,12 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.Flags +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.bouncer.data.repository.BouncerMessageRepository import com.android.systemui.bouncer.shared.model.BouncerMessageModel +import com.android.systemui.bouncer.shared.model.BouncerMessageStrings import com.android.systemui.bouncer.shared.model.Message import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -35,46 +37,6 @@ import com.android.systemui.flags.SystemPropertiesHelper import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.TrustRepository -import com.android.systemui.res.R.string.bouncer_face_not_recognized -import com.android.systemui.res.R.string.keyguard_enter_password -import com.android.systemui.res.R.string.keyguard_enter_pattern -import com.android.systemui.res.R.string.keyguard_enter_pin -import com.android.systemui.res.R.string.kg_bio_too_many_attempts_password -import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pattern -import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pin -import com.android.systemui.res.R.string.kg_bio_try_again_or_password -import com.android.systemui.res.R.string.kg_bio_try_again_or_pattern -import com.android.systemui.res.R.string.kg_bio_try_again_or_pin -import com.android.systemui.res.R.string.kg_face_locked_out -import com.android.systemui.res.R.string.kg_fp_not_recognized -import com.android.systemui.res.R.string.kg_primary_auth_locked_out_password -import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pattern -import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pin -import com.android.systemui.res.R.string.kg_prompt_after_adaptive_auth_lock -import com.android.systemui.res.R.string.kg_prompt_after_dpm_lock -import com.android.systemui.res.R.string.kg_prompt_after_update_password -import com.android.systemui.res.R.string.kg_prompt_after_update_pattern -import com.android.systemui.res.R.string.kg_prompt_after_update_pin -import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_password -import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pattern -import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pin -import com.android.systemui.res.R.string.kg_prompt_auth_timeout -import com.android.systemui.res.R.string.kg_prompt_password_auth_timeout -import com.android.systemui.res.R.string.kg_prompt_pattern_auth_timeout -import com.android.systemui.res.R.string.kg_prompt_pin_auth_timeout -import com.android.systemui.res.R.string.kg_prompt_reason_restart_password -import com.android.systemui.res.R.string.kg_prompt_reason_restart_pattern -import com.android.systemui.res.R.string.kg_prompt_reason_restart_pin -import com.android.systemui.res.R.string.kg_prompt_unattended_update -import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown -import com.android.systemui.res.R.string.kg_trust_agent_disabled -import com.android.systemui.res.R.string.kg_unlock_with_password_or_fp -import com.android.systemui.res.R.string.kg_unlock_with_pattern_or_fp -import com.android.systemui.res.R.string.kg_unlock_with_pin_or_fp -import com.android.systemui.res.R.string.kg_wrong_input_try_fp_suggestion -import com.android.systemui.res.R.string.kg_wrong_password_try_again -import com.android.systemui.res.R.string.kg_wrong_pattern_try_again -import com.android.systemui.res.R.string.kg_wrong_pin_try_again import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.Quint import javax.inject.Inject @@ -130,17 +92,22 @@ constructor( repository.setMessage( when (biometricSourceType) { BiometricSourceType.FINGERPRINT -> - incorrectFingerprintInput(currentSecurityMode) + BouncerMessageStrings.incorrectFingerprintInput( + currentSecurityMode.toAuthModel() + ) + .toMessage() BiometricSourceType.FACE -> - incorrectFaceInput( - currentSecurityMode, - isFingerprintAuthCurrentlyAllowed.value - ) + BouncerMessageStrings.incorrectFaceInput( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() else -> - defaultMessage( - currentSecurityMode, - isFingerprintAuthCurrentlyAllowed.value - ) + BouncerMessageStrings.defaultMessage( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } ) } @@ -189,45 +156,79 @@ constructor( trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot ) { if (wasRebootedForMainlineUpdate) { - authRequiredForMainlineUpdate(currentSecurityMode) + BouncerMessageStrings.authRequiredForMainlineUpdate( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else { - authRequiredAfterReboot(currentSecurityMode) + BouncerMessageStrings.authRequiredAfterReboot( + currentSecurityMode.toAuthModel() + ) + .toMessage() } } else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) { - authRequiredAfterPrimaryAuthTimeout(currentSecurityMode) + BouncerMessageStrings.authRequiredAfterPrimaryAuthTimeout( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) { - authRequiredAfterAdminLockdown(currentSecurityMode) + BouncerMessageStrings.authRequiredAfterAdminLockdown( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else if ( trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate ) { - authRequiredForUnattendedUpdate(currentSecurityMode) + BouncerMessageStrings.authRequiredForUnattendedUpdate( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else if (fpLockedOut) { - class3AuthLockedOut(currentSecurityMode) + BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel()) + .toMessage() } else if (faceLockedOut) { if (isFaceAuthClass3) { - class3AuthLockedOut(currentSecurityMode) + BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel()) + .toMessage() } else { - faceLockedOut(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + BouncerMessageStrings.faceLockedOut( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } } else if (flags.isSomeAuthRequiredAfterAdaptiveAuthRequest) { - authRequiredAfterAdaptiveAuthRequest( - currentSecurityMode, - isFingerprintAuthCurrentlyAllowed.value - ) + BouncerMessageStrings.authRequiredAfterAdaptiveAuthRequest( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } else if ( trustOrBiometricsAvailable && flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ) { - nonStrongAuthTimeout( - currentSecurityMode, - isFingerprintAuthCurrentlyAllowed.value - ) + BouncerMessageStrings.nonStrongAuthTimeout( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterUserRequest) { - trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + BouncerMessageStrings.trustAgentDisabled( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterTrustAgentExpired) { - trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + BouncerMessageStrings.trustAgentDisabled( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } else if (trustOrBiometricsAvailable && flags.isInUserLockdown) { - authRequiredAfterUserLockdown(currentSecurityMode) + BouncerMessageStrings.authRequiredAfterUserLockdown( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else { defaultMessage } @@ -244,7 +245,11 @@ constructor( override fun onTick(millisUntilFinished: Long) { val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt() - val message = primaryAuthLockedOut(currentSecurityMode) + val message = + BouncerMessageStrings.primaryAuthLockedOut( + currentSecurityMode.toAuthModel() + ) + .toMessage() message.message?.animate = false message.message?.formatterArgs = mutableMapOf<String, Any>(Pair("count", secondsRemaining)) @@ -258,7 +263,11 @@ constructor( if (!Flags.revampedBouncerMessages()) return repository.setMessage( - incorrectSecurityInput(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + BouncerMessageStrings.incorrectSecurityInput( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() ) } @@ -285,7 +294,12 @@ constructor( } private val defaultMessage: BouncerMessageModel - get() = defaultMessage(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + get() = + BouncerMessageStrings.defaultMessage( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() fun onPrimaryBouncerUserInput() { if (!Flags.revampedBouncerMessages()) return @@ -354,283 +368,35 @@ private fun defaultMessage( return BouncerMessageModel( message = Message( - messageResId = defaultMessage(securityMode, fpAuthIsAllowed).message?.messageResId, + messageResId = + BouncerMessageStrings.defaultMessage( + securityMode.toAuthModel(), + fpAuthIsAllowed + ) + .toMessage() + .message + ?.messageResId, animate = false ), secondaryMessage = Message(message = secondaryMessage, animate = false) ) } -private fun defaultMessage( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) { - defaultMessageWithFingerprint(securityMode) - } else - when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0) - SecurityMode.Password -> Pair(keyguard_enter_password, 0) - SecurityMode.PIN -> Pair(keyguard_enter_pin, 0) - else -> Pair(0, 0) - }.toMessage() -} - -private fun defaultMessageWithFingerprint(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, 0) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectSecurityInput( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) { - incorrectSecurityInputWithFingerprint(securityMode) - } else - when (securityMode) { - SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0) - SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0) - SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, 0) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion) - SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion) - SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, kg_wrong_input_try_fp_suggestion) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectFingerprintInput(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern) - SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password) - SecurityMode.PIN -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectFaceInput( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode) - else - when (securityMode) { - SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern) - SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password) - SecurityMode.PIN -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectFaceInputWithFingerprintAllowed( - securityMode: SecurityMode -): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, bouncer_face_not_recognized) - else -> Pair(0, 0) - }.toMessage() -} - -private fun biometricLockout(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterReboot(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterAdaptiveAuthRequest( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(securityMode) - else - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_adaptive_auth_lock) - SecurityMode.Password -> - Pair(keyguard_enter_password, kg_prompt_after_adaptive_auth_lock) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_adaptive_auth_lock) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterAdaptiveAuthRequestFingerprintAllowed( - securityMode: SecurityMode -): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> - Pair(kg_unlock_with_pattern_or_fp, kg_prompt_after_adaptive_auth_lock) - SecurityMode.Password -> - Pair(kg_unlock_with_password_or_fp, kg_prompt_after_adaptive_auth_lock) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_after_adaptive_auth_lock) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern) - SecurityMode.Password -> - Pair(keyguard_enter_password, kg_prompt_after_user_lockdown_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_unattended_update) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredForMainlineUpdate(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_update_pattern) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_update_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_update_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout) - else -> Pair(0, 0) - }.toMessage() -} - -private fun nonStrongAuthTimeout( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) { - nonStrongAuthTimeoutWithFingerprintAllowed(securityMode) - } else - when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_auth_timeout) - else -> Pair(0, 0) - }.toMessage() -} - -fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_auth_timeout) - else -> Pair(0, 0) - }.toMessage() -} - -private fun faceLockedOut( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) faceLockedOutButFingerprintAvailable(securityMode) - else - when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_face_locked_out) - else -> Pair(0, 0) - }.toMessage() -} - -private fun faceLockedOutButFingerprintAvailable(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_face_locked_out) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_face_locked_out) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_face_locked_out) - else -> Pair(0, 0) - }.toMessage() -} - -private fun class3AuthLockedOut(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun trustAgentDisabled( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) trustAgentDisabledWithFingerprintAllowed(securityMode) - else - when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_trust_agent_disabled) - else -> Pair(0, 0) - }.toMessage() -} - -private fun trustAgentDisabledWithFingerprintAllowed( - securityMode: SecurityMode -): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_trust_agent_disabled) - else -> Pair(0, 0) - }.toMessage() -} - -private fun primaryAuthLockedOut(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> - Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern) - SecurityMode.Password -> - Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_password) - SecurityMode.PIN -> - Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pin) - else -> Pair(0, 0) - }.toMessage() -} - private fun Pair<Int, Int>.toMessage(): BouncerMessageModel { return BouncerMessageModel( message = Message(messageResId = this.first, animate = false), secondaryMessage = Message(messageResId = this.second, animate = false) ) } + +private fun SecurityMode.toAuthModel(): AuthenticationMethodModel { + return when (this) { + SecurityMode.Invalid -> AuthenticationMethodModel.None + SecurityMode.None -> AuthenticationMethodModel.None + SecurityMode.Pattern -> AuthenticationMethodModel.Pattern + SecurityMode.Password -> AuthenticationMethodModel.Password + SecurityMode.PIN -> AuthenticationMethodModel.Pin + SecurityMode.SimPin -> AuthenticationMethodModel.Sim + SecurityMode.SimPuk -> AuthenticationMethodModel.Sim + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt new file mode 100644 index 000000000000..cb12ce50dd23 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt @@ -0,0 +1,267 @@ +/* + * 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.bouncer.shared.model + +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin +import com.android.systemui.res.R + +typealias BouncerMessagePair = Pair<Int, Int> + +val BouncerMessagePair.primaryMessage: Int + get() = this.first + +val BouncerMessagePair.secondaryMessage: Int + get() = this.second + +object BouncerMessageStrings { + private val EmptyMessage = Pair(0, 0) + + fun defaultMessage( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), 0) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), 0) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), 0) + else -> EmptyMessage + } + } + + fun incorrectSecurityInput( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMessage = incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed) + return when (securityMode) { + Pattern -> Pair(R.string.kg_wrong_pattern_try_again, secondaryMessage) + Password -> Pair(R.string.kg_wrong_password_try_again, secondaryMessage) + Pin -> Pair(R.string.kg_wrong_pin_try_again, secondaryMessage) + else -> EmptyMessage + } + } + + private fun incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed: Boolean): Int { + return if (fpAuthIsAllowed) R.string.kg_wrong_input_try_fp_suggestion else 0 + } + + fun incorrectFingerprintInput(securityMode: AuthenticationMethodModel): BouncerMessagePair { + val primaryMessage = R.string.kg_fp_not_recognized + return when (securityMode) { + Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern) + Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password) + Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin) + else -> EmptyMessage + } + } + + fun incorrectFaceInput( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode) + else { + val primaryMessage = R.string.bouncer_face_not_recognized + when (securityMode) { + Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern) + Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password) + Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin) + else -> EmptyMessage + } + } + } + + private fun incorrectFaceInputWithFingerprintAllowed( + securityMode: AuthenticationMethodModel + ): BouncerMessagePair { + val secondaryMsg = R.string.bouncer_face_not_recognized + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(true), secondaryMsg) + Password -> Pair(passwordDefaultMessage(true), secondaryMsg) + Pin -> Pair(pinDefaultMessage(true), secondaryMsg) + else -> EmptyMessage + } + } + + fun authRequiredAfterReboot(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_reason_restart_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_reason_restart_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_reason_restart_pin) + else -> EmptyMessage + } + } + + fun authRequiredAfterAdminLockdown( + securityMode: AuthenticationMethodModel + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_prompt_after_dpm_lock + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), secondaryMsg) + Password -> Pair(passwordDefaultMessage(false), secondaryMsg) + Pin -> Pair(pinDefaultMessage(false), secondaryMsg) + else -> EmptyMessage + } + } + + fun authRequiredAfterAdaptiveAuthRequest( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_prompt_after_adaptive_auth_lock + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg) + else -> EmptyMessage + } + } + + fun authRequiredAfterUserLockdown(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> + Pair(patternDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pin) + else -> EmptyMessage + } + } + + fun authRequiredForUnattendedUpdate( + securityMode: AuthenticationMethodModel + ): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_added_security_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_added_security_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_added_security_pin) + else -> EmptyMessage + } + } + + fun authRequiredForMainlineUpdate(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_after_update_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_update_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_update_pin) + else -> EmptyMessage + } + } + + fun authRequiredAfterPrimaryAuthTimeout( + securityMode: AuthenticationMethodModel + ): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_pattern_auth_timeout) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_password_auth_timeout) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_pin_auth_timeout) + else -> EmptyMessage + } + } + + fun nonStrongAuthTimeout( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_prompt_auth_timeout + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg) + else -> EmptyMessage + } + } + + fun faceLockedOut( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_face_locked_out + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg) + else -> EmptyMessage + } + } + + fun class3AuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_bio_too_many_attempts_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_bio_too_many_attempts_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_bio_too_many_attempts_pin) + else -> EmptyMessage + } + } + + fun trustAgentDisabled( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_trust_agent_disabled + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg) + else -> EmptyMessage + } + } + + fun primaryAuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> + Pair( + R.string.kg_too_many_failed_attempts_countdown, + R.string.kg_primary_auth_locked_out_pattern + ) + Password -> + Pair( + R.string.kg_too_many_failed_attempts_countdown, + R.string.kg_primary_auth_locked_out_password + ) + Pin -> + Pair( + R.string.kg_too_many_failed_attempts_countdown, + R.string.kg_primary_auth_locked_out_pin + ) + else -> EmptyMessage + } + } + + private fun patternDefaultMessage(fingerprintAllowed: Boolean): Int { + return if (fingerprintAllowed) R.string.kg_unlock_with_pattern_or_fp + else R.string.keyguard_enter_pattern + } + + private fun pinDefaultMessage(fingerprintAllowed: Boolean): Int { + return if (fingerprintAllowed) R.string.kg_unlock_with_pin_or_fp + else R.string.keyguard_enter_pin + } + + private fun passwordDefaultMessage(fingerprintAllowed: Boolean): Int { + return if (fingerprintAllowed) R.string.kg_unlock_with_password_or_fp + else R.string.keyguard_enter_password + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index 288ef3c52e21..993e81bfbf69 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -14,9 +14,15 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.keyguard.ui.viewmodel -import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.Edge +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -27,9 +33,10 @@ import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn /** Models UI state and handles user input for the lockscreen scene. */ @@ -44,37 +51,69 @@ constructor( val longPress: KeyguardLongPressViewModel, val notifications: NotificationsPlaceholderViewModel, ) { - /** The key of the scene we should switch to when swiping up. */ - val upDestinationSceneKey: StateFlow<SceneKey> = - deviceEntryInteractor.isUnlocked - .map { isUnlocked -> upDestinationSceneKey(isUnlocked) } + val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = + combine( + deviceEntryInteractor.isUnlocked, + communalInteractor.isCommunalAvailable, + shadeInteractor.shadeMode, + ) { isDeviceUnlocked, isCommunalAvailable, shadeMode -> + destinationScenes( + isDeviceUnlocked = isDeviceUnlocked, + isCommunalAvailable = isCommunalAvailable, + shadeMode = shadeMode, + ) + } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = upDestinationSceneKey(deviceEntryInteractor.isUnlocked.value), + initialValue = + destinationScenes( + isDeviceUnlocked = deviceEntryInteractor.isUnlocked.value, + isCommunalAvailable = false, + shadeMode = shadeInteractor.shadeMode.value, + ), ) - private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey { - return if (isUnlocked) Scenes.Gone else Scenes.Bouncer - } + private fun destinationScenes( + isDeviceUnlocked: Boolean, + isCommunalAvailable: Boolean, + shadeMode: ShadeMode, + ): Map<UserAction, UserActionResult> { + val quickSettingsIfSingleShade = + if (shadeMode is ShadeMode.Single) { + Scenes.QuickSettings + } else { + Scenes.Shade + } - /** The key of the scene we should switch to when swiping left. */ - val leftDestinationSceneKey: StateFlow<SceneKey?> = - communalInteractor.isCommunalAvailable - .map { available -> if (available) Scenes.Communal else null } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = null, - ) + return mapOf( + Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable }, + Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer, - /** The key of the scene we should switch to when swiping down from the top edge. */ - val downFromTopEdgeDestinationSceneKey: StateFlow<SceneKey?> = - shadeInteractor.shadeMode - .map { shadeMode -> Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single } } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = null, + // Swiping down from the top edge goes to QS (or shade if in split shade mode). + swipeDownFromTop(pointerCount = 1) to quickSettingsIfSingleShade, + swipeDownFromTop(pointerCount = 2) to quickSettingsIfSingleShade, + + // Swiping down, not from the edge, always navigates to the shade scene. + swipeDown(pointerCount = 1) to Scenes.Shade, + swipeDown(pointerCount = 2) to Scenes.Shade, ) + .filterValues { it != null } + .mapValues { checkNotNull(it.value) } + } + + private fun swipeDownFromTop(pointerCount: Int): Swipe { + return Swipe( + SwipeDirection.Down, + fromSource = Edge.Top, + pointerCount = pointerCount, + ) + } + + private fun swipeDown(pointerCount: Int): Swipe { + return Swipe( + SwipeDirection.Down, + pointerCount = pointerCount, + ) + } } 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 91c86dff34ea..9d0ea5ebd925 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -19,6 +19,7 @@ import static android.view.InputDevice.SOURCE_MOUSE; import static android.view.InputDevice.SOURCE_TOUCHPAD; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; +import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground; import static com.android.systemui.classifier.Classifier.BACK_GESTURE; import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll; import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe; @@ -54,7 +55,6 @@ import android.view.ISystemGestureExclusionListener; import android.view.IWindowManager; import android.view.InputDevice; import android.view.InputEvent; -import android.view.InputMonitor; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; @@ -104,6 +104,7 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import javax.inject.Inject; @@ -151,7 +152,12 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { @Override public void onTaskStackChanged() { - mGestureBlockingActivityRunning = isGestureBlockingActivityRunning(); + if (edgebackGestureHandlerGetRunningTasksBackground()) { + mBackgroundExecutor.execute(() -> mGestureBlockingActivityRunning.set( + isGestureBlockingActivityRunning())); + } else { + mGestureBlockingActivityRunning.set(isGestureBlockingActivityRunning()); + } } @Override public void onTaskCreated(int taskId, ComponentName componentName) { @@ -241,6 +247,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private final PointF mDownPoint = new PointF(); private final PointF mEndPoint = new PointF(); + private AtomicBoolean mGestureBlockingActivityRunning = new AtomicBoolean(); + private boolean mThresholdCrossed = false; private boolean mAllowGesture = false; private boolean mLogGesture = false; @@ -256,7 +264,6 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private boolean mIsEnabled; private boolean mIsNavBarShownTransiently; private boolean mIsBackGestureAllowed; - private boolean mGestureBlockingActivityRunning; private boolean mIsNewBackAffordanceEnabled; private boolean mIsTrackpadGestureFeaturesEnabled; private boolean mIsTrackpadThreeFingerSwipe; @@ -1017,7 +1024,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack mInRejectedExclusion = false; boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY()); boolean isBackAllowedCommon = !mDisabledForQuickstep && mIsBackGestureAllowed - && !mGestureBlockingActivityRunning + && !mGestureBlockingActivityRunning.get() && !QuickStepContract.isBackGestureDisabled(mSysUiFlags, mIsTrackpadThreeFingerSwipe) && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev); @@ -1053,8 +1060,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack curTime, curTimeStr, mAllowGesture, mIsTrackpadThreeFingerSwipe, mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed, QuickStepContract.isBackGestureDisabled(mSysUiFlags, - mIsTrackpadThreeFingerSwipe), - mDisabledForQuickstep, mGestureBlockingActivityRunning, mIsInPip, mDisplaySize, + mIsTrackpadThreeFingerSwipe), mDisabledForQuickstep, + mGestureBlockingActivityRunning.get(), mIsInPip, mDisplaySize, mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion)); } else if (mAllowGesture || mLogGesture) { if (!mThresholdCrossed) { @@ -1236,7 +1243,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed); pw.println(" mIsGestureHandlingEnabled=" + mIsGestureHandlingEnabled); pw.println(" mIsNavBarShownTransiently=" + mIsNavBarShownTransiently); - pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning); + pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning.get()); pw.println(" mAllowGesture=" + mAllowGesture); pw.println(" mUseMLModel=" + mUseMLModel); pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep); diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt index faf743475579..e8b1ee459384 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt @@ -21,6 +21,7 @@ import android.media.AudioManager import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.AudioStreamModel +import com.android.settingslib.volume.shared.model.RingerMode import com.android.systemui.common.shared.model.Icon import com.android.systemui.res.R import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor @@ -54,14 +55,6 @@ constructor( AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer, AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm, ) - private val mutedIconsByStream = - mapOf( - AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_volume_off, - AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_volume_off, - AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_volume_off, - AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_off, - AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_off, - ) private val labelsByStream = mapOf( AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music, @@ -81,8 +74,9 @@ constructor( combine( audioVolumeInteractor.getAudioStream(audioStream), audioVolumeInteractor.canChangeVolume(audioStream), - ) { model, isEnabled -> - model.toState(value, isEnabled) + audioVolumeInteractor.ringerMode, + ) { model, isEnabled, ringerMode -> + model.toState(value, isEnabled, ringerMode) } .stateIn(coroutineScope, SharingStarted.Eagerly, EmptyState) @@ -100,7 +94,11 @@ constructor( } } - private fun AudioStreamModel.toState(value: Float, isEnabled: Boolean): State { + private fun AudioStreamModel.toState( + value: Float, + isEnabled: Boolean, + ringerMode: RingerMode, + ): State { return State( value = volumeSliderInteractor.processVolumeToValue( @@ -110,7 +108,7 @@ constructor( isMuted, ), valueRange = volumeSliderInteractor.displayValueRange, - icon = getIcon(this), + icon = getIcon(ringerMode), label = labelsByStream[audioStream]?.let(context::getString) ?: error("No label for the stream: $audioStream"), disabledMessage = disabledTextByStream[audioStream]?.let(context::getString), @@ -119,14 +117,31 @@ constructor( ) } - private fun getIcon(model: AudioStreamModel): Icon { - val isMutedOrNoVolume = model.isMuted || model.volume == model.minVolume + private fun AudioStreamModel.getIcon(ringerMode: RingerMode): Icon { + val isMutedOrNoVolume = isMuted || volume == minVolume val iconRes = if (isMutedOrNoVolume) { - mutedIconsByStream + when (audioStream.value) { + AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off + AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off + AudioManager.STREAM_RING -> + if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { + R.drawable.ic_volume_ringer_vibrate + } else { + R.drawable.ic_volume_off + } + AudioManager.STREAM_NOTIFICATION -> + if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { + R.drawable.ic_volume_ringer_vibrate + } else { + R.drawable.ic_volume_off + } + AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off + else -> null + } } else { - iconsByStream - }[audioStream] + iconsByStream[audioStream] + } ?: error("No icon for the stream: $audioStream") return Icon.Resource(iconRes, null) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt index e7963031411d..701b7039a1ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt @@ -401,7 +401,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to Pair("Enter PIN", "PIN is required after lockdown"), LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to - Pair("Enter PIN", "Update will install when device not in use"), + Pair("Enter PIN", "PIN required for additional security"), LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to Pair( @@ -439,7 +439,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to Pair("Enter PIN", "PIN is required after lockdown"), LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to - Pair("Enter PIN", "Update will install when device not in use"), + Pair("Enter PIN", "PIN required for additional security"), LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to Pair( @@ -481,7 +481,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to Pair("Enter PIN", "PIN is required after lockdown"), LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to - Pair("Enter PIN", "Update will install when device not in use"), + Pair("Enter PIN", "PIN required for additional security"), LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to Pair( diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index 10e6ed4542c7..323917564bf2 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -59,6 +59,8 @@ import android.hardware.camera2.extension.Request; import android.hardware.camera2.extension.SizeList; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.PhysicalCaptureResultInfo; +import android.hardware.camera2.params.ColorSpaceProfiles; +import android.hardware.camera2.params.DynamicRangeProfiles; import android.hardware.camera2.utils.SurfaceUtils; import android.media.Image; import android.media.ImageReader; @@ -1228,7 +1230,6 @@ public class CameraExtensionsProxyService extends Service { return null; } - } private class CaptureCallbackStub implements SessionProcessorImpl.CaptureCallback { @@ -1585,11 +1586,13 @@ public class CameraExtensionsProxyService extends Service { Camera2SessionConfigImpl sessionConfig; if (LATENCY_IMPROVEMENTS_SUPPORTED) { + int outputsColorSpace = getColorSpaceFromOutputSurfaces(previewSurface, + imageCaptureSurface, postviewSurface); OutputSurfaceConfigurationImplStub outputSurfaceConfigs = new OutputSurfaceConfigurationImplStub(mOutputPreviewSurfaceImpl, // Image Analysis Output is currently only supported in CameraX mOutputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/, - mOutputPostviewSurfaceImpl); + mOutputPostviewSurfaceImpl, outputsColorSpace); sessionConfig = mSessionProcessor.initSession(cameraId, getCharacteristicsMap(charsMapNative), @@ -1616,6 +1619,11 @@ public class CameraExtensionsProxyService extends Service { } ret.outputConfigs.add(entry); } + if (Flags.extension10Bit() && EFV_SUPPORTED) { + ret.colorSpace = sessionConfig.getColorSpace(); + } else { + ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED; + } ret.sessionTemplateId = sessionConfig.getSessionTemplateId(); ret.sessionType = -1; if (LATENCY_IMPROVEMENTS_SUPPORTED) { @@ -1720,6 +1728,24 @@ public class CameraExtensionsProxyService extends Service { public void binderDied() { mSessionProcessor.deInitSession(); } + + // Get the color space of the output configurations. All of the OutputSurfaces + // can be assumed to have the same color space so return the color space + // of any non-null OutputSurface + private int getColorSpaceFromOutputSurfaces(OutputSurface previewSurface, + OutputSurface imageCaptureSurface, OutputSurface postviewSurface) { + int colorSpace = ColorSpaceProfiles.UNSPECIFIED; + + if (previewSurface.surface != null) { + colorSpace = previewSurface.colorSpace; + } else if (imageCaptureSurface.surface != null) { + colorSpace = imageCaptureSurface.colorSpace; + } else if (postviewSurface.surface != null) { + colorSpace = postviewSurface.colorSpace; + } + + return colorSpace; + } } private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl { @@ -1727,6 +1753,17 @@ public class CameraExtensionsProxyService extends Service { private OutputSurfaceImpl mOutputImageCaptureSurfaceImpl; private OutputSurfaceImpl mOutputImageAnalysisSurfaceImpl; private OutputSurfaceImpl mOutputPostviewSurfaceImpl; + private int mColorSpace; + + public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput, + OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput, + OutputSurfaceImpl postviewOutput, int colorSpace) { + mOutputPreviewSurfaceImpl = previewOutput; + mOutputImageCaptureSurfaceImpl = imageCaptureOutput; + mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput; + mOutputPostviewSurfaceImpl = postviewOutput; + mColorSpace = colorSpace; + } public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput, OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput, @@ -1735,6 +1772,7 @@ public class CameraExtensionsProxyService extends Service { mOutputImageCaptureSurfaceImpl = imageCaptureOutput; mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput; mOutputPostviewSurfaceImpl = postviewOutput; + mColorSpace = ColorSpaceProfiles.UNSPECIFIED; } @Override @@ -1756,6 +1794,11 @@ public class CameraExtensionsProxyService extends Service { public OutputSurfaceImpl getPostviewOutputSurface() { return mOutputPostviewSurfaceImpl; } + + @Override + public int getColorSpace() { + return mColorSpace; + } } private class OutputSurfaceImplStub implements OutputSurfaceImpl { @@ -1764,11 +1807,10 @@ public class CameraExtensionsProxyService extends Service { private final int mImageFormat; private final int mDataspace; private final long mUsage; + private final long mDynamicRangeProfile; public OutputSurfaceImplStub(OutputSurface outputSurface) { mSurface = outputSurface.surface; - mSize = new Size(outputSurface.size.width, outputSurface.size.height); - mImageFormat = outputSurface.imageFormat; if (mSurface != null) { mDataspace = SurfaceUtils.getSurfaceDataspace(mSurface); mUsage = SurfaceUtils.getSurfaceUsage(mSurface); @@ -1776,6 +1818,9 @@ public class CameraExtensionsProxyService extends Service { mDataspace = -1; mUsage = 0; } + mDynamicRangeProfile = outputSurface.dynamicRangeProfile; + mSize = new Size(outputSurface.size.width, outputSurface.size.height); + mImageFormat = outputSurface.imageFormat; } @Override @@ -1802,6 +1847,12 @@ public class CameraExtensionsProxyService extends Service { public long getUsage() { return mUsage; } + + @Override + public long getDynamicRangeProfile() { + return mDynamicRangeProfile; + } + } private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements @@ -2531,6 +2582,11 @@ public class CameraExtensionsProxyService extends Service { private static CameraOutputConfig getCameraOutputConfig(Camera2OutputConfigImpl output) { CameraOutputConfig ret = new CameraOutputConfig(); + if (Flags.extension10Bit() && EFV_SUPPORTED) { + ret.dynamicRangeProfile = output.getDynamicRangeProfile(); + } else { + ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD; + } ret.outputId = new OutputConfigId(); ret.outputId.id = output.getId(); ret.physicalCameraId = output.getPhysicalCameraId(); diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java index 0ef23e903b6a..e6bf2c968350 100644 --- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java +++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java @@ -61,7 +61,6 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; /** @@ -133,7 +132,11 @@ public class GrammaticalInflectionService extends SystemService { @Override public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) { - return canGetSystemGrammaticalGender(attributionSource) + if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) { + throw new SecurityException("AttributionSource: " + attributionSource + + " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission."); + } + return checkSystemTermsOfAddressIsEnabled() ? GrammaticalInflectionService.this.getSystemGrammaticalGender( attributionSource, userId) : GRAMMATICAL_GENDER_NOT_SPECIFIED; diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index c2f74a8895cb..c9fd2610bfb7 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -155,7 +155,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_CONFIG_DEFAULT_APPS, UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO, UserManager.DISALLOW_SIM_GLOBALLY, - UserManager.DISALLOW_ASSIST_CONTENT + UserManager.DISALLOW_ASSIST_CONTENT, + UserManager.DISALLOW_THREAD_NETWORK }); public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet( @@ -206,7 +207,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_ADD_WIFI_CONFIG, UserManager.DISALLOW_CELLULAR_2G, UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, - UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO + UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO, + UserManager.DISALLOW_THREAD_NETWORK ); /** @@ -252,7 +254,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_ADD_WIFI_CONFIG, UserManager.DISALLOW_CELLULAR_2G, UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, - UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO + UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO, + UserManager.DISALLOW_THREAD_NETWORK ); /** diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 71facab99fce..e713a827fc76 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -506,6 +506,10 @@ final class PolicyDefinition<V> { UserManager.DISALLOW_SIM_GLOBALLY, POLICY_FLAG_GLOBAL_ONLY_POLICY); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ASSIST_CONTENT, /* flags= */ 0); + if (com.android.net.thread.platform.flags.Flags.threadUserRestrictionEnabled()) { + USER_RESTRICTION_FLAGS.put( + UserManager.DISALLOW_THREAD_NETWORK, POLICY_FLAG_GLOBAL_ONLY_POLICY); + } for (String key : USER_RESTRICTION_FLAGS.keySet()) { createAndAddUserRestrictionPolicyDefinition(key, USER_RESTRICTION_FLAGS.get(key)); |