diff options
185 files changed, 3829 insertions, 698 deletions
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig index e73b434042af..788e82407926 100644 --- a/apex/jobscheduler/framework/aconfig/job.aconfig +++ b/apex/jobscheduler/framework/aconfig/job.aconfig @@ -13,3 +13,10 @@ flag { description: "Add APIs to let apps attach debug information to jobs" bug: "293491637" } + +flag { + name: "backup_jobs_exemption" + namespace: "backstage_power" + description: "Introduce a new RUN_BACKUP_JOBS permission and exemption logic allowing for longer running jobs for apps whose primary purpose is to backup or sync content." + bug: "318731461" +} diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 7a92cca74795..fc193d8147b5 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1840,7 +1840,9 @@ public class JobSchedulerService extends com.android.server.SystemService /* isFlexConstraintSatisfied */ false, jobStatus.canApplyTransportAffinities(), jobStatus.getNumAppliedFlexibleConstraints(), - jobStatus.getNumDroppedFlexibleConstraints()); + jobStatus.getNumDroppedFlexibleConstraints(), + jobStatus.getFilteredTraceTag(), + jobStatus.getFilteredDebugTags()); // If the job is immediately ready to run, then we can just immediately // put it in the pending list and try to schedule it. This is especially @@ -2288,7 +2290,9 @@ public class JobSchedulerService extends com.android.server.SystemService cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE), cancelled.canApplyTransportAffinities(), cancelled.getNumAppliedFlexibleConstraints(), - cancelled.getNumDroppedFlexibleConstraints()); + cancelled.getNumDroppedFlexibleConstraints(), + cancelled.getFilteredTraceTag(), + cancelled.getFilteredDebugTags()); } // If this is a replacement, bring in the new version of the job if (incomingJob != null) { @@ -5448,6 +5452,9 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print(Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE, Flags.throwOnUnsupportedBiasUsage()); pw.println(); + pw.print(android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION, + android.app.job.Flags.backupJobsExemption()); + pw.println(); pw.decreaseIndent(); pw.println(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java index 6f2393adfc7b..0cf6a7a8a4f6 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java @@ -356,6 +356,9 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { case com.android.server.job.Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE: pw.println(com.android.server.job.Flags.throwOnUnsupportedBiasUsage()); break; + case android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION: + pw.println(android.app.job.Flags.backupJobsExemption()); + break; default: pw.println("Unknown flag: " + flagName); break; diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index fe55e2702916..8ab7d2fae49f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -541,7 +541,9 @@ public final class JobServiceContext implements ServiceConnection { job.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE), job.canApplyTransportAffinities(), job.getNumAppliedFlexibleConstraints(), - job.getNumDroppedFlexibleConstraints()); + job.getNumDroppedFlexibleConstraints(), + job.getFilteredTraceTag(), + job.getFilteredDebugTags()); sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount()); final String sourcePackage = job.getSourcePackageName(); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { @@ -1630,7 +1632,9 @@ public final class JobServiceContext implements ServiceConnection { completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE), completedJob.canApplyTransportAffinities(), completedJob.getNumAppliedFlexibleConstraints(), - completedJob.getNumDroppedFlexibleConstraints()); + completedJob.getNumDroppedFlexibleConstraints(), + completedJob.getFilteredTraceTag(), + completedJob.getFilteredDebugTags()); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler", getId()); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index d39863c85f33..a4df5d829281 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -48,6 +48,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Pair; +import android.util.Patterns; import android.util.Range; import android.util.Slog; import android.util.TimeUtils; @@ -76,6 +77,7 @@ import java.util.Collections; import java.util.Objects; import java.util.Random; import java.util.function.Predicate; +import java.util.regex.Pattern; /** * Uniquely identifies a job internally. @@ -203,6 +205,17 @@ public final class JobStatus { // TODO(b/129954980): ensure this doesn't spam statsd, especially at boot private static final boolean STATS_LOG_ENABLED = false; + /** + * Simple patterns to match some common forms of PII. This is not intended all-encompassing and + * any clients should aim to do additional filtering. + */ + private static final ArrayMap<Pattern, String> BASIC_PII_FILTERS = new ArrayMap<>(); + + static { + BASIC_PII_FILTERS.put(Patterns.EMAIL_ADDRESS, "[EMAIL]"); + BASIC_PII_FILTERS.put(Patterns.PHONE, "[PHONE]"); + } + // No override. public static final int OVERRIDE_NONE = 0; // Override to improve sorting order. Does not affect constraint evaluation. @@ -250,6 +263,18 @@ public final class JobStatus { private final long mLoggingJobId; /** + * List of tags from {@link JobInfo#getDebugTags()}, filtered using {@link #BASIC_PII_FILTERS}. + * Lazily loaded in {@link #getFilteredDebugTags()}. + */ + @Nullable + private String[] mFilteredDebugTags; + /** + * Trace tag from {@link JobInfo#getTraceTag()}, filtered using {@link #BASIC_PII_FILTERS}. + * Lazily loaded in {@link #getFilteredTraceTag()}. + */ + @Nullable + private String mFilteredTraceTag; + /** * Tag to identify the wakelock held for this job. Lazily loaded in * {@link #getWakelockTag()} since it's not typically needed until the job is about to run. */ @@ -1325,6 +1350,47 @@ public final class JobStatus { return batteryName; } + @VisibleForTesting + @NonNull + static String applyBasicPiiFilters(@NonNull String val) { + for (int i = BASIC_PII_FILTERS.size() - 1; i >= 0; --i) { + val = BASIC_PII_FILTERS.keyAt(i).matcher(val).replaceAll(BASIC_PII_FILTERS.valueAt(i)); + } + return val; + } + + /** + * List of tags from {@link JobInfo#getDebugTags()}, filtered using a basic set of PII filters. + */ + @NonNull + public String[] getFilteredDebugTags() { + if (mFilteredDebugTags != null) { + return mFilteredDebugTags; + } + final ArraySet<String> debugTags = job.getDebugTagsArraySet(); + mFilteredDebugTags = new String[debugTags.size()]; + for (int i = 0; i < mFilteredDebugTags.length; ++i) { + mFilteredDebugTags[i] = applyBasicPiiFilters(debugTags.valueAt(i)); + } + return mFilteredDebugTags; + } + + /** + * Trace tag from {@link JobInfo#getTraceTag()}, filtered using a basic set of PII filters. + */ + @Nullable + public String getFilteredTraceTag() { + if (mFilteredTraceTag != null) { + return mFilteredTraceTag; + } + final String rawTag = job.getTraceTag(); + if (rawTag == null) { + return null; + } + mFilteredTraceTag = applyBasicPiiFilters(rawTag); + return mFilteredTraceTag; + } + /** Return the String to be used as the tag for the wakelock held for this job. */ @NonNull public String getWakelockTag() { diff --git a/core/api/current.txt b/core/api/current.txt index 3f0d10688466..8de5335a9b72 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -277,6 +277,7 @@ package android { field @FlaggedApi("android.companion.flags.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE"; field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY"; field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES"; + field @FlaggedApi("android.app.job.backup_jobs_exemption") public static final String RUN_BACKUP_JOBS = "android.permission.RUN_BACKUP_JOBS"; field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS"; field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM"; field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE"; @@ -284,6 +285,7 @@ package android { field public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM"; field public static final String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH"; field public static final String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE"; + field @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public static final String SET_BIOMETRIC_DIALOG_LOGO = "android.permission.SET_BIOMETRIC_DIALOG_LOGO"; field public static final String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP"; field @Deprecated public static final String SET_PREFERRED_APPLICATIONS = "android.permission.SET_PREFERRED_APPLICATIONS"; field public static final String SET_PROCESS_LIMIT = "android.permission.SET_PROCESS_LIMIT"; @@ -18722,8 +18724,8 @@ package android.hardware.biometrics { method @Nullable public int getAllowedAuthenticators(); method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView(); method @Nullable public CharSequence getDescription(); - method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.graphics.Bitmap getLogoBitmap(); - method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public int getLogoRes(); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.graphics.Bitmap getLogoBitmap(); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public int getLogoRes(); method @Nullable public CharSequence getNegativeButtonText(); method @Nullable public CharSequence getSubtitle(); method @NonNull public CharSequence getTitle(); @@ -18773,8 +18775,8 @@ package android.hardware.biometrics { method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence); method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean); - method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@DrawableRes int); - method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@NonNull android.graphics.Bitmap); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoBitmap(@NonNull android.graphics.Bitmap); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoRes(@DrawableRes int); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence); @@ -33468,6 +33470,7 @@ package android.os { field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi"; field public static final String DISALLOW_SMS = "no_sms"; field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs"; + field @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String DISALLOW_THREAD_NETWORK = "no_thread_network"; field public static final String DISALLOW_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio"; field public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password"; field public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps"; @@ -53664,13 +53667,13 @@ package android.view { method @Deprecated public android.view.Display getDefaultDisplay(); method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics(); method public default boolean isCrossWindowBlurEnabled(); - method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.os.IBinder registerBatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerBatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver); method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void registerTrustedPresentationListener(@NonNull android.os.IBinder, @NonNull android.window.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); - method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.os.IBinder registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver); method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer); method public void removeViewImmediate(android.view.View); - method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.os.IBinder); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.view.SurfaceControl); method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"; field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index d18001d4d5bf..ea008ac2b5c0 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -871,6 +871,10 @@ package android.app { field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR; } + @FlaggedApi("android.app.bic_client") public final class BackgroundInstallControlManager { + method @FlaggedApi("android.app.bic_client") @NonNull @RequiresPermission(android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES) public java.util.List<android.content.pm.PackageInfo> getBackgroundInstalledPackages(long); + } + public class BroadcastOptions { method public void clearRequireCompatChange(); method public int getPendingIntentBackgroundActivityStartMode(); @@ -3388,11 +3392,10 @@ package android.companion.virtual.camera { } @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final class VirtualCameraConfig.Builder { - ctor public VirtualCameraConfig.Builder(); + ctor public VirtualCameraConfig.Builder(@NonNull String); method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int, @IntRange(from=1) int); method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build(); method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setLensFacing(int); - method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setName(@NonNull String); method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setSensorOrientation(int); method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback); } @@ -14773,6 +14776,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String); method public boolean needsOtaServiceProvisioning(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled(); + method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticParams); method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot(); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerCarrierPrivilegesCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesCallback); @@ -14951,6 +14955,21 @@ package android.telephony { method public default void onCarrierServiceChanged(@Nullable String, int); } + @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class TelephonyManager.EmergencyCallDiagnosticParams { + method public long getLogcatCollectionStartTimeMillis(); + method public boolean isLogcatCollectionEnabled(); + method public boolean isTelecomDumpSysCollectionEnabled(); + method public boolean isTelephonyDumpSysCollectionEnabled(); + } + + public static final class TelephonyManager.EmergencyCallDiagnosticParams.Builder { + ctor public TelephonyManager.EmergencyCallDiagnosticParams.Builder(); + method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams build(); + method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setLogcatCollectionStartTimeMillis(long); + method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setTelecomDumpSysCollectionEnabled(boolean); + method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setTelephonyDumpSysCollectionEnabled(boolean); + } + public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception { ctor public TelephonyManager.ModemActivityInfoException(int); method public int getErrorCode(); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index b8b98a3cb3ba..7a3d320dddab 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3646,6 +3646,7 @@ package android.view { public interface WindowManager extends android.view.ViewManager { method public default int getDisplayImePolicy(int); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @Nullable public default android.os.IBinder getSurfaceControlInputClientToken(@NonNull android.view.SurfaceControl); method public static boolean hasWindowExtensionsEnabled(); method public default void holdLock(android.os.IBinder, int); method public default boolean isGlobalKey(int); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index ccd8456129eb..00c4b0f6515f 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1548,9 +1548,16 @@ public class AppOpsManager { public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER = AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER; + /** + * Allows an app whose primary use case is to backup or sync content to run longer jobs. + * + * @hide + */ + public static final int OP_RUN_BACKUP_JOBS = AppProtoEnums.APP_OP_RUN_BACKUP_JOBS; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 144; + public static final int _NUM_OP = 145; /** * All app ops represented as strings. @@ -1700,6 +1707,7 @@ public class AppOpsManager { OPSTR_ENABLE_MOBILE_DATA_BY_USER, OPSTR_RESERVED_FOR_TESTING, OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, + OPSTR_RUN_BACKUP_JOBS, }) public @interface AppOpString {} @@ -2392,6 +2400,13 @@ public class AppOpsManager { public static final String OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER = "android:read_system_grammatical_gender"; + /** + * Allows an app whose primary use case is to backup or sync content to run longer jobs. + * + * @hide + */ + public static final String OPSTR_RUN_BACKUP_JOBS = "android:run_backup_jobs"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -2504,6 +2519,7 @@ public class AppOpsManager { OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, OP_MEDIA_ROUTING_CONTROL, OP_READ_SYSTEM_GRAMMATICAL_GENDER, + OP_RUN_BACKUP_JOBS, }; static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{ @@ -2961,6 +2977,8 @@ public class AppOpsManager { // will make it an app-op permission in the future. // .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER) .build(), + new AppOpInfo.Builder(OP_RUN_BACKUP_JOBS, OPSTR_RUN_BACKUP_JOBS, "RUN_BACKUP_JOBS") + .setPermission(Manifest.permission.RUN_BACKUP_JOBS).build(), }; // The number of longs needed to form a full bitmask of app ops diff --git a/core/java/android/app/BackgroundInstallControlManager.java b/core/java/android/app/BackgroundInstallControlManager.java new file mode 100644 index 000000000000..664fcebcfc05 --- /dev/null +++ b/core/java/android/app/BackgroundInstallControlManager.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES; +import static android.annotation.SystemApi.Client.PRIVILEGED_APPS; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; +import android.content.pm.IBackgroundInstallControlService; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.RemoteException; +import android.os.ServiceManager; + +import java.util.List; + +/** + * BackgroundInstallControlManager client allows apps to query apps installed in background. + * + * <p>Any applications that was installed without an accompanying installer UI activity paired + * with recorded user interaction event is considered background installed. This is determined by + * analysis of user-activity logs. + * + * <p>Warning: BackgroundInstallControl should not be considered a definitive + * authority of identifying background installed applications. Consumers can use this as a + * supplementary signal, but must perform additional due diligence to confirm the install nature + * of the package. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_BIC_CLIENT) +@SystemApi(client = PRIVILEGED_APPS) +@SystemService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE) +public final class BackgroundInstallControlManager { + + private static final String TAG = "BackgroundInstallControlManager"; + private static IBackgroundInstallControlService sService; + private final Context mContext; + + BackgroundInstallControlManager(Context context) { + mContext = context; + } + + private static IBackgroundInstallControlService getService() { + if (sService == null) { + sService = + IBackgroundInstallControlService.Stub.asInterface( + ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE)); + } + return sService; + } + + /** + * Returns a full list of {@link PackageInfo} of apps currently installed for the current user + * that are considered installed in the background. + * + * <p>Refer to top level doc {@link BackgroundInstallControlManager} for more details on + * background-installed applications. + * <p> + * + * @param flags - Flags will be used to call + * {@link PackageManager#getInstalledPackages(PackageInfoFlags)} to retrieve installed packages. + * @return A list of packages retrieved from {@link PackageManager} with non-background + * installed app filter applied. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_BIC_CLIENT) + @SystemApi + @RequiresPermission(GET_BACKGROUND_INSTALLED_PACKAGES) + public @NonNull List<PackageInfo> getBackgroundInstalledPackages( + @PackageManager.PackageInfoFlagsBits long flags) { + List<PackageInfo> backgroundInstalledPackages; + try { + return getService() + .getBackgroundInstalledPackages(flags, mContext.getUserId()) + .getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + +} diff --git a/core/java/android/app/HomeVisibilityListener.java b/core/java/android/app/HomeVisibilityListener.java index 1f5f2e4c8237..5dd7ab0f99fa 100644 --- a/core/java/android/app/HomeVisibilityListener.java +++ b/core/java/android/app/HomeVisibilityListener.java @@ -69,6 +69,11 @@ public abstract class HomeVisibilityListener { public HomeVisibilityListener() { mObserver = new android.app.IProcessObserver.Stub() { @Override + public void onProcessStarted(int pid, int processUid, int packageUid, + String packageName, String processName) { + } + + @Override public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) { refreshHomeVisibility(); } diff --git a/core/java/android/app/IProcessObserver.aidl b/core/java/android/app/IProcessObserver.aidl index 7be3620f317b..5c5e72cf9d6f 100644 --- a/core/java/android/app/IProcessObserver.aidl +++ b/core/java/android/app/IProcessObserver.aidl @@ -18,6 +18,17 @@ package android.app; /** {@hide} */ oneway interface IProcessObserver { + /** + * Invoked when an app process starts up. + * + * @param pid The pid of the process. + * @param processUid The UID associated with the process. + * @param packageUid The UID associated with the package. + * @param packageName The name of the package. + * @param processName The name of the process. + */ + void onProcessStarted(int pid, int processUid, int packageUid, + @utf8InCpp String packageName, @utf8InCpp String processName); void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities); void onForegroundServicesChanged(int pid, int uid, int serviceTypes); void onProcessDied(int pid, int uid); diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 0760d4db9169..3b5bba20a10b 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -90,8 +90,8 @@ per-file InstantAppResolveInfo.aidl = file:/services/core/java/com/android/serve per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS # BackgroundInstallControlManager -per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/OWNERS -per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/OWNERS +per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS +per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS # ResourcesManager per-file ResourcesManager.java = file:RESOURCES_OWNERS diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java index 350cf3d832d6..06a0f5c09e18 100644 --- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java +++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java @@ -196,13 +196,12 @@ public final class VirtualCameraConfig implements Parcelable { * <li>At least one stream must be added with {@link #addStreamConfig(int, int, int, int)}. * <li>A callback must be set with {@link #setVirtualCameraCallback(Executor, * VirtualCameraCallback)} - * <li>A camera name must be set with {@link #setName(String)} * <li>A lens facing must be set with {@link #setLensFacing(int)} */ @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) public static final class Builder { - private String mName; + private final String mName; private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>(); private Executor mCallbackExecutor; private VirtualCameraCallback mCallback; @@ -210,12 +209,12 @@ public final class VirtualCameraConfig implements Parcelable { private int mLensFacing = LENS_FACING_UNKNOWN; /** - * Sets the name of the virtual camera instance. + * Creates a new instance of {@link Builder}. + * + * @param name The name of the {@link VirtualCamera}. */ - @NonNull - public Builder setName(@NonNull String name) { - mName = requireNonNull(name, "Display name cannot be null"); - return this; + public Builder(@NonNull String name) { + mName = requireNonNull(name, "Name cannot be null"); } /** diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index c0424dbeb813..bdaf9d789960 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -16,7 +16,7 @@ package android.hardware.biometrics; -import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG; +import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO; import static android.Manifest.permission.TEST_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; @@ -174,9 +174,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @return This builder. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) - @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO) @NonNull - public BiometricPrompt.Builder setLogo(@DrawableRes int logoRes) { + public BiometricPrompt.Builder setLogoRes(@DrawableRes int logoRes) { mPromptInfo.setLogoRes(logoRes); return this; } @@ -193,9 +193,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @return This builder. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) - @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO) @NonNull - public BiometricPrompt.Builder setLogo(@NonNull Bitmap logoBitmap) { + public BiometricPrompt.Builder setLogoBitmap(@NonNull Bitmap logoBitmap) { mPromptInfo.setLogoBitmap(logoBitmap); return this; } @@ -719,25 +719,25 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan /** * Gets the drawable resource of the logo for the prompt, as set by - * {@link Builder#setLogo(int)}. Currently for system applications use only. + * {@link Builder#setLogoRes(int)}. Currently for system applications use only. * * @return The drawable resource of the logo, or -1 if the prompt has no logo resource set. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) - @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO) @DrawableRes public int getLogoRes() { return mPromptInfo.getLogoRes(); } /** - * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogo(Bitmap)}. Currently for - * system applications use only. + * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogoBitmap(Bitmap)}. + * Currently for system applications use only. * * @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) - @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO) @Nullable public Bitmap getLogoBitmap() { return mPromptInfo.getLogoBitmap(); diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index d788b37c781d..0f9cadc52608 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -166,9 +166,9 @@ public class PromptInfo implements Parcelable { } /** - * Returns whether MANAGE_BIOMETRIC_DIALOG is contained. + * Returns whether SET_BIOMETRIC_DIALOG_LOGO is contained. */ - public boolean containsManageBioApiConfigurations() { + public boolean containsSetLogoApiConfigurations() { if (mLogoRes != -1) { return true; } else if (mLogoBitmap != null) { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 533946d89706..d6df8d940904 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1898,6 +1898,30 @@ public class UserManager { "no_near_field_communication_radio"; /** + * This user restriction specifies if Thread network is disallowed on the device. If Thread + * network is disallowed it cannot be turned on via Settings. + * + * <p>This restriction can only be set by a device owner or a profile owner of an + * organization-owned managed profile on the parent profile. + * In both cases, the restriction applies globally on the device and will turn off the + * Thread network radio if it's currently on and prevent the radio from being turned + * on in the future. + * + * <p> <a href="https://www.threadgroup.org">Thread</a> is a low-power and low-latency wireless + * mesh networking protocol built on IPv6. + * + * <p>Default is <code>false</code>. + * + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") + public static final String DISALLOW_THREAD_NETWORK = "no_thread_network"; + + /** * List of key values that can be passed into the various user restriction related methods * in {@link UserManager} & {@link DevicePolicyManager}. * Note: This is slightly different from the real set of user restrictions listed in {@link @@ -1983,6 +2007,7 @@ public class UserManager { DISALLOW_ULTRA_WIDEBAND_RADIO, DISALLOW_GRANT_ADMIN, DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO, + DISALLOW_THREAD_NETWORK, }) @Retention(RetentionPolicy.SOURCE) public @interface UserRestrictionKey {} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e3a41ba05ec6..11edcafecdee 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -444,6 +444,18 @@ public final class Settings { "android.settings.ACCESSIBILITY_DETAILS_SETTINGS"; /** + * Activity Action: Show settings to allow configuration of an accessibility + * shortcut belonging to an accessibility feature or features. + * <p> + * Input: ":settings:show_fragment_args" must contain "targets" denoting the services to edit. + * <p> + * Output: Nothing. + * @hide + **/ + public static final String ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS = + "android.settings.ACCESSIBILITY_SHORTCUT_SETTINGS"; + + /** * Activity Action: Show settings to allow configuration of accessibility color and motion. * <p> * In some cases, a matching Activity may not exist, so ensure you diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index 5ad2502d1546..298bdb881e9f 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -622,6 +622,15 @@ public abstract class AutofillService extends Service { new FillCallback(callback, request.getId()))); } + @Override + public void onConvertCredentialRequest( + @NonNull ConvertCredentialRequest convertCredentialRequest, + @NonNull IConvertCredentialCallback convertCredentialCallback) { + mHandler.sendMessage(obtainMessage( + AutofillService::onConvertCredentialRequest, + AutofillService.this, convertCredentialRequest, + new ConvertCredentialCallback(convertCredentialCallback))); + } @Override public void onFillCredentialRequest(FillRequest request, IFillCallback callback, @@ -707,7 +716,19 @@ public abstract class AutofillService extends Service { */ public void onFillCredentialRequest(@NonNull FillRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback, - IAutoFillManagerClient autofillClientCallback) {} + @NonNull IAutoFillManagerClient autofillClientCallback) {} + + /** + * Called by the Android system to convert a credential manager response to a dataset + * + * @param convertCredentialRequest the request that has the original credential manager response + * @param convertCredentialCallback callback used to notify the result of the request. + * + * @hide + */ + public void onConvertCredentialRequest( + @NonNull ConvertCredentialRequest convertCredentialRequest, + @NonNull ConvertCredentialCallback convertCredentialCallback){} /** * Called when the user requests the service to save the contents of a screen. diff --git a/core/java/android/service/autofill/ConvertCredentialCallback.java b/core/java/android/service/autofill/ConvertCredentialCallback.java new file mode 100644 index 000000000000..a39f01115338 --- /dev/null +++ b/core/java/android/service/autofill/ConvertCredentialCallback.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.RemoteException; + +/** + * <p><code>ConvertCredentialCallback</code> handles convertCredentialResponse from Autofill + * Service. + * + * @hide + */ +public final class ConvertCredentialCallback { + + private static final String TAG = "ConvertCredentialCallback"; + + private final IConvertCredentialCallback mCallback; + + /** @hide */ + public ConvertCredentialCallback(IConvertCredentialCallback callback) { + mCallback = callback; + } + + /** + * Notifies the Android System that a convertCredentialRequest was fulfilled by the service. + * + * @param convertCredentialResponse the result + */ + public void onSuccess(@NonNull ConvertCredentialResponse convertCredentialResponse) { + try { + mCallback.onSuccess(convertCredentialResponse); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Notifies the Android System that a convert credential request has failed + * + * @param message the error message + */ + public void onFailure(@Nullable CharSequence message) { + try { + mCallback.onFailure(message); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } +} diff --git a/core/java/android/service/autofill/ConvertCredentialRequest.aidl b/core/java/android/service/autofill/ConvertCredentialRequest.aidl new file mode 100644 index 000000000000..79681e2dbe84 --- /dev/null +++ b/core/java/android/service/autofill/ConvertCredentialRequest.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2024, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +parcelable ConvertCredentialRequest;
\ No newline at end of file diff --git a/core/java/android/service/autofill/ConvertCredentialRequest.java b/core/java/android/service/autofill/ConvertCredentialRequest.java new file mode 100644 index 000000000000..d2d7556f40b7 --- /dev/null +++ b/core/java/android/service/autofill/ConvertCredentialRequest.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.annotation.NonNull; +import android.credentials.GetCredentialResponse; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + + +/** + * This class represents a request to an autofill service to convert the credential manager response + * to a dataset. + * + * @hide + */ +@DataClass( + genToString = true, + genHiddenConstructor = true, + genHiddenConstDefs = true) +public final class ConvertCredentialRequest implements Parcelable { + private final @NonNull GetCredentialResponse mGetCredentialResponse; + private final @NonNull Bundle mClientState; + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/ConvertCredentialRequest.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Creates a new ConvertCredentialRequest. + * + * @hide + */ + @DataClass.Generated.Member + public ConvertCredentialRequest( + @NonNull GetCredentialResponse getCredentialResponse, + @NonNull Bundle clientState) { + this.mGetCredentialResponse = getCredentialResponse; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mGetCredentialResponse); + this.mClientState = clientState; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mClientState); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @NonNull GetCredentialResponse getGetCredentialResponse() { + return mGetCredentialResponse; + } + + @DataClass.Generated.Member + public @NonNull Bundle getClientState() { + return mClientState; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "ConvertCredentialRequest { " + + "getCredentialResponse = " + mGetCredentialResponse + ", " + + "clientState = " + mClientState + + " }"; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeTypedObject(mGetCredentialResponse, flags); + dest.writeBundle(mClientState); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ ConvertCredentialRequest(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + GetCredentialResponse getCredentialResponse = (GetCredentialResponse) in.readTypedObject(GetCredentialResponse.CREATOR); + Bundle clientState = in.readBundle(); + + this.mGetCredentialResponse = getCredentialResponse; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mGetCredentialResponse); + this.mClientState = clientState; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mClientState); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<ConvertCredentialRequest> CREATOR + = new Parcelable.Creator<ConvertCredentialRequest>() { + @Override + public ConvertCredentialRequest[] newArray(int size) { + return new ConvertCredentialRequest[size]; + } + + @Override + public ConvertCredentialRequest createFromParcel(@NonNull Parcel in) { + return new ConvertCredentialRequest(in); + } + }; + + @DataClass.Generated( + time = 1706132305002L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/service/autofill/ConvertCredentialRequest.java", + inputSignatures = "private final @android.annotation.NonNull android.credentials.GetCredentialResponse mGetCredentialResponse\nprivate final @android.annotation.NonNull android.os.Bundle mClientState\nclass ConvertCredentialRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/service/autofill/ConvertCredentialResponse.aidl b/core/java/android/service/autofill/ConvertCredentialResponse.aidl new file mode 100644 index 000000000000..98ac6f67c521 --- /dev/null +++ b/core/java/android/service/autofill/ConvertCredentialResponse.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2024, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +parcelable ConvertCredentialResponse;
\ No newline at end of file diff --git a/core/java/android/service/autofill/ConvertCredentialResponse.java b/core/java/android/service/autofill/ConvertCredentialResponse.java new file mode 100644 index 000000000000..5da4f63f31f9 --- /dev/null +++ b/core/java/android/service/autofill/ConvertCredentialResponse.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +/** + * Response for a {@Link ConvertCredentialRequest} + * + * @hide + */ +@DataClass( + genToString = true, + genHiddenConstructor = true, + genHiddenConstDefs = true) +public final class ConvertCredentialResponse implements Parcelable { + private final @NonNull Dataset mDataset; + private final @Nullable Bundle mClientState; + + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/ConvertCredentialResponse.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Creates a new ConvertCredentialResponse. + * + * @hide + */ + @DataClass.Generated.Member + public ConvertCredentialResponse( + @NonNull Dataset dataset, + @Nullable Bundle clientState) { + this.mDataset = dataset; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDataset); + this.mClientState = clientState; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @NonNull Dataset getDataset() { + return mDataset; + } + + @DataClass.Generated.Member + public @Nullable Bundle getClientState() { + return mClientState; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "ConvertCredentialResponse { " + + "dataset = " + mDataset + ", " + + "clientState = " + mClientState + + " }"; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mClientState != null) flg |= 0x2; + dest.writeByte(flg); + dest.writeTypedObject(mDataset, flags); + if (mClientState != null) dest.writeBundle(mClientState); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ ConvertCredentialResponse(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + Dataset dataset = (Dataset) in.readTypedObject(Dataset.CREATOR); + Bundle clientState = (flg & 0x2) == 0 ? null : in.readBundle(); + + this.mDataset = dataset; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDataset); + this.mClientState = clientState; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<ConvertCredentialResponse> CREATOR + = new Parcelable.Creator<ConvertCredentialResponse>() { + @Override + public ConvertCredentialResponse[] newArray(int size) { + return new ConvertCredentialResponse[size]; + } + + @Override + public ConvertCredentialResponse createFromParcel(@NonNull Parcel in) { + return new ConvertCredentialResponse(in); + } + }; + + @DataClass.Generated( + time = 1706132669373L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/service/autofill/ConvertCredentialResponse.java", + inputSignatures = "private final @android.annotation.NonNull android.service.autofill.Dataset mDataset\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nclass ConvertCredentialResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java index 5d58120ef5bb..98dda1031eff 100644 --- a/core/java/android/service/autofill/FillEventHistory.java +++ b/core/java/android/service/autofill/FillEventHistory.java @@ -309,12 +309,19 @@ public final class FillEventHistory implements Parcelable { /** The autofill suggestion is shown as a dialog presentation. */ public static final int UI_TYPE_DIALOG = 3; + /** + * The autofill suggestion is shown os a credman bottom sheet + * @hide + */ + public static final int UI_TYPE_CREDMAN_BOTTOM_SHEET = 4; + /** @hide */ @IntDef(prefix = { "UI_TYPE_" }, value = { UI_TYPE_UNKNOWN, UI_TYPE_MENU, UI_TYPE_INLINE, - UI_TYPE_DIALOG + UI_TYPE_DIALOG, + UI_TYPE_CREDMAN_BOTTOM_SHEET }) @Retention(RetentionPolicy.SOURCE) public @interface UiType {} @@ -755,6 +762,8 @@ public final class FillEventHistory implements Parcelable { return "UI_TYPE_INLINE"; case UI_TYPE_DIALOG: return "UI_TYPE_FILL_DIALOG"; + case UI_TYPE_CREDMAN_BOTTOM_SHEET: + return "UI_TYPE_CREDMAN_BOTTOM_SHEET"; default: return "UI_TYPE_UNKNOWN"; } diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl index 03ead3266521..2c2feae7aeea 100644 --- a/core/java/android/service/autofill/IAutoFillService.aidl +++ b/core/java/android/service/autofill/IAutoFillService.aidl @@ -16,6 +16,8 @@ package android.service.autofill; +import android.service.autofill.ConvertCredentialRequest; +import android.service.autofill.IConvertCredentialCallback; import android.service.autofill.FillRequest; import android.service.autofill.IFillCallback; import android.service.autofill.ISaveCallback; @@ -32,7 +34,8 @@ oneway interface IAutoFillService { void onConnectedStateChanged(boolean connected); void onFillRequest(in FillRequest request, in IFillCallback callback); void onFillCredentialRequest(in FillRequest request, in IFillCallback callback, - in IAutoFillManagerClient client); + in IAutoFillManagerClient client); void onSaveRequest(in SaveRequest request, in ISaveCallback callback); void onSavedPasswordCountRequest(in IResultReceiver receiver); + void onConvertCredentialRequest(in ConvertCredentialRequest convertCredentialRequest, in IConvertCredentialCallback convertCredentialCallback); } diff --git a/core/java/android/service/autofill/IConvertCredentialCallback.aidl b/core/java/android/service/autofill/IConvertCredentialCallback.aidl new file mode 100644 index 000000000000..9dfc29429876 --- /dev/null +++ b/core/java/android/service/autofill/IConvertCredentialCallback.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.os.ICancellationSignal; + +import android.service.autofill.ConvertCredentialResponse; + +/** + * Interface to receive the result of a convert credential request + * + * @hide + */ +oneway interface IConvertCredentialCallback { + void onSuccess(in ConvertCredentialResponse convertCredentialResponse); + void onFailure(CharSequence message); +} diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index ba7874eb2d21..a1f44e4c9622 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -1256,13 +1256,13 @@ public class Surface implements Parcelable { } private static void registerNativeMemoryUsage() { - if (Flags.enableSurfaceNativeAllocRegistration()) { + if (Flags.enableSurfaceNativeAllocRegistrationRo()) { VMRuntime.getRuntime().registerNativeAllocation(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES); } } private static void freeNativeMemoryUsage() { - if (Flags.enableSurfaceNativeAllocRegistration()) { + if (Flags.enableSurfaceNativeAllocRegistrationRo()) { VMRuntime.getRuntime().registerNativeFree(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES); } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 42355bb17c2d..427d053f754e 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -6022,8 +6022,8 @@ public interface WindowManager extends ViewManager { * This is different from * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper, * SurfaceControlInputReceiver)} in that the input events are received batched. The caller must - * invoke {@link #unregisterSurfaceControlInputReceiver(IBinder)} to clean up the resources when - * no longer needing to use the {@link SurfaceControlInputReceiver} + * invoke {@link #unregisterSurfaceControlInputReceiver(SurfaceControl)} to clean up the + * resources when no longer needing to use the {@link SurfaceControlInputReceiver} * * @param displayId The display that the SurfaceControl will be placed on. Input will * only work @@ -6035,14 +6035,9 @@ public interface WindowManager extends ViewManager { * @param choreographer The Choreographer used for batching. This should match the rendering * Choreographer. * @param receiver The SurfaceControlInputReceiver that will receive the input events - * @return an {@link IBinder} token that is used to unregister the input receiver via - * {@link #unregisterSurfaceControlInputReceiver(IBinder)}. - * @see #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper, - * SurfaceControlInputReceiver) */ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) - @NonNull - default IBinder registerBatchedSurfaceControlInputReceiver(int displayId, + default void registerBatchedSurfaceControlInputReceiver(int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) { throw new UnsupportedOperationException( @@ -6054,8 +6049,8 @@ public interface WindowManager extends ViewManager { * receive every input event. This is different than calling @link * #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer, * SurfaceControlInputReceiver)} in that the input events are received unbatched. The caller - * must invoke {@link #unregisterSurfaceControlInputReceiver(IBinder)} to clean up the resources - * when no longer needing to use the {@link SurfaceControlInputReceiver} + * must invoke {@link #unregisterSurfaceControlInputReceiver(SurfaceControl)} to clean up the + * resources when no longer needing to use the {@link SurfaceControlInputReceiver} * * @param displayId The display that the SurfaceControl will be placed on. Input will only * work if SurfaceControl is on that display and that display was @@ -6066,14 +6061,9 @@ public interface WindowManager extends ViewManager { * @param surfaceControl The SurfaceControl to register the InputChannel for * @param looper The looper to use when invoking callbacks. * @param receiver The SurfaceControlInputReceiver that will receive the input events - * @return an {@link IBinder} token that is used to unregister the input receiver via - * {@link #unregisterSurfaceControlInputReceiver(IBinder)}. - * @see #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer, - * SurfaceControlInputReceiver) **/ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) - @NonNull - default IBinder registerUnbatchedSurfaceControlInputReceiver(int displayId, + default void registerUnbatchedSurfaceControlInputReceiver(int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) { throw new UnsupportedOperationException( @@ -6091,17 +6081,32 @@ public interface WindowManager extends ViewManager { * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper, * SurfaceControlInputReceiver)} * - * @param token The token that was returned via - * {@link #registerBatchedSurfaceControlInputReceiver(int, IBinder, - * SurfaceControl, - * Choreographer, SurfaceControlInputReceiver)} or - * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, - * SurfaceControl, - * Looper, SurfaceControlInputReceiver)} + * @param surfaceControl The SurfaceControl to remove and unregister the input channel for. */ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) - default void unregisterSurfaceControlInputReceiver(@NonNull IBinder token) { + default void unregisterSurfaceControlInputReceiver(@NonNull SurfaceControl surfaceControl) { throw new UnsupportedOperationException( "unregisterSurfaceControlInputReceiver is not implemented"); } + + /** + * Returns the input client token for the {@link SurfaceControl}. This will only return non null + * if the SurfaceControl was registered for input via + * { #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer, + * SurfaceControlInputReceiver)} or + * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper, + * SurfaceControlInputReceiver)}. + * <p> + * This is helpful for testing to ensure the test waits for the layer to be registered with + * SurfaceFlinger and Input before proceeding with the test. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) + @TestApi + @Nullable + default IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) { + throw new UnsupportedOperationException( + "getSurfaceControlInputClientToken is not implemented"); + } } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 8d40f9a4f7b1..c49fce5b558f 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -38,10 +38,12 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.Pair; +import android.util.SparseArray; import android.view.inputmethod.InputMethodManager; import android.window.ITrustedPresentationListener; import android.window.TrustedPresentationThresholds; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.FastPrintWriter; import java.io.FileDescriptor; @@ -50,7 +52,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.WeakHashMap; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.IntConsumer; @@ -156,8 +157,9 @@ public final class WindowManagerGlobal { private final TrustedPresentationListener mTrustedPresentationListener = new TrustedPresentationListener(); - private final ConcurrentHashMap<IBinder, InputEventReceiver> mSurfaceControlInputReceivers = - new ConcurrentHashMap<>(); + @GuardedBy("mSurfaceControlInputReceivers") + private final SparseArray<SurfaceControlInputReceiverInfo> + mSurfaceControlInputReceivers = new SparseArray<>(); private WindowManagerGlobal() { } @@ -816,7 +818,7 @@ public final class WindowManagerGlobal { mTrustedPresentationListener.removeListener(listener); } - IBinder registerBatchedSurfaceControlInputReceiver(int displayId, + void registerBatchedSurfaceControlInputReceiver(int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) { IBinder clientToken = new Binder(); @@ -830,19 +832,21 @@ public final class WindowManagerGlobal { e.rethrowAsRuntimeException(); } - mSurfaceControlInputReceivers.put(clientToken, - new BatchedInputEventReceiver(inputChannel, choreographer.getLooper(), - choreographer) { - @Override - public void onInputEvent(InputEvent event) { - boolean handled = receiver.onInputEvent(event); - finishInputEvent(event, handled); - } - }); - return clientToken; + synchronized (mSurfaceControlInputReceivers) { + mSurfaceControlInputReceivers.put(surfaceControl.getLayerId(), + new SurfaceControlInputReceiverInfo(clientToken, + new BatchedInputEventReceiver(inputChannel, choreographer.getLooper(), + choreographer) { + @Override + public void onInputEvent(InputEvent event) { + boolean handled = receiver.onInputEvent(event); + finishInputEvent(event, handled); + } + })); + } } - IBinder registerUnbatchedSurfaceControlInputReceiver( + void registerUnbatchedSurfaceControlInputReceiver( int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) { IBinder clientToken = new Binder(); @@ -856,32 +860,53 @@ public final class WindowManagerGlobal { e.rethrowAsRuntimeException(); } - mSurfaceControlInputReceivers.put(clientToken, - new InputEventReceiver(inputChannel, looper) { - @Override - public void onInputEvent(InputEvent event) { - boolean handled = receiver.onInputEvent(event); - finishInputEvent(event, handled); - } - }); - - return clientToken; + synchronized (mSurfaceControlInputReceivers) { + mSurfaceControlInputReceivers.put(surfaceControl.getLayerId(), + new SurfaceControlInputReceiverInfo(clientToken, + new InputEventReceiver(inputChannel, looper) { + @Override + public void onInputEvent(InputEvent event) { + boolean handled = receiver.onInputEvent(event); + finishInputEvent(event, handled); + } + })); + } } - void unregisterSurfaceControlInputReceiver(IBinder token) { - InputEventReceiver inputEventReceiver = mSurfaceControlInputReceivers.get(token); - if (inputEventReceiver == null) { - Log.w(TAG, "No registered input event receiver with token: " + token); + void unregisterSurfaceControlInputReceiver(SurfaceControl surfaceControl) { + SurfaceControlInputReceiverInfo surfaceControlInputReceiverInfo; + synchronized (mSurfaceControlInputReceivers) { + surfaceControlInputReceiverInfo = mSurfaceControlInputReceivers.removeReturnOld( + surfaceControl.getLayerId()); + } + + if (surfaceControlInputReceiverInfo == null) { + Log.w(TAG, "No registered input event receiver with sc: " + surfaceControl); return; } try { - WindowManagerGlobal.getWindowSession().remove(token); + WindowManagerGlobal.getWindowSession().remove( + surfaceControlInputReceiverInfo.mClientToken); } catch (RemoteException e) { Log.e(TAG, "Failed to remove input channel", e); e.rethrowAsRuntimeException(); } - inputEventReceiver.dispose(); + surfaceControlInputReceiverInfo.mInputEventReceiver.dispose(); + } + + IBinder getSurfaceControlInputClientToken(SurfaceControl surfaceControl) { + SurfaceControlInputReceiverInfo surfaceControlInputReceiverInfo; + synchronized (mSurfaceControlInputReceivers) { + surfaceControlInputReceiverInfo = mSurfaceControlInputReceivers.get( + surfaceControl.getLayerId()); + } + + if (surfaceControlInputReceiverInfo == null) { + Log.w(TAG, "No registered input event receiver with sc: " + surfaceControl); + return null; + } + return surfaceControlInputReceiverInfo.mClientToken; } private final class TrustedPresentationListener extends @@ -976,6 +1001,17 @@ public final class WindowManagerGlobal { throw e.rethrowFromSystemServer(); } } + + private static class SurfaceControlInputReceiverInfo { + final IBinder mClientToken; + final InputEventReceiver mInputEventReceiver; + + private SurfaceControlInputReceiverInfo(IBinder clientToken, + InputEventReceiver inputEventReceiver) { + mClientToken = clientToken; + mInputEventReceiver = inputEventReceiver; + } + } } final class WindowLeaked extends AndroidRuntimeException { diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index aaf5fcc6f095..41d181c1b10c 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -523,26 +523,30 @@ public final class WindowManagerImpl implements WindowManager { mGlobal.unregisterTrustedPresentationListener(listener); } - @NonNull @Override - public IBinder registerBatchedSurfaceControlInputReceiver(int displayId, + public void registerBatchedSurfaceControlInputReceiver(int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) { - return mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostToken, + mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostToken, surfaceControl, choreographer, receiver); } - @NonNull @Override - public IBinder registerUnbatchedSurfaceControlInputReceiver( + public void registerUnbatchedSurfaceControlInputReceiver( int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) { - return mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, hostToken, + mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, hostToken, surfaceControl, looper, receiver); } @Override - public void unregisterSurfaceControlInputReceiver(@NonNull IBinder token) { - mGlobal.unregisterSurfaceControlInputReceiver(token); + public void unregisterSurfaceControlInputReceiver(@NonNull SurfaceControl surfaceControl) { + mGlobal.unregisterSurfaceControlInputReceiver(surfaceControl); + } + + @Override + @Nullable + public IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) { + return mGlobal.getSurfaceControlInputClientToken(surfaceControl); } } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index dbeffc89fa09..559ccfea72c2 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -298,6 +298,15 @@ public final class AutofillManager { "android.view.autofill.extra.AUGMENTED_AUTOFILL_CLIENT"; /** + * Internal extra used to pass the fill request id in client state of + * {@link ConvertCredentialResponse} + * + * @hide + */ + public static final String EXTRA_AUTOFILL_REQUEST_ID = + "android.view.autofill.extra.AUTOFILL_REQUEST_ID"; + + /** * Autofill Hint to indicate that it can match any field. * * @hide diff --git a/core/java/android/view/autofill/OWNERS b/core/java/android/view/autofill/OWNERS index 37c6f5bf3425..898947adcd1b 100644 --- a/core/java/android/view/autofill/OWNERS +++ b/core/java/android/view/autofill/OWNERS @@ -4,6 +4,7 @@ simranjit@google.com haoranzhang@google.com skxu@google.com yunicorn@google.com +reemabajwa@google.com # Bug component: 543785 = per-file *Augmented* per-file *Augmented* = wangqi@google.com diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 86804c6117c7..65075aea7b27 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -226,9 +226,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { setTopOnBackInvokedCallback(null); } - // We should also stop running animations since all callbacks have been removed. - // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler. - Handler.getMain().post(mProgressAnimator::reset); mAllCallbacks.clear(); mOnBackInvokedCallbacks.clear(); } @@ -442,8 +439,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return WindowOnBackInvokedDispatcher .isOnBackInvokedCallbackEnabled(activityInfo, applicationInfo, - () -> originalContext.obtainStyledAttributes( - new int[] {android.R.attr.windowSwipeToDismiss}), true); + () -> originalContext); } @Override @@ -501,7 +497,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { */ public static boolean isOnBackInvokedCallbackEnabled(@Nullable ActivityInfo activityInfo, @NonNull ApplicationInfo applicationInfo, - @NonNull Supplier<TypedArray> windowAttrSupplier, boolean recycleTypedArray) { + @NonNull Supplier<Context> contextSupplier) { // new back is enabled if the feature flag is enabled AND the app does not explicitly // request legacy back. if (!ENABLE_PREDICTIVE_BACK) { @@ -547,15 +543,15 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { // setTrigger(true) // Use the original context to resolve the styled attribute so that they stay // true to the window. - TypedArray windowAttr = windowAttrSupplier.get(); + final Context context = contextSupplier.get(); boolean windowSwipeToDismiss = true; - if (windowAttr != null) { - if (windowAttr.getIndexCount() > 0) { - windowSwipeToDismiss = windowAttr.getBoolean(0, true); - } - if (recycleTypedArray) { - windowAttr.recycle(); + if (context != null) { + final TypedArray array = context.obtainStyledAttributes( + new int[]{android.R.attr.windowSwipeToDismiss}); + if (array.getIndexCount() > 0) { + windowSwipeToDismiss = array.getBoolean(0, true); } + array.recycle(); } if (DEBUG) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 723eb70a00d9..0e0af4db17a0 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6654,7 +6654,14 @@ <!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide --> <permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" - android:protectionLevel="signature" /> + android:protectionLevel="signature" /> + + <!-- Allows an application to set the BiometricDialog (SystemUI) logo . + <p>Not for use by third-party applications. + @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") + --> + <permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" + android:protectionLevel="signature" /> <!-- Allows an application to control keyguard. Only allowed for system processes. @hide --> @@ -7801,6 +7808,16 @@ <permission android:name="android.permission.RUN_USER_INITIATED_JOBS" android:protectionLevel="normal"/> + <!-- @FlaggedApi("android.app.job.backup_jobs_exemption") + Gives applications whose <b>primary use case</b> is to backup or sync content increased + job execution allowance in order to complete the related work. The jobs must have a valid + content URI trigger and network constraint set. + <p>This is a special access permission that can be revoked by the system or the user. + <p>Protection level: signature|privileged|appop + --> + <permission android:name="android.permission.RUN_BACKUP_JOBS" + android:protectionLevel="signature|privileged|appop"/> + <!-- Allows an app access to the installer provided app metadata. @SystemApi @hide @@ -8374,6 +8391,16 @@ </intent-filter> </receiver> + <!-- Broadcast Receiver listens to sufficient verifier broadcast from Package Manager + when installing new SDK. Verification of SDK code during installation time is run + to determine compatibility with privacy sandbox restrictions. --> + <receiver android:name="com.android.server.sdksandbox.SdkSandboxVerifierReceiver" + android:exported="false"> + <intent-filter> + <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/> + </intent-filter> + </receiver> + <service android:name="android.hardware.location.GeofenceHardwareService" android:permission="android.permission.LOCATION_HARDWARE" android:exported="false" /> diff --git a/core/res/res/color-night/notification_expand_button_state_tint.xml b/core/res/res/color-night/notification_expand_button_state_tint.xml deleted file mode 100644 index a794d53c7e71..000000000000 --- a/core/res/res/color-night/notification_expand_button_state_tint.xml +++ /dev/null @@ -1,21 +0,0 @@ -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.06"/> - <item android:state_hovered="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.03"/> - <item android:color="@android:color/system_on_surface_dark" android:alpha="0.00"/> -</selector>
\ No newline at end of file diff --git a/core/res/res/color/notification_expand_button_state_tint.xml b/core/res/res/color/notification_expand_button_state_tint.xml index 67b2c2568bb1..5a8594f0e461 100644 --- a/core/res/res/color/notification_expand_button_state_tint.xml +++ b/core/res/res/color/notification_expand_button_state_tint.xml @@ -14,8 +14,11 @@ ~ limitations under the License. --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="true" android:color="@android:color/system_on_surface_light" android:alpha="0.12"/> - <item android:state_hovered="true" android:color="@android:color/system_on_surface_light" android:alpha="0.08"/> - <item android:color="@android:color/system_on_surface_light" android:alpha="0.00"/> +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item android:state_pressed="true" android:color="?androidprv:attr/materialColorOnPrimaryFixed" + android:alpha="0.15"/> + <item android:state_hovered="true" android:color="?androidprv:attr/materialColorOnPrimaryFixed" + android:alpha="0.11"/> + <item android:color="@color/transparent" /> </selector>
\ No newline at end of file diff --git a/core/tests/InputMethodCoreTests/Android.bp b/core/tests/InputMethodCoreTests/Android.bp new file mode 100644 index 000000000000..ac6462589e16 --- /dev/null +++ b/core/tests/InputMethodCoreTests/Android.bp @@ -0,0 +1,66 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "InputMethodCoreTests", + + srcs: [ + "src/**/*.java", + "src/**/*.kt", + "src/**/I*.aidl", + ], + + dxflags: ["--core-library"], + + static_libs: [ + "collector-device-lib-platform", + "android-common", + "frameworks-core-util-lib", + "androidx.core_core", + "androidx.core_core-ktx", + "androidx.test.ext.junit", + "androidx.test.runner", + "androidx.test.rules", + "flag-junit", + "junit-params", + "kotlin-test", + "mockito-target-minus-junit4", + "platform-test-annotations", + "platform-compat-test-rules", + "truth", + "print-test-util-lib", + "testng", + "device-time-shell-utils", + "testables", + "flag-junit", + ], + + libs: [ + "android.test.runner", + "android.test.base", + "android.test.mock", + "framework", + "ext", + "framework-res", + ], + + sdk_version: "core_platform", + test_suites: [ + "device-tests", + "automotive-tests", + ], + + certificate: "platform", + + resource_dirs: ["res"], + + data: [ + ":com.android.cts.helpers.aosp", + ], +} diff --git a/core/tests/InputMethodCoreTests/AndroidManifest.xml b/core/tests/InputMethodCoreTests/AndroidManifest.xml new file mode 100644 index 000000000000..8d00d0f755bf --- /dev/null +++ b/core/tests/InputMethodCoreTests/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + android:installLocation="internalOnly" + package="com.android.frameworks.inputmethodcoretests" + android:sharedUserId="com.android.uid.test"> + + <application + android:supportsRtl="true" + android:enableOnBackInvokedCallback="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.inputmethodcoretests" + android:label="InputMethod Core Tests" /> +</manifest> diff --git a/core/tests/InputMethodCoreTests/AndroidTest.xml b/core/tests/InputMethodCoreTests/AndroidTest.xml new file mode 100644 index 000000000000..fa585d8d1075 --- /dev/null +++ b/core/tests/InputMethodCoreTests/AndroidTest.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs InputMethod Core Tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="InputMethodCoreTests.apk" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <!-- TODO(b/254155965): Design a mechanism to finally remove this command. --> + <option name="run-command" value="settings put global device_config_sync_disabled 0" /> + </target_preparer> + + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DeviceInteractionHelperInstaller" /> + + <option name="test-tag" value="InputMethodCoreTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.frameworks.inputmethodcoretests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/core/tests/InputMethodCoreTests/OWNERS b/core/tests/InputMethodCoreTests/OWNERS new file mode 100644 index 000000000000..5deb2ce8f24b --- /dev/null +++ b/core/tests/InputMethodCoreTests/OWNERS @@ -0,0 +1 @@ +include /core/java/android/view/inputmethod/OWNERS diff --git a/core/tests/coretests/res/xml/ime_meta.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta.xml index a975718c50e9..a975718c50e9 100644 --- a/core/tests/coretests/res/xml/ime_meta.xml +++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta.xml diff --git a/core/tests/coretests/res/xml/ime_meta_inline_suggestions.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions.xml index e67bf6331bfe..e67bf6331bfe 100644 --- a/core/tests/coretests/res/xml/ime_meta_inline_suggestions.xml +++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions.xml diff --git a/core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml index 34402089b47d..34402089b47d 100644 --- a/core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml +++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml diff --git a/core/tests/coretests/res/xml/ime_meta_sw_next.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_sw_next.xml index 2e2ee33e9933..2e2ee33e9933 100644 --- a/core/tests/coretests/res/xml/ime_meta_sw_next.xml +++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_sw_next.xml diff --git a/core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_virtual_device_only.xml index 1905365808bc..1905365808bc 100644 --- a/core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml +++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_virtual_device_only.xml diff --git a/core/tests/coretests/res/xml/ime_meta_vr_only.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_vr_only.xml index 653a8ffcb944..653a8ffcb944 100644 --- a/core/tests/coretests/res/xml/ime_meta_vr_only.xml +++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_vr_only.xml diff --git a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/BaseInputConnectionTest.java index f04f603564f6..f04f603564f6 100644 --- a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/BaseInputConnectionTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java index 9d7d71d8d539..9d7d71d8d539 100644 --- a/core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java index d7b911dda672..d7b911dda672 100644 --- a/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java index 4839dd27b283..4839dd27b283 100644 --- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java index 909af7b4c5fb..a3f537ef5f1c 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java @@ -32,7 +32,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.frameworks.coretests.R; +import com.android.frameworks.inputmethodcoretests.R; import org.junit.Rule; import org.junit.Test; diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodManagerTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java index d70572444128..d70572444128 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InputMethodManagerTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java index e7b1110f898a..e7b1110f898a 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java index 5095cad1b607..5095cad1b607 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java index 47a724d36038..47a724d36038 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java index a94f8772fcd0..a94f8772fcd0 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java index 90f7d06857c7..90f7d06857c7 100644 --- a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java index b2eb07c0a9e7..b2eb07c0a9e7 100644 --- a/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java index df63a4aaaefe..df63a4aaaefe 100644 --- a/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java index f264cc630dc5..f264cc630dc5 100644 --- a/core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SurroundingTextTest.java index 047f33074460..047f33074460 100644 --- a/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SurroundingTextTest.java diff --git a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/TextAppearanceInfoTest.java index 0750cf1a64ab..0750cf1a64ab 100644 --- a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/TextAppearanceInfoTest.java diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt index 07471f08e0f5..07471f08e0f5 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java index a6262944e8b0..a6262944e8b0 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java index 32bfdcb7b217..32bfdcb7b217 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java index f111bf6fcd64..f111bf6fcd64 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java index ba6390808151..ba6390808151 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index a709d7be898b..52ff0d4037e8 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -20,6 +20,7 @@ import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT; import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.atLeast; @@ -358,7 +359,7 @@ public class WindowOnBackInvokedDispatcherTest { } @Test - public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException { + public void onDetachFromWindow_cancelsBackAnimation() throws RemoteException { mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo(); @@ -368,13 +369,12 @@ public class WindowOnBackInvokedDispatcherTest { waitForIdle(); verify(mCallback1).onBackStarted(any(BackEvent.class)); - // This should trigger mCallback1.onBackCancelled() + // This should trigger mCallback1.onBackCancelled() and unset the callback in WM mDispatcher.detachFromWindow(); - // This should be ignored by mCallback1 - callbackInfo.getCallback().onBackInvoked(); + OnBackInvokedCallbackInfo callbackInfo1 = assertSetCallbackInfo(); + assertNull(callbackInfo1); waitForIdle(); - verify(mCallback1, never()).onBackInvoked(); verify(mCallback1).onBackCancelled(); } } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 2de305f8e925..62c9e16f753a 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -450,7 +450,7 @@ applications that come with the platform <!-- Permissions required for CTS test - android.server.biometrics --> <permission name="android.permission.USE_BIOMETRIC" /> <permission name="android.permission.TEST_BIOMETRIC" /> - <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" /> + <permission name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" /> <permission name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" /> <!-- Permissions required for CTS test - CtsContactsProviderTestCases --> <permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" /> diff --git a/libs/WindowManager/Shell/multivalentTests/OWNERS b/libs/WindowManager/Shell/multivalentTests/OWNERS new file mode 100644 index 000000000000..24c1a3a6d400 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/OWNERS @@ -0,0 +1,4 @@ +atsjenk@google.com +liranb@google.com +madym@google.com + diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java index 1b1ebc39b558..4cbb78f2dae2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.pip.phone; +package com.android.wm.shell.common.pip; import android.annotation.IntDef; import android.annotation.NonNull; import android.graphics.Rect; -import com.android.wm.shell.common.pip.PipBoundsState; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -50,9 +48,9 @@ public class PipDoubleTapHelper { @Retention(RetentionPolicy.SOURCE) @interface PipSizeSpec {} - static final int SIZE_SPEC_DEFAULT = 0; - static final int SIZE_SPEC_MAX = 1; - static final int SIZE_SPEC_CUSTOM = 2; + public static final int SIZE_SPEC_DEFAULT = 0; + public static final int SIZE_SPEC_MAX = 1; + public static final int SIZE_SPEC_CUSTOM = 2; /** * Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from. @@ -84,7 +82,7 @@ public class PipDoubleTapHelper { * @return pip screen size to switch to */ @PipSizeSpec - static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState, + public static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState, @NonNull Rect userResizeBounds) { // is pip screen at its maximum boolean isScreenMax = mPipBoundsState.getBounds().width() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 3b48c67a5bbd..7b98fa6523cb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -50,15 +50,16 @@ import java.util.Optional; public abstract class Pip2Module { @WMSingleton @Provides - static PipTransition providePipTransition(@NonNull ShellInit shellInit, + static PipTransition providePipTransition(Context context, + @NonNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, PipBoundsState pipBoundsState, PipBoundsAlgorithm pipBoundsAlgorithm, Optional<PipController> pipController, @NonNull PipScheduler pipScheduler) { - return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null, - pipBoundsAlgorithm, pipScheduler); + return new PipTransition(context, shellInit, shellTaskOrganizer, transitions, + pipBoundsState, null, pipBoundsAlgorithm, pipScheduler); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 04911c0bc064..0e7073688ec4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -47,6 +47,7 @@ import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; /** * Responsible supplying PiP Transitions. @@ -116,6 +117,17 @@ public abstract class PipTransitionController implements Transitions.TransitionH } /** + * Called when the Shell wants to start resizing Pip transition/animation. + * + * @param onFinishResizeCallback callback guaranteed to execute when animation ends and + * client completes any potential draws upon WM state updates. + */ + public void startResizeTransition(WindowContainerTransaction wct, + Consumer<Rect> onFinishResizeCallback) { + // Default implementation does nothing. + } + + /** * Called when the transition animation can't continue (eg. task is removed during * animation) */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 452a41696fcf..81705e20a1df 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -52,6 +52,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDoubleTapHelper; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.pip.SizeSpecSource; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 0b8f60e44c7e..57b73b3019f4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -24,10 +24,12 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.Rect; import android.view.SurfaceControl; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; @@ -36,6 +38,10 @@ import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipTransitionController; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.function.Consumer; + /** * Scheduler for Shell initiated PiP transitions and animations. */ @@ -58,13 +64,37 @@ public class PipScheduler { private SurfaceControl mPinnedTaskLeash; /** - * A temporary broadcast receiver to initiate exit PiP via expand. - * This will later be modified to be triggered by the PiP menu. + * Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell. + * This is used for a broadcast receiver to resolve intents. This should be removed once + * there is an equivalent of PipTouchHandler and PipResizeGestureHandler for PiP2. + */ + private static final int PIP_EXIT_VIA_EXPAND_CODE = 0; + private static final int PIP_DOUBLE_TAP = 1; + + @IntDef(value = { + PIP_EXIT_VIA_EXPAND_CODE, + PIP_DOUBLE_TAP + }) + @Retention(RetentionPolicy.SOURCE) + @interface PipUserJourneyCode {} + + /** + * A temporary broadcast receiver to initiate PiP CUJs. */ private class PipSchedulerReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - scheduleExitPipViaExpand(); + int userJourneyCode = intent.getIntExtra("cuj_code_extra", 0); + switch (userJourneyCode) { + case PIP_EXIT_VIA_EXPAND_CODE: + scheduleExitPipViaExpand(); + break; + case PIP_DOUBLE_TAP: + scheduleDoubleTapToResize(); + break; + default: + throw new IllegalStateException("unexpected CUJ code=" + userJourneyCode); + } } } @@ -121,6 +151,23 @@ public class PipScheduler { } } + /** + * Schedules resize PiP via double tap. + */ + public void scheduleDoubleTapToResize() {} + + /** + * Animates resizing of the pinned stack given the duration. + */ + public void scheduleAnimateResizePip(Rect toBounds, Consumer<Rect> onFinishResizeCallback) { + if (mPipTaskToken == null) { + return; + } + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setBounds(mPipTaskToken, toBounds); + mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback); + } + void onExitPip() { mPipTaskToken = null; mPinnedTaskLeash = null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 3b0e7c139bed..f3d178aef4ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -22,10 +22,12 @@ import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; +import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.PictureInPictureParams; +import android.content.Context; import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; @@ -36,6 +38,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; +import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; @@ -45,25 +48,29 @@ import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import java.util.function.Consumer; + /** * Implementation of transitions for PiP on phone. */ public class PipTransition extends PipTransitionController { private static final String TAG = PipTransition.class.getSimpleName(); + private final Context mContext; private final PipScheduler mPipScheduler; @Nullable private WindowContainerToken mPipTaskToken; @Nullable private IBinder mEnterTransition; @Nullable - private IBinder mAutoEnterButtonNavTransition; - @Nullable private IBinder mExitViaExpandTransition; @Nullable - private IBinder mLegacyEnterTransition; + private IBinder mResizeTransition; + + private Consumer<Rect> mFinishResizeCallback; public PipTransition( + Context context, @NonNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, @@ -74,6 +81,7 @@ public class PipTransition extends PipTransitionController { super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController, pipBoundsAlgorithm); + mContext = context; mPipScheduler = pipScheduler; mPipScheduler.setPipTransitionController(this); } @@ -87,7 +95,7 @@ public class PipTransition extends PipTransitionController { @Override public void startExitTransition(int type, WindowContainerTransaction out, - @android.annotation.Nullable Rect destinationBounds) { + @Nullable Rect destinationBounds) { if (out == null) { return; } @@ -97,6 +105,16 @@ public class PipTransition extends PipTransitionController { } } + @Override + public void startResizeTransition(WindowContainerTransaction wct, + Consumer<Rect> onFinishResizeCallback) { + if (wct == null) { + return; + } + mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this); + mFinishResizeCallback = onFinishResizeCallback; + } + @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @@ -126,43 +144,6 @@ public class PipTransition extends PipTransitionController { public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT) {} - private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition, - @NonNull TransitionRequestInfo request) { - // cache the original task token to check for multi-activity case later - final ActivityManager.RunningTaskInfo pipTask = request.getPipTask(); - PictureInPictureParams pipParams = pipTask.pictureInPictureParams; - mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo, - pipParams, mPipBoundsAlgorithm); - - // calculate the entry bounds and notify core to move task to pinned with final bounds - final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); - mPipBoundsState.setBounds(entryBounds); - - WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds); - return wct; - } - - private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) { - final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask(); - if (pipTask == null) { - return false; - } - if (pipTask.pictureInPictureParams == null) { - return false; - } - - // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type - // implies that we are entering PiP in button navigation mode. This is guaranteed by - // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav. - return requestInfo.getType() == TRANSIT_OPEN - && pipTask.pictureInPictureParams.isAutoEnterEnabled(); - } - - private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) { - return requestInfo.getType() == TRANSIT_PIP; - } - @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @@ -182,16 +163,48 @@ public class PipTransition extends PipTransitionController { } else if (transition == mExitViaExpandTransition) { mExitViaExpandTransition = null; return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback); + } else if (transition == mResizeTransition) { + mResizeTransition = null; + return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback); } return false; } - private boolean isLegacyEnter(@NonNull TransitionInfo info) { + private boolean startResizeAnimation(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { TransitionInfo.Change pipChange = getPipChange(info); - // If the only change in the changes list is a TO_FRONT mode PiP task, - // then this is legacy-enter PiP. - return pipChange != null && pipChange.getMode() == TRANSIT_TO_FRONT - && info.getChanges().size() == 1; + if (pipChange == null) { + return false; + } + SurfaceControl pipLeash = pipChange.getLeash(); + Rect destinationBounds = pipChange.getEndAbsBounds(); + + // Even though the final bounds and crop are applied with finishTransaction since + // this is a visible change, we still need to handle the app draw coming in. Snapshot + // covering app draw during collection will be removed by startTransaction. So we make + // the crop equal to the final bounds and then scale the leash back to starting bounds. + startTransaction.setWindowCrop(pipLeash, pipChange.getEndAbsBounds().width(), + pipChange.getEndAbsBounds().height()); + startTransaction.setScale(pipLeash, + (float) mPipBoundsState.getBounds().width() / destinationBounds.width(), + (float) mPipBoundsState.getBounds().height() / destinationBounds.height()); + startTransaction.apply(); + + finishTransaction.setScale(pipLeash, + (float) mPipBoundsState.getBounds().width() / destinationBounds.width(), + (float) mPipBoundsState.getBounds().height() / destinationBounds.height()); + + // We are done with the transition, but will continue animating leash to final bounds. + finishCallback.onTransitionFinished(null); + + // Animate the pip leash with the new buffer + final int duration = mContext.getResources().getInteger( + R.integer.config_pipResizeAnimationDuration); + // TODO: b/275910498 Couple this routine with a new implementation of the PiP animator. + startResizeAnimation(pipLeash, mPipBoundsState.getBounds(), destinationBounds, duration); + return true; } private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info, @@ -251,6 +264,57 @@ public class PipTransition extends PipTransitionController { return null; } + private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request) { + // cache the original task token to check for multi-activity case later + final ActivityManager.RunningTaskInfo pipTask = request.getPipTask(); + PictureInPictureParams pipParams = pipTask.pictureInPictureParams; + mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo, + pipParams, mPipBoundsAlgorithm); + + // calculate the entry bounds and notify core to move task to pinned with final bounds + final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); + mPipBoundsState.setBounds(entryBounds); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds); + return wct; + } + + private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) { + final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask(); + if (pipTask == null) { + return false; + } + if (pipTask.pictureInPictureParams == null) { + return false; + } + + // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type + // implies that we are entering PiP in button navigation mode. This is guaranteed by + // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav. + return requestInfo.getType() == TRANSIT_OPEN + && pipTask.pictureInPictureParams.isAutoEnterEnabled(); + } + + private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) { + return requestInfo.getType() == TRANSIT_PIP; + } + + private boolean isLegacyEnter(@NonNull TransitionInfo info) { + TransitionInfo.Change pipChange = getPipChange(info); + // If the only change in the changes list is a TO_FRONT mode PiP task, + // then this is legacy-enter PiP. + return pipChange != null && pipChange.getMode() == TRANSIT_TO_FRONT + && info.getChanges().size() == 1; + } + + /** + * TODO: b/275910498 Use a new implementation of the PiP animator here. + */ + private void startResizeAnimation(SurfaceControl leash, Rect startBounds, + Rect endBounds, int duration) {} + private void onExitPip() { mPipTaskToken = null; mPipScheduler.onExitPip(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl index 253acc49071a..0ca244c4b96a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl @@ -158,5 +158,10 @@ interface ISplitScreen { * does not expect split to currently be running. */ RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14; + + /** + * Reverse the split. + */ + oneway void switchSplitPosition() = 22; } -// Last id = 21
\ No newline at end of file +// Last id = 22
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 2ec52bb028c6..70cb2fc6d52c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -1109,6 +1109,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator.onDroppedToSplit(position, dragSessionId); } + void switchSplitPosition(String reason) { + if (isSplitScreenVisible()) { + mStageCoordinator.switchSplitPosition(reason); + } + } + /** * Return the {@param exitReason} as a string. */ @@ -1473,5 +1479,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, true /* blocking */); return out[0]; } + + @Override + public void switchSplitPosition() { + executeRemoteCallWithTaskPermission(mController, "switchSplitPosition", + (controller) -> controller.switchSplitPosition("remoteCall")); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java index 7fd03a9a306b..7f16c5e3592e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java @@ -43,6 +43,8 @@ public class SplitScreenShellCommandHandler implements return runRemoveFromSideStage(args, pw); case "setSideStagePosition": return runSetSideStagePosition(args, pw); + case "switchSplitPosition": + return runSwitchSplitPosition(); default: pw.println("Invalid command: " + args[0]); return false; @@ -84,6 +86,11 @@ public class SplitScreenShellCommandHandler implements return true; } + private boolean runSwitchSplitPosition() { + mController.switchSplitPosition("shellCommand"); + return true; + } + @Override public void printShellCommandHelp(PrintWriter pw, String prefix) { pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>"); @@ -92,5 +99,7 @@ public class SplitScreenShellCommandHandler implements pw.println(prefix + " Remove a task with given id in split-screen mode."); pw.println(prefix + "setSideStagePosition <SideStagePosition>"); pw.println(prefix + " Sets the position of the side-stage."); + pw.println(prefix + "switchSplitPosition"); + pw.println(prefix + " Reverses the split."); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 3fb0dbfaa63d..67fc7e2b4ea6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -175,6 +175,9 @@ public class Transitions implements RemoteCallable<Transitions>, /** Transition to animate task to desktop. */ public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15; + /** Transition to resize PiP task. */ + public static final int TRANSIT_RESIZE_PIP = TRANSIT_FIRST_CUSTOM + 16; + private final ShellTaskOrganizer mOrganizer; private final Context mContext; private final ShellExecutor mMainExecutor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 554b1fb99550..4ba05ce8aef1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -32,6 +32,7 @@ import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFO import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; @@ -60,7 +61,6 @@ import android.view.ViewConfiguration; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; @@ -544,12 +544,22 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return true; } + /** + * Perform a task size toggle on release of the double-tap, assuming no drag event + * was handled during the double-tap. + * @param e The motion event that occurred during the double-tap gesture. + * @return true if the event should be consumed, false if not + */ @Override - public boolean onDoubleTap(@NonNull MotionEvent e) { + public boolean onDoubleTapEvent(@NonNull MotionEvent e) { + final int action = e.getActionMasked(); + if (mIsDragging || (action != MotionEvent.ACTION_UP + && action != MotionEvent.ACTION_CANCEL)) { + return false; + } final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); - mDesktopTasksController.ifPresent(c -> { - c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId)); - }); + mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo, + mWindowDecorByTaskId.get(taskInfo.taskId))); return true; } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt index 47bff8de377e..0d1853534927 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt @@ -78,6 +78,14 @@ abstract class TvPipTestBase : PipTestBase(rotationToString(ROTATION_0), ROTATIO uiAutomation.dropShellPermissionIdentity() } + override fun onProcessStarted( + pid: Int, + processUid: Int, + packageUid: Int, + packageName: String, + processName: String + ) {} + override fun onForegroundActivitiesChanged(pid: Int, uid: Int, foreground: Boolean) {} override fun onForegroundServicesChanged(pid: Int, uid: Int, serviceTypes: Int) {} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java index 0f8db85dcef4..b583acda1c9a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java @@ -16,10 +16,10 @@ package com.android.wm.shell.pip.phone; -import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_CUSTOM; -import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_DEFAULT; -import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_MAX; -import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.nextSizeSpec; +import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_CUSTOM; +import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_DEFAULT; +import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_MAX; +import static com.android.wm.shell.common.pip.PipDoubleTapHelper.nextSizeSpec; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -30,6 +30,7 @@ import android.testing.AndroidTestingRunner; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDoubleTapHelper; import org.junit.Assert; import org.junit.Before; @@ -38,7 +39,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; /** - * Unit test against {@link PipDoubleTapHelper}. + * Unit test against {@link com.android.wm.shell.common.pip.PipDoubleTapHelper}. */ @RunWith(AndroidTestingRunner.class) public class PipDoubleTapHelperTest extends ShellTestCase { 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 12a5594ae1da..7f3bfbb0e81d 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 @@ -421,6 +421,15 @@ public class SplitScreenControllerTests extends ShellTestCase { assertEquals(false, controller.supportsMultiInstanceSplit(component)); } + @Test + public void testSwitchSplitPosition_checksIsSplitScreenVisible() { + final String reason = "test"; + when(mSplitScreenController.isSplitScreenVisible()).thenReturn(true, false); + mSplitScreenController.switchSplitPosition(reason); + mSplitScreenController.switchSplitPosition(reason); + verify(mStageCoordinator, times(1)).switchSplitPosition(reason); + } + private Intent createStartIntent(String activityName) { Intent intent = new Intent(); intent.setComponent(new ComponentName(mContext, activityName)); diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt index e099f1124bf1..0a424bc4230a 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt @@ -37,10 +37,11 @@ private const val TAG = "BroadcastReceiverFlow" fun Context.broadcastReceiverFlow(intentFilter: IntentFilter): Flow<Intent> = callbackFlow { val broadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { + Log.d(TAG, "onReceive: $intent") trySend(intent) } } - registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED) + registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) awaitClose { unregisterReceiver(broadcastReceiver) } }.catch { e -> diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt index eef5225aef42..772f925c0a77 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt @@ -43,7 +43,7 @@ class BroadcastReceiverFlowTest { private val context = mock<Context> { on { - registerReceiver(any(), eq(INTENT_FILTER), eq(Context.RECEIVER_NOT_EXPORTED)) + registerReceiver(any(), eq(INTENT_FILTER), eq(Context.RECEIVER_VISIBLE_TO_INSTANT_APPS)) } doAnswer { registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver null diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index d38454221f76..cdb4aea6ee79 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -561,7 +561,7 @@ <uses-permission android:name="android.permission.TEST_BIOMETRIC" /> <!-- Permission required for CTS test - android.server.biometrics --> - <uses-permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" /> + <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" /> <!-- Permission required for CTS test - android.server.biometrics --> <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" /> @@ -902,6 +902,9 @@ <!-- Permission required for BinaryTransparencyService shell API and host test --> <uses-permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" /> + <!-- Permissions required for CTS test - CtsPermissionUiTestCases --> + <uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index 7ba889bc8fee..866aa8945525 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -17,6 +17,13 @@ flag { } flag { + name: "floating_menu_drag_to_edit" + namespace: "accessibility" + description: "adds a second drag button to allow the user edit the shortcut." + bug: "297583708" +} + +flag { name: "floating_menu_ime_displacement_animation" namespace: "accessibility" description: "Adds an animation for when the FAB is displaced by an IME becoming visible." diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 2c35c777ab12..a2530d59e2e6 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -364,3 +364,10 @@ flag { description: "Enables styled focus states on pin input field if keyboard is connected" bug: "316106516" } + +flag { + name: "keyguard_wm_state_refactor" + namespace: "systemui" + description: "Enables refactored logic for SysUI+WM unlock/occlusion code paths" + bug: "278086361" +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt index 2a6bea75791c..be6f022d8d52 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt @@ -33,6 +33,7 @@ import com.android.keyguard.LockIconView import com.android.keyguard.LockIconViewController import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.biometrics.AuthController +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags @@ -47,10 +48,12 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import dagger.Lazy import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope class LockSection @Inject constructor( + @Application private val applicationScope: CoroutineScope, private val windowManager: WindowManager, private val authController: AuthController, private val featureFlags: FeatureFlagsClassic, @@ -76,6 +79,7 @@ constructor( DeviceEntryIconView(context, null).apply { id = R.id.device_entry_icon_view DeviceEntryIconViewBinder.bind( + applicationScope, this, deviceEntryIconViewModel.get(), deviceEntryForegroundViewModel.get(), diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 030d41ddd8fb..c82688c2772a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -203,11 +203,17 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true) featureFlags = FakeFeatureFlags() - featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES) + mSetFlagsRule.enableFlags( + AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES, + ) + mSetFlagsRule.disableFlags( + FLAG_SIDEFPS_CONTROLLER_REFACTOR, + AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR + ) + keyguardPasswordViewController = KeyguardPasswordViewController( keyguardPasswordView, @@ -238,7 +244,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { sceneInteractor.setTransitionState(sceneTransitionStateFlow) deviceEntryInteractor = kosmos.deviceEntryInteractor - mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) underTest = KeyguardSecurityContainerController( view, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 6a14220e6a42..6808f5d643a9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -38,6 +38,7 @@ import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId.fakeInstanceId import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository @@ -62,7 +63,6 @@ import com.android.systemui.display.data.repository.FakeDisplayRepository import com.android.systemui.display.data.repository.display import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.keyguard.data.repository.BiometricType import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeCommandQueue @@ -194,7 +194,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { biometricSettingsRepository = FakeBiometricSettingsRepository() deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() trustRepository = FakeTrustRepository() - featureFlags = FakeFeatureFlags().apply { set(KEYGUARD_WM_STATE_REFACTOR, false) } + featureFlags = FakeFeatureFlags() powerRepository = FakePowerRepository() powerInteractor = @@ -252,6 +252,10 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true))) whenever(bypassController.bypassEnabled).thenReturn(true) underTest = createDeviceEntryFaceAuthRepositoryImpl(faceManager, bypassController) + + mSetFlagsRule.disableFlags( + AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR, + ) } private fun createDeviceEntryFaceAuthRepositoryImpl( @@ -301,7 +305,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { faceAuthBuffer, keyguardTransitionInteractor, displayStateInteractor, - featureFlags, dumpManager, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt new file mode 100644 index 000000000000..88ad3f37dacd --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.deviceentry.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class AuthRippleInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val deviceEntrySourceInteractor = kosmos.deviceEntrySourceInteractor + private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository + private val keyguardRepository = kosmos.fakeKeyguardRepository + private val underTest = kosmos.authRippleInteractor + + @Test + fun enteringDeviceFromDeviceEntryIcon_udfpsNotSupported_doesNotShowAuthRipple() = + testScope.runTest { + val showUnlockRipple by collectLastValue(underTest.showUnlockRipple) + fingerprintPropertyRepository.supportsRearFps() + keyguardRepository.setKeyguardDismissible(true) + runCurrent() + deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon() + assertThat(showUnlockRipple).isNull() + } + + @Test + fun enteringDeviceFromDeviceEntryIcon_udfpsSupported_showsAuthRipple() = + testScope.runTest { + val showUnlockRipple by collectLastValue(underTest.showUnlockRipple) + fingerprintPropertyRepository.supportsUdfps() + keyguardRepository.setKeyguardDismissible(true) + runCurrent() + deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon() + assertThat(showUnlockRipple).isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR) + } + + @Test + fun faceUnlocked_showsAuthRipple() = + testScope.runTest { + val showUnlockRipple by collectLastValue(underTest.showUnlockRipple) + keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR) + keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + assertThat(showUnlockRipple).isEqualTo(BiometricUnlockSource.FACE_SENSOR) + } + + @Test + fun fingerprintUnlocked_showsAuthRipple() = + testScope.runTest { + val showUnlockRippleFromBiometricUnlock by collectLastValue(underTest.showUnlockRipple) + keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR) + keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + assertThat(showUnlockRippleFromBiometricUnlock) + .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt new file mode 100644 index 000000000000..d216fa0d0eff --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt @@ -0,0 +1,77 @@ +/* + * 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.deviceentry.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceEntrySourceInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val keyguardRepository = kosmos.fakeKeyguardRepository + private val underTest = kosmos.deviceEntrySourceInteractor + + @Test + fun deviceEntryFromFaceUnlock() = + testScope.runTest { + val deviceEntryFromBiometricAuthentication by + collectLastValue(underTest.deviceEntryFromBiometricSource) + keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR) + keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + runCurrent() + assertThat(deviceEntryFromBiometricAuthentication) + .isEqualTo(BiometricUnlockSource.FACE_SENSOR) + } + + @Test + fun deviceEntryFromFingerprintUnlock() = runTest { + val deviceEntryFromBiometricAuthentication by + collectLastValue(underTest.deviceEntryFromBiometricSource) + keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR) + keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + runCurrent() + assertThat(deviceEntryFromBiometricAuthentication) + .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR) + } + + @Test + fun noDeviceEntry() = runTest { + val deviceEntryFromBiometricAuthentication by + collectLastValue(underTest.deviceEntryFromBiometricSource) + keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR) + // doesn't dismiss keyguard: + keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.ONLY_WAKE) + runCurrent() + assertThat(deviceEntryFromBiometricAuthentication).isNull() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index dc8b97abbfe8..78ae8b119c69 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -238,10 +238,10 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test - fun isKeyguardUnlocked() = + fun isKeyguardDismissible() = testScope.runTest { whenever(keyguardStateController.isUnlocked).thenReturn(false) - val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked) + val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardDismissible) runCurrent() assertThat(isKeyguardUnlocked).isFalse() diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 3839dd98cdd3..307a6192a570 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -29,6 +29,9 @@ <color name="status_bar_icons_hover_color_light">#38FFFFFF</color> <!-- 22% white --> <color name="status_bar_icons_hover_color_dark">#38000000</color> <!-- 22% black --> + <!-- The dark background color behind the shade --> + <color name="shade_scrim_background_dark">@*android:color/black</color> + <!-- The color of the background in the separated list of the Global Actions menu --> <color name="global_actions_separated_background">#F5F5F5</color> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 8ec5ccd7a080..2ab0813300e3 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -184,6 +184,7 @@ <item type="id" name="action_move_to_edge_and_hide"/> <item type="id" name="action_move_out_edge_and_show"/> <item type="id" name="action_remove_menu"/> + <item type="id" name="action_edit"/> <!-- rounded corner view id --> <item type="id" name="rounded_corner_top_left"/> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2b43360f0689..47ac96ca7960 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2560,6 +2560,8 @@ <string name="accessibility_floating_button_action_remove_menu">Remove</string> <!-- Action in accessibility menu to toggle on/off the accessibility feature. [CHAR LIMIT=30]--> <string name="accessibility_floating_button_action_double_tap_to_toggle">toggle</string> + <!-- Action in accessibility menu to open the shortcut edit menu" [CHAR LIMIT=30]--> + <string name="accessibility_floating_button_action_edit">Edit</string> <!-- Device Controls strings --> <!-- Device Controls, Quick Settings tile title [CHAR LIMIT=30] --> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index ecce22315c50..25d771308aea 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -19,9 +19,9 @@ package com.android.keyguard; import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS; -import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_PASSWORD; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM; @@ -84,6 +84,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInt import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.KeyguardWmStateRefactor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.ActivityStarter; @@ -100,8 +101,6 @@ import com.android.systemui.util.ViewController; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.GlobalSettings; -import dagger.Lazy; - import java.io.File; import java.util.Arrays; import java.util.Optional; @@ -109,6 +108,7 @@ import java.util.Optional; import javax.inject.Inject; import javax.inject.Provider; +import dagger.Lazy; import kotlinx.coroutines.Job; /** Controller for {@link KeyguardSecurityContainer} */ @@ -330,7 +330,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } } - if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (KeyguardWmStateRefactor.isEnabled()) { mKeyguardTransitionInteractor.startDismissKeyguardTransition(); } } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index d5dc85cd8715..8e9815085e31 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -63,6 +63,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; +import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; @@ -87,6 +88,8 @@ import java.util.function.Consumer; import javax.inject.Inject; +import kotlinx.coroutines.ExperimentalCoroutinesApi; + /** * Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen. * @@ -717,6 +720,7 @@ public class LockIconViewController implements Dumpable { return mDownDetected; } + @ExperimentalCoroutinesApi @VisibleForTesting protected void onLongPress() { cancelTouches(); @@ -727,7 +731,8 @@ public class LockIconViewController implements Dumpable { // pre-emptively set to true to hide view mIsBouncerShowing = true; - if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) { + if (!DeviceEntryUdfpsRefactor.isEnabled() + && mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) { mAuthRippleController.showUnlockRipple(FINGERPRINT); } updateVisibility(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java index 568b24dbd4f3..7fd72ec8ce93 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java @@ -16,127 +16,138 @@ package com.android.systemui.accessibility.floatingmenu; +import static android.R.id.empty; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.util.ArrayMap; +import android.util.Pair; import android.view.MotionEvent; import androidx.annotation.NonNull; import androidx.dynamicanimation.animation.DynamicAnimation; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Flags; +import com.android.wm.shell.common.bubbles.DismissCircleView; import com.android.wm.shell.common.bubbles.DismissView; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; +import java.util.Map; +import java.util.Objects; + /** * Controls the interaction between {@link MagnetizedObject} and * {@link MagnetizedObject.MagneticTarget}. */ class DragToInteractAnimationController { - private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false; private static final float COMPLETELY_OPAQUE = 1.0f; private static final float COMPLETELY_TRANSPARENT = 0.0f; private static final float CIRCLE_VIEW_DEFAULT_SCALE = 1.0f; private static final float ANIMATING_MAX_ALPHA = 0.7f; + private final DragToInteractView mInteractView; private final DismissView mDismissView; private final MenuView mMenuView; - private final ValueAnimator mDismissAnimator; - private final MagnetizedObject<?> mMagnetizedObject; - private float mMinDismissSize; + + /** + * MagnetizedObject cannot differentiate between its MagnetizedTargets, + * so we need an object & an animator for every interactable. + */ + private final ArrayMap<Integer, Pair<MagnetizedObject<MenuView>, ValueAnimator>> mInteractMap; + + private float mMinInteractSize; private float mSizePercent; - DragToInteractAnimationController(DismissView dismissView, MenuView menuView) { - mDismissView = dismissView; - mDismissView.setPivotX(dismissView.getWidth() / 2.0f); - mDismissView.setPivotY(dismissView.getHeight() / 2.0f); + DragToInteractAnimationController(DragToInteractView interactView, MenuView menuView) { + mDismissView = null; + mInteractView = interactView; + mInteractView.setPivotX(interactView.getWidth() / 2.0f); + mInteractView.setPivotY(interactView.getHeight() / 2.0f); mMenuView = menuView; updateResources(); - mDismissAnimator = ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT); - mDismissAnimator.addUpdateListener(dismissAnimation -> { - final float animatedValue = (float) dismissAnimation.getAnimatedValue(); - final float scaleValue = Math.max(animatedValue, mSizePercent); - dismissView.getCircle().setScaleX(scaleValue); - dismissView.getCircle().setScaleY(scaleValue); - - menuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA)); + mInteractMap = new ArrayMap<>(); + interactView.getInteractMap().forEach((viewId, pair) -> { + DismissCircleView circleView = pair.getFirst(); + createMagnetizedObjectAndAnimator(circleView); }); + } - mDismissAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) { - super.onAnimationEnd(animation, isReverse); + DragToInteractAnimationController(DismissView dismissView, MenuView menuView) { + mDismissView = dismissView; + mInteractView = null; + mDismissView.setPivotX(dismissView.getWidth() / 2.0f); + mDismissView.setPivotY(dismissView.getHeight() / 2.0f); + mMenuView = menuView; - if (isReverse) { - mDismissView.getCircle().setScaleX(CIRCLE_VIEW_DEFAULT_SCALE); - mDismissView.getCircle().setScaleY(CIRCLE_VIEW_DEFAULT_SCALE); - mMenuView.setAlpha(COMPLETELY_OPAQUE); - } - } - }); + updateResources(); - mMagnetizedObject = - new MagnetizedObject<MenuView>(mMenuView.getContext(), mMenuView, - new MenuAnimationController.MenuPositionProperty( - DynamicAnimation.TRANSLATION_X), - new MenuAnimationController.MenuPositionProperty( - DynamicAnimation.TRANSLATION_Y)) { - @Override - public void getLocationOnScreen(MenuView underlyingObject, int[] loc) { - underlyingObject.getLocationOnScreen(loc); - } - - @Override - public float getHeight(MenuView underlyingObject) { - return underlyingObject.getHeight(); - } - - @Override - public float getWidth(MenuView underlyingObject) { - return underlyingObject.getWidth(); - } - }; - - final MagnetizedObject.MagneticTarget magneticTarget = new MagnetizedObject.MagneticTarget( - dismissView.getCircle(), (int) mMinDismissSize); - mMagnetizedObject.addTarget(magneticTarget); - mMagnetizedObject.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_MENU); + mInteractMap = new ArrayMap<>(); + createMagnetizedObjectAndAnimator(dismissView.getCircle()); } - void showDismissView(boolean show) { - if (show) { - mDismissView.show(); - } else { - mDismissView.hide(); + void showInteractView(boolean show) { + if (Flags.floatingMenuDragToEdit() && mInteractView != null) { + if (show) { + mInteractView.show(); + } else { + mInteractView.hide(); + } + } else if (mDismissView != null) { + if (show) { + mDismissView.show(); + } else { + mDismissView.hide(); + } } } void setMagnetListener(MagnetizedObject.MagnetListener magnetListener) { - mMagnetizedObject.setMagnetListener(magnetListener); + mInteractMap.forEach((viewId, pair) -> { + MagnetizedObject<?> magnetizedObject = pair.first; + magnetizedObject.setMagnetListener(magnetListener); + }); } @VisibleForTesting - MagnetizedObject.MagnetListener getMagnetListener() { - return mMagnetizedObject.getMagnetListener(); + MagnetizedObject.MagnetListener getMagnetListener(int id) { + return Objects.requireNonNull(mInteractMap.get(id)).first.getMagnetListener(); } void maybeConsumeDownMotionEvent(MotionEvent event) { - mMagnetizedObject.maybeConsumeMotionEvent(event); + mInteractMap.forEach((viewId, pair) -> { + MagnetizedObject<?> magnetizedObject = pair.first; + magnetizedObject.maybeConsumeMotionEvent(event); + }); + } + + private int maybeConsumeMotionEvent(MotionEvent event) { + for (Map.Entry<Integer, Pair<MagnetizedObject<MenuView>, ValueAnimator>> set: + mInteractMap.entrySet()) { + MagnetizedObject<MenuView> magnetizedObject = set.getValue().first; + if (magnetizedObject.maybeConsumeMotionEvent(event)) { + return set.getKey(); + } + } + return empty; } /** - * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized object to check if it was - * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}. + * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized objects + * to check if it was within a magnetic field. + * It should be used in the {@link MenuListViewTouchHandler}. * * @param event that move the magnetized object which is also the menu list view. - * @return true if the location of the motion events moves within the magnetic field of a - * target, but false if didn't set + * @return id of a target if the location of the motion events moves + * within the field of the target, otherwise it returns{@link android.R.id#empty}. + * <p> * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}. */ - boolean maybeConsumeMoveMotionEvent(MotionEvent event) { - return mMagnetizedObject.maybeConsumeMotionEvent(event); + int maybeConsumeMoveMotionEvent(MotionEvent event) { + return maybeConsumeMotionEvent(event); } /** @@ -144,31 +155,93 @@ class DragToInteractAnimationController { * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}. * * @param event that move the magnetized object which is also the menu list view. - * @return true if the location of the motion events moves within the magnetic field of a - * target, but false if didn't set + * @return id of a target if the location of the motion events moves + * within the field of the target, otherwise it returns{@link android.R.id#empty}. * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}. */ - boolean maybeConsumeUpMotionEvent(MotionEvent event) { - return mMagnetizedObject.maybeConsumeMotionEvent(event); + int maybeConsumeUpMotionEvent(MotionEvent event) { + return maybeConsumeMotionEvent(event); } - void animateDismissMenu(boolean scaleUp) { + void animateInteractMenu(int targetViewId, boolean scaleUp) { + Pair<MagnetizedObject<MenuView>, ValueAnimator> value = mInteractMap.get(targetViewId); + if (value == null) { + return; + } + ValueAnimator animator = value.second; if (scaleUp) { - mDismissAnimator.start(); + animator.start(); } else { - mDismissAnimator.reverse(); + animator.reverse(); } } void updateResources() { - final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize( + final float maxInteractSize = mMenuView.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.dismiss_circle_size); - mMinDismissSize = mDismissView.getResources().getDimensionPixelSize( + mMinInteractSize = mMenuView.getResources().getDimensionPixelSize( com.android.wm.shell.R.dimen.dismiss_circle_small); - mSizePercent = mMinDismissSize / maxDismissSize; + mSizePercent = mMinInteractSize / maxInteractSize; } - interface DismissCallback { - void onDismiss(); + /** + * Creates a magnetizedObject & valueAnimator pair for the provided circleView, + * and adds them to the interactMap. + * + * @param circleView circleView to create objects for. + */ + private void createMagnetizedObjectAndAnimator(DismissCircleView circleView) { + MagnetizedObject<MenuView> magnetizedObject = new MagnetizedObject<MenuView>( + mMenuView.getContext(), mMenuView, + new MenuAnimationController.MenuPositionProperty( + DynamicAnimation.TRANSLATION_X), + new MenuAnimationController.MenuPositionProperty( + DynamicAnimation.TRANSLATION_Y)) { + @Override + public void getLocationOnScreen(MenuView underlyingObject, @NonNull int[] loc) { + underlyingObject.getLocationOnScreen(loc); + } + + @Override + public float getHeight(MenuView underlyingObject) { + return underlyingObject.getHeight(); + } + + @Override + public float getWidth(MenuView underlyingObject) { + return underlyingObject.getWidth(); + } + }; + // Avoid unintended selection of an object / option + magnetizedObject.setFlingToTargetEnabled(false); + magnetizedObject.addTarget(new MagnetizedObject.MagneticTarget( + circleView, (int) mMinInteractSize)); + + final ValueAnimator animator = + ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT); + + animator.addUpdateListener(dismissAnimation -> { + final float animatedValue = (float) dismissAnimation.getAnimatedValue(); + final float scaleValue = Math.max(animatedValue, mSizePercent); + circleView.setScaleX(scaleValue); + circleView.setScaleY(scaleValue); + + mMenuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA)); + }); + + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) { + super.onAnimationEnd(animation, isReverse); + + if (isReverse) { + circleView.setScaleX(CIRCLE_VIEW_DEFAULT_SCALE); + circleView.setScaleY(CIRCLE_VIEW_DEFAULT_SCALE); + mMenuView.setAlpha(COMPLETELY_OPAQUE); + } + } + }); + + mInteractMap.put(circleView.getId(), new Pair<>(magnetizedObject, animator)); } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt new file mode 100644 index 000000000000..0ef3d200d1fa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.floatingmenu + +import android.animation.ObjectAnimator +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.util.ArrayMap +import android.util.IntProperty +import android.util.Log +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.WindowInsets +import android.view.WindowManager +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.Space +import androidx.annotation.ColorRes +import androidx.annotation.DimenRes +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY +import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW +import com.android.wm.shell.R +import com.android.wm.shell.animation.PhysicsAnimator +import com.android.wm.shell.common.bubbles.DismissCircleView +import com.android.wm.shell.common.bubbles.DismissView + +/** + * View that handles interactions between DismissCircleView and BubbleStackView. + * + * @note [setup] method should be called after initialisation + */ +class DragToInteractView(context: Context) : FrameLayout(context) { + /** + * The configuration is used to provide module specific resource ids + * + * @see [setup] method + */ + data class Config( + /** dimen resource id of the dismiss target circle view size */ + @DimenRes val targetSizeResId: Int, + /** dimen resource id of the icon size in the dismiss target */ + @DimenRes val iconSizeResId: Int, + /** dimen resource id of the bottom margin for the dismiss target */ + @DimenRes var bottomMarginResId: Int, + /** dimen resource id of the height for dismiss area gradient */ + @DimenRes val floatingGradientHeightResId: Int, + /** color resource id of the dismiss area gradient color */ + @ColorRes val floatingGradientColorResId: Int, + /** drawable resource id of the dismiss target background */ + @DrawableRes val backgroundResId: Int, + /** drawable resource id of the icon for the dismiss target */ + @DrawableRes val iconResId: Int + ) + + companion object { + private const val SHOULD_SETUP = "The view isn't ready. Should be called after `setup`" + private val TAG = DragToInteractView::class.simpleName + } + + // START DragToInteractView modification + // We could technically access each DismissCircleView from their Animator, + // but the animators only store a weak reference to their targets. This is safer. + var interactMap = ArrayMap<Int, Pair<DismissCircleView, PhysicsAnimator<DismissCircleView>>>() + // END DragToInteractView modification + var isShowing = false + var config: Config? = null + + private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY) + private val INTERACT_SCRIM_FADE_MS = 200L + private var wm: WindowManager = + context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + private var gradientDrawable: GradientDrawable? = null + + private val GRADIENT_ALPHA: IntProperty<GradientDrawable> = + object : IntProperty<GradientDrawable>("alpha") { + override fun setValue(d: GradientDrawable, percent: Int) { + d.alpha = percent + } + override fun get(d: GradientDrawable): Int { + return d.alpha + } + } + + init { + clipToPadding = false + clipChildren = false + visibility = View.INVISIBLE + + // START DragToInteractView modification + // Resources included within implementation as we aren't concerned with decoupling them. + setup( + Config( + targetSizeResId = R.dimen.dismiss_circle_size, + iconSizeResId = R.dimen.dismiss_target_x_size, + bottomMarginResId = R.dimen.floating_dismiss_bottom_margin, + floatingGradientHeightResId = R.dimen.floating_dismiss_gradient_height, + floatingGradientColorResId = android.R.color.system_neutral1_900, + backgroundResId = R.drawable.dismiss_circle_background, + iconResId = R.drawable.pip_ic_close_white + ) + ) + // END DragToInteractView modification + } + + /** + * Sets up view with the provided resource ids. + * + * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called + * with default params in module specific extension: + * + * @see [DismissView.setup] in DismissViewExt.kt + */ + fun setup(config: Config) { + this.config = config + + // Setup layout + layoutParams = + LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + resources.getDimensionPixelSize(config.floatingGradientHeightResId), + Gravity.BOTTOM + ) + updatePadding() + + // Setup gradient + gradientDrawable = createGradient(color = config.floatingGradientColorResId) + background = gradientDrawable + + // START DragToInteractView modification + + // Setup LinearLayout. Added to organize multiple circles. + val linearLayout = LinearLayout(context) + linearLayout.layoutParams = + LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + linearLayout.weightSum = 0f + addView(linearLayout) + + // Setup DismissCircleView. Code block replaced with repeatable functions + addSpace(linearLayout) + addCircle( + config, + com.android.systemui.res.R.id.action_remove_menu, + R.drawable.pip_ic_close_white, + linearLayout + ) + addCircle( + config, + com.android.systemui.res.R.id.action_edit, + com.android.systemui.res.R.drawable.ic_screenshot_edit, + linearLayout + ) + // END DragToInteractView modification + } + + /** Animates this view in. */ + fun show() { + if (isShowing) return + val gradientDrawable = checkExists(gradientDrawable) ?: return + isShowing = true + visibility = View.VISIBLE + val alphaAnim = + ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 255) + alphaAnim.duration = INTERACT_SCRIM_FADE_MS + alphaAnim.start() + + // START DragToInteractView modification + interactMap.forEach { + val animator = it.value.second + animator.cancel() + animator.spring(DynamicAnimation.TRANSLATION_Y, 0f, spring).start() + } + // END DragToInteractView modification + } + + /** + * Animates this view out, as well as the circle that encircles the bubbles, if they were + * dragged into the target and encircled. + */ + fun hide() { + if (!isShowing) return + val gradientDrawable = checkExists(gradientDrawable) ?: return + isShowing = false + val alphaAnim = + ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 0) + alphaAnim.duration = INTERACT_SCRIM_FADE_MS + alphaAnim.start() + + // START DragToInteractView modification + interactMap.forEach { + val animator = it.value.second + animator + .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(), spring) + .withEndActions({ visibility = View.INVISIBLE }) + .start() + } + // END DragToInteractView modification + } + + /** Cancels the animator for the dismiss target. */ + fun cancelAnimators() { + // START DragToInteractView modification + interactMap.forEach { + val animator = it.value.second + animator.cancel() + } + // END DragToInteractView modification + } + + fun updateResources() { + val config = checkExists(config) ?: return + updatePadding() + layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId) + val targetSize = resources.getDimensionPixelSize(config.targetSizeResId) + + // START DragToInteractView modification + interactMap.forEach { + val circle = it.value.first + circle.layoutParams.width = targetSize + circle.layoutParams.height = targetSize + circle.requestLayout() + } + // END DragToInteractView modification + } + + private fun createGradient(@ColorRes color: Int): GradientDrawable { + val gradientColor = ContextCompat.getColor(context, color) + val alpha = 0.7f * 255 + val gradientColorWithAlpha = + Color.argb( + alpha.toInt(), + Color.red(gradientColor), + Color.green(gradientColor), + Color.blue(gradientColor) + ) + val gd = + GradientDrawable( + GradientDrawable.Orientation.BOTTOM_TOP, + intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT) + ) + gd.setDither(true) + gd.alpha = 0 + return gd + } + + private fun updatePadding() { + val config = checkExists(config) ?: return + val insets: WindowInsets = wm.currentWindowMetrics.windowInsets + val navInset = insets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()) + setPadding( + 0, + 0, + 0, + navInset.bottom + resources.getDimensionPixelSize(config.bottomMarginResId) + ) + } + + /** + * Checks if the value is set up and exists, if not logs an exception. Used for convenient + * logging in case `setup` wasn't called before + * + * @return value provided as argument + */ + private fun <T> checkExists(value: T?): T? { + if (value == null) Log.e(TAG, SHOULD_SETUP) + return value + } + + // START DragToInteractView modification + private fun addSpace(parent: LinearLayout) { + val space = Space(context) + // Spaces are weighted equally to space out circles evenly + space.layoutParams = + LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ) + parent.addView(space) + parent.weightSum = parent.weightSum + 1f + } + + private fun addCircle(config: Config, id: Int, iconResId: Int, parent: LinearLayout) { + val targetSize = resources.getDimensionPixelSize(config.targetSizeResId) + val circleLayoutParams = LinearLayout.LayoutParams(targetSize, targetSize, 0f) + circleLayoutParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + val circle = DismissCircleView(context) + circle.id = id + circle.setup(config.backgroundResId, iconResId, config.iconSizeResId) + circle.layoutParams = circleLayoutParams + + // Initial position with circle offscreen so it's animated up + circle.translationY = + resources.getDimensionPixelSize(config.floatingGradientHeightResId).toFloat() + + interactMap[circle.id] = Pair(circle, PhysicsAnimator.getInstance(circle)) + parent.addView(circle) + addSpace(parent) + } + // END DragToInteractView modification +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java index a2705584d76a..d3e85e092b3a 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java @@ -37,7 +37,6 @@ import androidx.dynamicanimation.animation.SpringForce; import androidx.recyclerview.widget.RecyclerView; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; import com.android.systemui.Flags; import java.util.HashMap; @@ -73,7 +72,6 @@ class MenuAnimationController { private final ValueAnimator mFadeOutAnimator; private final Handler mHandler; private boolean mIsFadeEffectEnabled; - private DragToInteractAnimationController.DismissCallback mDismissCallback; private Runnable mSpringAnimationsEndAction; // Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link @@ -170,11 +168,6 @@ class MenuAnimationController { mSpringAnimationsEndAction = runnable; } - void setDismissCallback( - DragToInteractAnimationController.DismissCallback dismissCallback) { - mDismissCallback = dismissCallback; - } - void moveToTopLeftPosition() { mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false); final Rect draggableBounds = mMenuView.getMenuDraggableBounds(); @@ -205,13 +198,6 @@ class MenuAnimationController { constrainPositionAndUpdate(position, /* writeToPosition = */ true); } - void removeMenu() { - Preconditions.checkArgument(mDismissCallback != null, - "The dismiss callback should be initialized first."); - - mDismissCallback.onDismiss(); - } - void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) { final boolean shouldMenuFlingLeft = isOnLeftSide() ? velocityX < ESCAPE_VELOCITY @@ -334,8 +320,6 @@ class MenuAnimationController { moveToEdgeAndHide(); return true; } - - fadeOutIfEnabled(); return false; } @@ -453,8 +437,6 @@ class MenuAnimationController { mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y); constrainPositionAndUpdate(position, writeToPosition); - fadeOutIfEnabled(); - if (mSpringAnimationsEndAction != null) { mSpringAnimationsEndAction.run(); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java index 9c22a7738ad6..975a6020430d 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java @@ -27,6 +27,7 @@ import androidx.annotation.NonNull; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate; +import com.android.systemui.Flags; import com.android.systemui.res.R; /** @@ -35,15 +36,18 @@ import com.android.systemui.res.R; */ class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.ItemDelegate { private final MenuAnimationController mAnimationController; + private final MenuViewLayer mMenuViewLayer; MenuItemAccessibilityDelegate(@NonNull RecyclerViewAccessibilityDelegate recyclerViewDelegate, - MenuAnimationController animationController) { + MenuAnimationController animationController, MenuViewLayer menuViewLayer) { super(recyclerViewDelegate); mAnimationController = animationController; + mMenuViewLayer = menuViewLayer; } @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { + public void onInitializeAccessibilityNodeInfo( + @NonNull View host, @NonNull AccessibilityNodeInfoCompat info) { super.onInitializeAccessibilityNodeInfo(host, info); final Resources res = host.getResources(); @@ -90,6 +94,15 @@ class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.It R.id.action_remove_menu, res.getString(R.string.accessibility_floating_button_action_remove_menu)); info.addAction(removeMenu); + + if (Flags.floatingMenuDragToEdit()) { + final AccessibilityNodeInfoCompat.AccessibilityActionCompat edit = + new AccessibilityNodeInfoCompat.AccessibilityActionCompat( + R.id.action_edit, + res.getString( + R.string.accessibility_floating_button_action_remove_menu)); + info.addAction(edit); + } } @Override @@ -132,8 +145,8 @@ class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.It return true; } - if (action == R.id.action_remove_menu) { - mAnimationController.removeMenu(); + if (action == R.id.action_remove_menu || action == R.id.action_edit) { + mMenuViewLayer.dispatchAccessibilityAction(action); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java index 52e7b91d373e..75191685b119 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility.floatingmenu; +import static android.R.id.empty; + import android.graphics.PointF; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -78,10 +80,9 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener { mMenuAnimationController.onDraggingStart(); } - mDragToInteractAnimationController.showDismissView(/* show= */ true); - - if (!mDragToInteractAnimationController.maybeConsumeMoveMotionEvent( - motionEvent)) { + mDragToInteractAnimationController.showInteractView(/* show= */ true); + if (mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(motionEvent) + == empty) { mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx); mMenuAnimationController.moveToPositionYIfNeeded( mMenuTranslationDown.y + dy); @@ -94,21 +95,19 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener { final float endX = mMenuTranslationDown.x + dx; mIsDragging = false; + mDragToInteractAnimationController.showInteractView(/* show= */ false); + if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) { - mDragToInteractAnimationController.showDismissView(/* show= */ false); mMenuAnimationController.fadeOutIfEnabled(); - return true; } - if (!mDragToInteractAnimationController.maybeConsumeUpMotionEvent( - motionEvent)) { + if (mDragToInteractAnimationController.maybeConsumeUpMotionEvent(motionEvent) + == empty) { mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS); mMenuAnimationController.flingMenuThenSpringToEdge(endX, mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); - mDragToInteractAnimationController.showDismissView(/* show= */ false); } - // Avoid triggering the listener of the item. return true; } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java index 76808cb7ab7b..334cc87144f9 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -21,24 +21,28 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.annotation.SuppressLint; import android.content.ComponentCallbacks; import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.GradientDrawable; +import android.os.Bundle; +import android.os.UserHandle; +import android.provider.Settings; +import android.provider.SettingsStringUtil; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import androidx.annotation.NonNull; -import androidx.core.view.AccessibilityDelegateCompat; import androidx.lifecycle.Observer; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate; import com.android.internal.accessibility.dialog.AccessibilityTarget; import com.android.systemui.Flags; +import com.android.systemui.util.settings.SecureSettings; import java.util.ArrayList; import java.util.Collections; @@ -72,26 +76,20 @@ class MenuView extends FrameLayout implements private final MenuAnimationController mMenuAnimationController; private OnTargetFeaturesChangeListener mFeaturesChangeListener; private OnMoveToTuckedListener mMoveToTuckedListener; + private SecureSettings mSecureSettings; - MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) { + MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance, + SecureSettings secureSettings) { super(context); mMenuViewModel = menuViewModel; mMenuViewAppearance = menuViewAppearance; + mSecureSettings = secureSettings; mMenuAnimationController = new MenuAnimationController(this, menuViewAppearance); mAdapter = new AccessibilityTargetAdapter(mTargetFeatures); mTargetFeaturesView = new RecyclerView(context); mTargetFeaturesView.setAdapter(mAdapter); mTargetFeaturesView.setLayoutManager(new LinearLayoutManager(context)); - mTargetFeaturesView.setAccessibilityDelegateCompat( - new RecyclerViewAccessibilityDelegate(mTargetFeaturesView) { - @NonNull - @Override - public AccessibilityDelegateCompat getItemDelegate() { - return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this, - mMenuAnimationController); - } - }); setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); // Avoid drawing out of bounds of the parent view setClipToOutline(true); @@ -278,6 +276,7 @@ class MenuView extends FrameLayout implements if (mFeaturesChangeListener != null) { mFeaturesChangeListener.onChange(newTargetFeatures); } + mMenuAnimationController.fadeOutIfEnabled(); } @@ -306,6 +305,10 @@ class MenuView extends FrameLayout implements return mMenuViewAppearance.getMenuPosition(); } + RecyclerView getTargetFeaturesView() { + return mTargetFeaturesView; + } + void persistPositionAndUpdateEdge(Position percentagePosition) { mMenuViewModel.updateMenuSavingPosition(percentagePosition); mMenuViewAppearance.setPercentagePosition(percentagePosition); @@ -424,6 +427,35 @@ class MenuView extends FrameLayout implements onPositionChanged(); } + void gotoEditScreen() { + if (!Flags.floatingMenuDragToEdit()) { + return; + } + mMenuAnimationController.flingMenuThenSpringToEdge( + getMenuPosition().x, 100f, 0f); + mContext.startActivity(getIntentForEditScreen()); + } + + Intent getIntentForEditScreen() { + List<String> targets = new SettingsStringUtil.ColonDelimitedSet.OfStrings( + mSecureSettings.getStringForUser( + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, + UserHandle.USER_CURRENT)).stream().toList(); + + Intent intent = new Intent( + Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS); + Bundle args = new Bundle(); + Bundle fragmentArgs = new Bundle(); + fragmentArgs.putStringArray("targets", targets.toArray(new String[0])); + args.putBundle(":settings:show_fragment_args", fragmentArgs); + // TODO: b/318748373 - The fragment should set its own title using the targets + args.putString( + ":settings:show_fragment_title", "Accessibility Shortcut"); + intent.replaceExtras(args); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + return intent; + } + private InstantInsetLayerDrawable getContainerViewInsetLayer() { return (InstantInsetLayerDrawable) getBackground(); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java index 97999cc19dc8..bb5364d798da 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java @@ -59,7 +59,10 @@ import android.widget.FrameLayout; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.core.view.AccessibilityDelegateCompat; import androidx.lifecycle.Observer; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate; import com.android.internal.accessibility.dialog.AccessibilityTarget; import com.android.internal.annotations.VisibleForTesting; @@ -94,6 +97,8 @@ class MenuViewLayer extends FrameLayout implements private final MenuListViewTouchHandler mMenuListViewTouchHandler; private final MenuMessageView mMessageView; private final DismissView mDismissView; + private final DragToInteractView mDragToInteractView; + private final MenuViewAppearance mMenuViewAppearance; private final MenuAnimationController mMenuAnimationController; private final AccessibilityManager mAccessibilityManager; @@ -178,7 +183,10 @@ class MenuViewLayer extends FrameLayout implements }; MenuViewLayer(@NonNull Context context, WindowManager windowManager, - AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu, + AccessibilityManager accessibilityManager, + MenuViewModel menuViewModel, + MenuViewAppearance menuViewAppearance, MenuView menuView, + IAccessibilityFloatingMenu floatingMenu, SecureSettings secureSettings) { super(context); @@ -190,43 +198,52 @@ class MenuViewLayer extends FrameLayout implements mFloatingMenu = floatingMenu; mSecureSettings = secureSettings; - mMenuViewModel = new MenuViewModel(context, accessibilityManager, secureSettings); - mMenuViewAppearance = new MenuViewAppearance(context, windowManager); - mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance); + mMenuViewModel = menuViewModel; + mMenuViewAppearance = menuViewAppearance; + mMenuView = menuView; + RecyclerView targetFeaturesView = mMenuView.getTargetFeaturesView(); + targetFeaturesView.setAccessibilityDelegateCompat( + new RecyclerViewAccessibilityDelegate(targetFeaturesView) { + @NonNull + @Override + public AccessibilityDelegateCompat getItemDelegate() { + return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this, + mMenuAnimationController, MenuViewLayer.this); + } + }); mMenuAnimationController = mMenuView.getMenuAnimationController(); - if (Flags.floatingMenuDragToHide()) { - mMenuAnimationController.setDismissCallback(this::hideMenuAndShowNotification); - } else { - mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage); - } mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction); mDismissView = new DismissView(context); + mDragToInteractView = new DragToInteractView(context); DismissViewUtils.setup(mDismissView); + mDismissView.getCircle().setId(R.id.action_remove_menu); mNotificationFactory = new MenuNotificationFactory(context); mNotificationManager = context.getSystemService(NotificationManager.class); - mDragToInteractAnimationController = new DragToInteractAnimationController( - mDismissView, mMenuView); + + if (Flags.floatingMenuDragToEdit()) { + mDragToInteractAnimationController = new DragToInteractAnimationController( + mDragToInteractView, mMenuView); + } else { + mDragToInteractAnimationController = new DragToInteractAnimationController( + mDismissView, mMenuView); + } mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() { @Override public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { - mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ true); + mDragToInteractAnimationController.animateInteractMenu( + target.getTargetView().getId(), /* scaleUp= */ true); } @Override public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, float velocityX, float velocityY, boolean wasFlungOut) { - mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false); + mDragToInteractAnimationController.animateInteractMenu( + target.getTargetView().getId(), /* scaleUp= */ false); } @Override public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { - if (Flags.floatingMenuDragToHide()) { - hideMenuAndShowNotification(); - } else { - hideMenuAndShowMessage(); - } - mDismissView.hide(); - mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false); + dispatchAccessibilityAction(target.getTargetView().getId()); } }); @@ -262,7 +279,11 @@ class MenuViewLayer extends FrameLayout implements }); addView(mMenuView, LayerIndex.MENU_VIEW); - addView(mDismissView, LayerIndex.DISMISS_VIEW); + if (Flags.floatingMenuDragToEdit()) { + addView(mDragToInteractView, LayerIndex.DISMISS_VIEW); + } else { + addView(mDismissView, LayerIndex.DISMISS_VIEW); + } addView(mMessageView, LayerIndex.MESSAGE_VIEW); if (Flags.floatingMenuAnimatedTuck()) { @@ -272,6 +293,7 @@ class MenuViewLayer extends FrameLayout implements @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { + mDragToInteractView.updateResources(); mDismissView.updateResources(); mDragToInteractAnimationController.updateResources(); } @@ -428,6 +450,23 @@ class MenuViewLayer extends FrameLayout implements } } + void dispatchAccessibilityAction(int id) { + if (id == R.id.action_remove_menu) { + if (Flags.floatingMenuDragToHide()) { + hideMenuAndShowNotification(); + } else { + hideMenuAndShowMessage(); + } + } else if (id == R.id.action_edit + && Flags.floatingMenuDragToEdit()) { + mMenuView.gotoEditScreen(); + } + mDismissView.hide(); + mDragToInteractView.hide(); + mDragToInteractAnimationController.animateInteractMenu( + id, /* scaleUp= */ false); + } + private CharSequence getMigrationMessage() { final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -475,7 +514,8 @@ class MenuViewLayer extends FrameLayout implements mEduTooltipView = Optional.empty(); } - private void hideMenuAndShowMessage() { + @VisibleForTesting + void hideMenuAndShowMessage() { final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis( SHOW_MESSAGE_DELAY_MS, AccessibilityManager.FLAG_CONTENT_TEXT @@ -485,7 +525,8 @@ class MenuViewLayer extends FrameLayout implements mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE)); } - private void hideMenuAndShowNotification() { + @VisibleForTesting + void hideMenuAndShowNotification() { mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE)); showNotification(); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java index 1f549525256b..bc9d1ffd259b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java @@ -39,7 +39,16 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu { MenuViewLayerController(Context context, WindowManager windowManager, AccessibilityManager accessibilityManager, SecureSettings secureSettings) { mWindowManager = windowManager; - mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, this, + + MenuViewModel menuViewModel = new MenuViewModel( + context, accessibilityManager, secureSettings); + MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context, windowManager); + + mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, + menuViewModel, + menuViewAppearance, + new MenuView(context, menuViewModel, menuViewAppearance, secureSettings), + this, secureSettings); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 71d0e7d6081a..d2c62272e2ec 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -25,6 +25,7 @@ import android.hardware.biometrics.BiometricFingerprintConstants import android.hardware.biometrics.BiometricSourceType import android.util.DisplayMetrics import androidx.annotation.VisibleForTesting +import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback @@ -35,7 +36,11 @@ import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor +import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource +import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.core.LogLevel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R @@ -78,6 +83,7 @@ class AuthRippleController @Inject constructor( private val logger: KeyguardLogger, private val biometricUnlockController: BiometricUnlockController, private val lightRevealScrim: LightRevealScrim, + private val authRippleInteractor: AuthRippleInteractor, private val facePropertyRepository: FacePropertyRepository, rippleView: AuthRippleView? ) : @@ -100,6 +106,22 @@ class AuthRippleController @Inject constructor( init() } + init { + if (DeviceEntryUdfpsRefactor.isEnabled) { + rippleView?.repeatWhenAttached { + repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.CREATED) { + authRippleInteractor.showUnlockRipple.collect { biometricUnlockSource -> + if (biometricUnlockSource == BiometricUnlockSource.FINGERPRINT_SENSOR) { + showUnlockRippleInternal(BiometricSourceType.FINGERPRINT) + } else { + showUnlockRippleInternal(BiometricSourceType.FACE) + } + } + } + } + } + } + @VisibleForTesting public override fun onViewAttached() { authController.addCallback(authControllerCallback) @@ -111,7 +133,9 @@ class AuthRippleController @Inject constructor( keyguardStateController.addCallback(this) wakefulnessLifecycle.addObserver(this) commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } - biometricUnlockController.addListener(biometricModeListener) + if (!DeviceEntryUdfpsRefactor.isEnabled) { + biometricUnlockController.addListener(biometricModeListener) + } } private val biometricModeListener = @@ -119,8 +143,9 @@ class AuthRippleController @Inject constructor( override fun onBiometricUnlockedWithKeyguardDismissal( biometricSourceType: BiometricSourceType? ) { + DeviceEntryUdfpsRefactor.assertInLegacyMode() if (biometricSourceType != null) { - showUnlockRipple(biometricSourceType) + showUnlockRippleInternal(biometricSourceType) } else { logger.log(TAG, LogLevel.ERROR, @@ -143,7 +168,13 @@ class AuthRippleController @Inject constructor( notificationShadeWindowController.setForcePluginOpen(false, this) } - fun showUnlockRipple(biometricSourceType: BiometricSourceType) { + @Deprecated("Update authRippleInteractor.showUnlockRipple instead of calling this.") + fun showUnlockRipple(biometricSourceType: BiometricSourceType) { + DeviceEntryUdfpsRefactor.assertInLegacyMode() + showUnlockRippleInternal(biometricSourceType) + } + + private fun showUnlockRippleInternal(biometricSourceType: BiometricSourceType) { val keyguardNotShowing = !keyguardStateController.isShowing val unlockNotAllowed = !keyguardUpdateMonitor .isUnlockingWithBiometricAllowed(biometricSourceType) @@ -377,12 +408,12 @@ class AuthRippleController @Inject constructor( } "fingerprint" -> { pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation") - showUnlockRipple(BiometricSourceType.FINGERPRINT) + showUnlockRippleInternal(BiometricSourceType.FINGERPRINT) } "face" -> { // note: only shows when about to proceed to the home screen pw.println("face ripple sensorLocation=$faceSensorLocation") - showUnlockRipple(BiometricSourceType.FACE) + showUnlockRippleInternal(BiometricSourceType.FACE) } "custom" -> { if (args.size != 3 || @@ -409,7 +440,7 @@ class AuthRippleController @Inject constructor( pw.println(" custom <x-location: int> <y-location: int>") } - fun invalidCommand(pw: PrintWriter) { + private fun invalidCommand(pw: PrintWriter) { pw.println("invalid command") help(pw) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt index c36e0e21d021..80d37b4741e4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt @@ -121,11 +121,13 @@ constructor( if (it.isAttachedToWindow) { lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation) lottie?.pauseAnimation() + lottie?.removeAllLottieOnCompositionLoadedListener() windowManager.get().removeView(it) } } overlayView = layoutInflater.get().inflate(R.layout.sidefps_view, null, false) + val overlayViewModel = SideFpsOverlayViewModel( applicationContext, @@ -163,8 +165,10 @@ constructor( val lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation) lottie.addLottieOnCompositionLoadedListener { composition: LottieComposition -> - viewModel.setLottieBounds(composition.bounds) - overlayView.visibility = View.VISIBLE + if (overlayView.visibility != View.VISIBLE) { + viewModel.setLottieBounds(composition.bounds) + overlayView.visibility = View.VISIBLE + } } it.alpha = 0f val overlayShowAnimator = diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt index 7a70c4ac9fab..cf7d60140aee 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt @@ -40,8 +40,7 @@ import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationSta import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.BiometricType import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository @@ -63,10 +62,6 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.UserRepository import com.google.errorprone.annotations.CompileTimeConstant -import java.io.PrintWriter -import java.util.Arrays -import java.util.stream.Collectors -import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -88,6 +83,10 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.io.PrintWriter +import java.util.Arrays +import java.util.stream.Collectors +import javax.inject.Inject /** * API to run face authentication and detection for device entry / on keyguard (as opposed to the @@ -165,7 +164,6 @@ constructor( @FaceAuthTableLog private val faceAuthLog: TableLogBuffer, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val displayStateInteractor: DisplayStateInteractor, - private val featureFlags: FeatureFlags, dumpManager: DumpManager, ) : DeviceEntryFaceAuthRepository, Dumpable { private var authCancellationSignal: CancellationSignal? = null @@ -315,7 +313,7 @@ constructor( // or device starts going to sleep. merge( powerInteractor.isAsleep, - if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (KeyguardWmStateRefactor.isEnabled) { keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE) } else { keyguardRepository.keyguardDoneAnimationsFinished.map { true } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt index 08e8c2d8271f..82834387f7b8 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt @@ -8,35 +8,26 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.KeyguardRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel -import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.data.repository.UserRepository -import com.android.systemui.util.kotlin.sample import dagger.Binds import dagger.Module import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext /** Interface for classes that can access device-entry-related application state. */ interface DeviceEntryRepository { - /** Whether the device is immediately entering the device after a biometric unlock. */ - val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> - /** * Whether the device is unlocked. * @@ -85,12 +76,6 @@ constructor( keyguardStateController: KeyguardStateController, keyguardRepository: KeyguardRepository, ) : DeviceEntryRepository { - override val enteringDeviceFromBiometricUnlock = - keyguardRepository.biometricUnlockState - .filter { BiometricUnlockModel.dismissesKeyguard(it) } - .sample( - keyguardRepository.biometricUnlockSource.filterNotNull(), - ) private val _isUnlocked = MutableStateFlow(false) diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt new file mode 100644 index 000000000000..337fe1ea7d98 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.deviceentry.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** Business logic for device entry auth ripple interactions. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class AuthRippleInteractor +@Inject +constructor( + deviceEntrySourceInteractor: DeviceEntrySourceInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) { + private val showUnlockRippleFromDeviceEntryIcon: Flow<BiometricUnlockSource> = + deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfpsSupported -> + if (isUdfpsSupported) { + deviceEntrySourceInteractor.deviceEntryFromDeviceEntryIcon.map { + BiometricUnlockSource.FINGERPRINT_SENSOR + } + } else { + emptyFlow() + } + } + + private val showUnlockRippleFromBiometricUnlock: Flow<BiometricUnlockSource> = + deviceEntrySourceInteractor.deviceEntryFromBiometricSource + val showUnlockRipple: Flow<BiometricUnlockSource> = + merge( + showUnlockRippleFromDeviceEntryIcon, + showUnlockRippleFromBiometricUnlock, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt index 649a9715ffea..782bce494d11 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt @@ -46,7 +46,7 @@ import kotlinx.coroutines.flow.onStart class DeviceEntryHapticsInteractor @Inject constructor( - deviceEntryInteractor: DeviceEntryInteractor, + deviceEntrySourceInteractor: DeviceEntrySourceInteractor, deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor, fingerprintPropertyRepository: FingerprintPropertyRepository, @@ -80,7 +80,7 @@ constructor( } val playSuccessHaptic: Flow<Unit> = - deviceEntryInteractor.enteringDeviceFromBiometricUnlock + deviceEntrySourceInteractor.deviceEntryFromBiometricSource .sample( combine( powerButtonSideFpsEnrolled, diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index 09853578d3f1..73389cb1bdce 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -23,7 +23,6 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository import com.android.systemui.keyguard.data.repository.TrustRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey @@ -31,7 +30,6 @@ import com.android.systemui.scene.shared.model.SceneModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest @@ -55,7 +53,7 @@ class DeviceEntryInteractor @Inject constructor( @Application private val applicationScope: CoroutineScope, - repository: DeviceEntryRepository, + private val repository: DeviceEntryRepository, private val authenticationInteractor: AuthenticationInteractor, private val sceneInteractor: SceneInteractor, deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository, @@ -63,9 +61,6 @@ constructor( flags: SceneContainerFlags, deviceUnlockedInteractor: DeviceUnlockedInteractor, ) { - val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> = - repository.enteringDeviceFromBiometricUnlock - /** * Whether the device is unlocked. * diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt new file mode 100644 index 000000000000..d4f76a84c016 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.deviceentry.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map + +/** + * Hosts application business logic related to the source of the user entering the device. Note: The + * source of the user entering the device isn't equivalent to the reason the device is unlocked. + * + * For example, the user successfully enters the device when they dismiss the lockscreen via a + * bypass biometric or, if the device is already unlocked, by triggering an affordance that + * dismisses the lockscreen. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class DeviceEntrySourceInteractor +@Inject +constructor( + keyguardInteractor: KeyguardInteractor, +) { + val deviceEntryFromBiometricSource: Flow<BiometricUnlockSource> = + keyguardInteractor.biometricUnlockState + .filter { BiometricUnlockModel.dismissesKeyguard(it) } + .sample( + keyguardInteractor.biometricUnlockSource.filterNotNull(), + ) + + private val attemptEnterDeviceFromDeviceEntryIcon: MutableSharedFlow<Unit> = MutableSharedFlow() + val deviceEntryFromDeviceEntryIcon: Flow<Unit> = + attemptEnterDeviceFromDeviceEntryIcon + .sample(keyguardInteractor.isKeyguardDismissible) + .filter { it } // only send events if the keyguard is dismissible + .map {} // map to Unit + + suspend fun attemptEnterDeviceFromDeviceEntryIcon() { + attemptEnterDeviceFromDeviceEntryIcon.emit(Unit) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index d2883cce06c2..c69c9ef93761 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -220,19 +220,6 @@ object Flags { @JvmField val WALLPAPER_PICKER_PREVIEW_ANIMATION = releasedFlag("wallpaper_picker_preview_animation") - /** - * TODO(b/278086361): Tracking bug - * Complete rewrite of the interactions between System UI and Window Manager involving keyguard - * state. When enabled, calls to ActivityTaskManagerService from System UI will exclusively - * occur from [WmLockscreenVisibilityManager] rather than the legacy KeyguardViewMediator. - * - * This flag is under development; some types of unlock may not animate properly if you enable - * it. - */ - @JvmField - val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag = - unreleasedFlag("keyguard_wm_state_refactor") - // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index e2ab20e29e2d..f10b87ee1cc5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -77,7 +77,6 @@ import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.SystemUIApplication; import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier; import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder; import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder; @@ -329,7 +328,7 @@ public class KeyguardService extends Service { mFlags = featureFlags; mPowerInteractor = powerInteractor; - if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (KeyguardWmStateRefactor.isEnabled()) { WindowManagerLockscreenVisibilityViewBinder.bind( wmLockscreenVisibilityViewModel, wmLockscreenVisibilityManager, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 01ba0d214a97..53c81e537708 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -419,7 +419,7 @@ class KeyguardUnlockAnimationController @Inject constructor( */ fun canPerformInWindowLauncherAnimations(): Boolean { // TODO(b/278086361): Refactor in-window animations. - return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) && + return !KeyguardWmStateRefactor.isEnabled && isSupportedLauncherUnderneath() && // If the launcher is underneath, but we're about to launch an activity, don't do // the animations since they won't be visible. @@ -866,7 +866,7 @@ class KeyguardUnlockAnimationController @Inject constructor( } surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget -> - if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (!KeyguardWmStateRefactor.isEnabled) { val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height() @@ -1005,7 +1005,7 @@ class KeyguardUnlockAnimationController @Inject constructor( if (keyguardStateController.isShowing) { // Hide the keyguard, with no fade out since we animated it away during the unlock. - if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (!KeyguardWmStateRefactor.isEnabled) { keyguardViewController.hide( surfaceBehindRemoteAnimationStartTime, 0 /* fadeOutDuration */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 50caf17f71dd..8e3b19609142 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -139,7 +139,6 @@ import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.flags.SystemPropertiesHelper; import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; @@ -175,8 +174,6 @@ import com.android.systemui.util.time.SystemClock; import com.android.systemui.wallpapers.data.repository.WallpaperRepository; import com.android.wm.shell.keyguard.KeyguardTransitions; -import dagger.Lazy; - import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -186,6 +183,7 @@ import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; +import dagger.Lazy; import kotlinx.coroutines.CoroutineDispatcher; /** @@ -1051,7 +1049,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, IRemoteAnimationFinishedCallback finishedCallback) { Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation"); startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback); - if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (KeyguardWmStateRefactor.isEnabled()) { mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationStart( transit, apps, wallpapers, nonApps, finishedCallback); } @@ -1061,7 +1059,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override // Binder interface public void onAnimationCancelled() { cancelKeyguardExitAnimation(); - if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (KeyguardWmStateRefactor.isEnabled()) { mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationCancelled(); } } @@ -2757,7 +2755,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mUiBgExecutor.execute(() -> { Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")"); - if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (KeyguardWmStateRefactor.isEnabled()) { // Handled in WmLockscreenVisibilityManager if flag is enabled. return; } @@ -2811,7 +2809,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, setShowingLocked(true, hidingOrGoingAway /* force */); mHiding = false; - if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (!KeyguardWmStateRefactor.isEnabled()) { // Handled directly in StatusBarKeyguardViewManager if enabled. mKeyguardViewControllerLazy.get().show(options); } @@ -2888,7 +2886,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true); // Handled in WmLockscreenVisibilityManager if flag is enabled. - if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (!KeyguardWmStateRefactor.isEnabled()) { // Don't actually hide the Keyguard at the moment, wait for window manager // until it tells us it's safe to do so with startKeyguardExitAnimation. // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager @@ -2994,7 +2992,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } else { Log.d(TAG, "Hiding keyguard while occluded. Just hide the keyguard view and exit."); - if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (!KeyguardWmStateRefactor.isEnabled()) { mKeyguardViewControllerLazy.get().hide( mSystemClock.uptimeMillis() + mHideAnimation.getStartOffset(), mHideAnimation.getDuration()); @@ -3030,7 +3028,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // If the flag is enabled, remote animation state is handled in // WmLockscreenVisibilityManager. if (finishedCallback != null - && !mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + && !KeyguardWmStateRefactor.isEnabled()) { // There will not execute animation, send a finish callback to ensure the remote // animation won't hang there. try { @@ -3056,7 +3054,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, new IRemoteAnimationFinishedCallback() { @Override public void onAnimationFinished() throws RemoteException { - if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (!KeyguardWmStateRefactor.isEnabled()) { try { finishedCallback.onAnimationFinished(); } catch (RemoteException e) { @@ -3088,7 +3086,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // it will dismiss the panel in that case. } else if (!mStatusBarStateController.leaveOpenOnKeyguardHide() && apps != null && apps.length > 0) { - if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (!KeyguardWmStateRefactor.isEnabled()) { // Handled in WmLockscreenVisibilityManager. Other logic in this class will // short circuit when this is null. mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback; @@ -3112,7 +3110,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, createInteractionJankMonitorConf( CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled")); - if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (!KeyguardWmStateRefactor.isEnabled()) { // Handled directly in StatusBarKeyguardViewManager if enabled. mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration); } @@ -3131,7 +3129,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Slog.e(TAG, "Keyguard exit without a corresponding app to show."); try { - if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (!KeyguardWmStateRefactor.isEnabled()) { finishedCallback.onAnimationFinished(); } } catch (RemoteException e) { @@ -3163,7 +3161,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onAnimationEnd(Animator animation) { try { - if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (!KeyguardWmStateRefactor.isEnabled()) { finishedCallback.onAnimationFinished(); } } catch (RemoteException e) { @@ -3176,7 +3174,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onAnimationCancel(Animator animation) { try { - if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (!KeyguardWmStateRefactor.isEnabled()) { finishedCallback.onAnimationFinished(); } } catch (RemoteException e) { @@ -3341,7 +3339,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; } - if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (!KeyguardWmStateRefactor.isEnabled()) { // Handled in WmLockscreenVisibilityManager. mActivityTaskManagerService.keyguardGoingAway(flags); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt new file mode 100644 index 000000000000..ddccc5d9e96d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the keyguard wm state refactor flag state. */ +@Suppress("NOTHING_TO_INLINE") +object KeyguardWmStateRefactor { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.keyguardWmStateRefactor() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 14371949c9c8..1c6056c3b9d0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -101,7 +101,7 @@ interface KeyguardRepository { * Whether the device is locked or unlocked right now. This is true when keyguard has been * dismissed or can be dismissed by a swipe */ - val isKeyguardUnlocked: StateFlow<Boolean> + val isKeyguardDismissible: StateFlow<Boolean> /** * Observable for the signal that keyguard is about to go away. @@ -388,7 +388,7 @@ constructor( } .distinctUntilChanged() - override val isKeyguardUnlocked: StateFlow<Boolean> = + override val isKeyguardDismissible: StateFlow<Boolean> = conflatedCallbackFlow { val callback = object : KeyguardStateController.Callback { @@ -396,7 +396,7 @@ constructor( trySendWithFailureLogging( keyguardStateController.isUnlocked, TAG, - "updated isKeyguardUnlocked due to onUnlockedChanged" + "updated isKeyguardDismissible due to onUnlockedChanged" ) } @@ -404,7 +404,7 @@ constructor( trySendWithFailureLogging( keyguardStateController.isUnlocked, TAG, - "updated isKeyguardUnlocked due to onKeyguardShowingChanged" + "updated isKeyguardDismissible due to onKeyguardShowingChanged" ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 8b2b45f1bf57..3965648bd224 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -23,7 +23,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel @@ -230,7 +230,7 @@ constructor( combine( startedKeyguardTransitionStep, keyguardInteractor.statusBarState, - keyguardInteractor.isKeyguardUnlocked, + keyguardInteractor.isKeyguardDismissible, ::Triple ), ::toQuad @@ -307,7 +307,7 @@ constructor( } private fun listenForLockscreenToGone() { - if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (KeyguardWmStateRefactor.isEnabled) { return } @@ -324,7 +324,7 @@ constructor( } private fun listenForLockscreenToGoneDragging() { - if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (KeyguardWmStateRefactor.isEnabled) { return } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 33b6373d5876..acbd9fb4c407 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -23,7 +23,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel @@ -217,7 +217,7 @@ constructor( } private fun listenForPrimaryBouncerToGone() { - if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (KeyguardWmStateRefactor.isEnabled) { // This is handled in KeyguardSecurityContainerController and // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a // transition vs. listening to legacy state flags. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 36bd905d23ac..22d11d08054e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -32,6 +32,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff @@ -162,8 +163,8 @@ constructor( /** Whether the keyguard is showing or not. */ val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing - /** Whether the keyguard is unlocked or not. */ - val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked + /** Whether the keyguard is dismissible or not. */ + val isKeyguardDismissible: Flow<Boolean> = repository.isKeyguardDismissible /** Whether the keyguard is occluded (covered by an activity). */ val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded @@ -194,6 +195,9 @@ constructor( /** Observable for the [StatusBarState] */ val statusBarState: Flow<StatusBarState> = repository.statusBarState + /** Source of the most recent biometric unlock, such as fingerprint or face. */ + val biometricUnlockSource: Flow<BiometricUnlockSource?> = repository.biometricUnlockSource + /** * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear, * side, under display) is used to unlock the device. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt index a02e8ac84a75..703bb879533c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -32,6 +32,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.VibratorHelper +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch @@ -48,6 +49,7 @@ object DeviceEntryIconViewBinder { @SuppressLint("ClickableViewAccessibility") @JvmStatic fun bind( + applicationScope: CoroutineScope, view: DeviceEntryIconView, viewModel: DeviceEntryIconViewModel, fgViewModel: DeviceEntryForegroundViewModel, @@ -69,7 +71,7 @@ object DeviceEntryIconViewBinder { view, HapticFeedbackConstants.CONFIRM, ) - viewModel.onLongPress() + applicationScope.launch { viewModel.onLongPress() } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index 0bf9ad02eb58..3fc9b4200f35 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -31,6 +31,7 @@ import com.android.keyguard.LockIconView import com.android.keyguard.LockIconViewController import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.biometrics.AuthController +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -46,6 +47,7 @@ import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.VibratorHelper import dagger.Lazy import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi /** Includes the device entry icon. */ @@ -53,6 +55,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi class DefaultDeviceEntrySection @Inject constructor( + @Application private val applicationScope: CoroutineScope, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val authController: AuthController, private val windowManager: WindowManager, @@ -91,6 +94,7 @@ constructor( if (DeviceEntryUdfpsRefactor.isEnabled) { constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let { DeviceEntryIconViewBinder.bind( + applicationScope, it, deviceEntryIconViewModel.get(), deviceEntryForegroundViewModel.get(), diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt index eacaa40de821..a3d54532411c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -20,6 +20,7 @@ import android.animation.FloatEvaluator import android.animation.IntEvaluator import com.android.keyguard.KeyguardViewController import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceEntrySourceInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -56,6 +57,7 @@ constructor( private val sceneContainerFlags: SceneContainerFlags, private val keyguardViewController: Lazy<KeyguardViewController>, private val deviceEntryInteractor: DeviceEntryInteractor, + private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor, ) { private val intEvaluator = IntEvaluator() private val floatEvaluator = FloatEvaluator() @@ -208,14 +210,13 @@ constructor( } } - fun onLongPress() { - // TODO (b/309804148): play auth ripple via an interactor - + suspend fun onLongPress() { if (sceneContainerFlags.isEnabled()) { deviceEntryInteractor.attemptDeviceEntry() } else { keyguardViewController.get().showPrimaryBouncer(/* scrim */ true) } + deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon() } private fun DeviceEntryIconView.IconType.toAccessibilityHintType(): diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index cc53aabfcd25..f173900caa8c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -16,6 +16,7 @@ package com.android.systemui.recents; +import static android.app.Flags.keyguardPrivateNotifications; import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; @@ -81,12 +82,13 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.util.ScreenshotHelper; import com.android.internal.util.ScreenshotRequest; import com.android.systemui.Dumpable; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.KeyguardWmStateRefactor; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager; import com.android.systemui.model.SysUiState; @@ -167,9 +169,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final Optional<UnfoldTransitionProgressForwarder> mUnfoldTransitionProgressForwarder; private final UiEventLogger mUiEventLogger; private final DisplayTracker mDisplayTracker; - private Region mActiveNavBarRegion; + private final BroadcastDispatcher mBroadcastDispatcher; + private IOverviewProxy mOverviewProxy; private int mConnectionBackoffAttempts; private boolean mBound; @@ -419,6 +422,21 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis retryConnectionWithBackoff(); }; + private final BroadcastReceiver mUserEventReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Objects.equals(intent.getAction(), Intent.ACTION_USER_UNLOCKED)) { + if (keyguardPrivateNotifications()) { + // Start the overview connection to the launcher service + // Connect if user hasn't connected yet + if (getProxy() == null) { + startConnectionToCurrentUser(); + } + } + } + } + }; + private final BroadcastReceiver mLauncherStateChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -586,7 +604,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis FeatureFlags featureFlags, SceneContainerFlags sceneContainerFlags, DumpManager dumpManager, - Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder + Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder, + BroadcastDispatcher broadcastDispatcher ) { // b/241601880: This component shouldn't be running for a non-primary user if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) { @@ -615,8 +634,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mUiEventLogger = uiEventLogger; mDisplayTracker = displayTracker; mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder; + mBroadcastDispatcher = broadcastDispatcher; - if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (!KeyguardWmStateRefactor.isEnabled()) { mSysuiUnlockAnimationController = sysuiUnlockAnimationController; } else { mSysuiUnlockAnimationController = inWindowLauncherUnlockAnimationManager; @@ -635,6 +655,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis filter.addAction(Intent.ACTION_PACKAGE_CHANGED); mContext.registerReceiver(mLauncherStateChangedReceiver, filter); + if (keyguardPrivateNotifications()) { + mBroadcastDispatcher.registerReceiver(mUserEventReceiver, + new IntentFilter(Intent.ACTION_USER_UNLOCKED), + null /* executor */, UserHandle.ALL); + } + // Listen for status bar state changes statusBarWinController.registerCallback(mStatusBarWindowCallback); mScreenshotHelper = new ScreenshotHelper(context); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 24ac70e63e46..2a4753def463 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -234,10 +234,12 @@ public class NotificationLockscreenUserManagerImpl implements } else if (profileAvailabilityActions(action)) { updateCurrentProfilesCache(); } else if (Objects.equals(action, Intent.ACTION_USER_UNLOCKED)) { - // Start the overview connection to the launcher service - // Connect if user hasn't connected yet - if (mOverviewProxyServiceLazy.get().getProxy() == null) { - mOverviewProxyServiceLazy.get().startConnectionToCurrentUser(); + if (!keyguardPrivateNotifications()) { + // Start the overview connection to the launcher service + // Connect if user hasn't connected yet + if (mOverviewProxyServiceLazy.get().getProxy() == null) { + mOverviewProxyServiceLazy.get().startConnectionToCurrentUser(); + } } } else if (Objects.equals(action, NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION)) { final IntentSender intentSender = intent.getParcelableExtra( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index e3b65ab27f48..61bd112121bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -20,6 +20,7 @@ import android.graphics.Color; import android.os.Trace; import com.android.systemui.dock.DockManager; +import com.android.systemui.res.R; import com.android.systemui.scrim.ScrimView; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; @@ -39,8 +40,8 @@ public enum ScrimState { OFF { @Override public void prepare(ScrimState previousState) { - mFrontTint = Color.BLACK; - mBehindTint = Color.BLACK; + mFrontTint = mBackgroundColor; + mBehindTint = mBackgroundColor; mFrontAlpha = 1f; mBehindAlpha = 1f; @@ -74,15 +75,15 @@ public enum ScrimState { } else { mAnimationDuration = ScrimController.ANIMATION_DURATION; } - mFrontTint = Color.BLACK; - mBehindTint = Color.BLACK; - mNotifTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT; + mFrontTint = mBackgroundColor; + mBehindTint = mBackgroundColor; + mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT; mFrontAlpha = 0; mBehindAlpha = mClipQsScrim ? 1 : mScrimBehindAlphaKeyguard; mNotifAlpha = mClipQsScrim ? mScrimBehindAlphaKeyguard : 0; if (mClipQsScrim) { - updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK); + updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor); } } }, @@ -93,10 +94,10 @@ public enum ScrimState { // notif scrim alpha values are determined by ScrimController#applyState // based on the shade expansion - mFrontTint = Color.BLACK; + mFrontTint = mBackgroundColor; mFrontAlpha = .66f; - mBehindTint = Color.BLACK; + mBehindTint = mBackgroundColor; mBehindAlpha = 1f; } }, @@ -110,7 +111,7 @@ public enum ScrimState { mBehindTint = previousState.mBehindTint; mBehindAlpha = previousState.mBehindAlpha; - mFrontTint = Color.BLACK; + mFrontTint = mBackgroundColor; mFrontAlpha = .66f; } }, @@ -122,7 +123,7 @@ public enum ScrimState { @Override public void prepare(ScrimState previousState) { mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha; - mBehindTint = mClipQsScrim ? Color.BLACK : mSurfaceColor; + mBehindTint = mClipQsScrim ? mBackgroundColor : mSurfaceColor; mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0; mNotifTint = Color.TRANSPARENT; mFrontAlpha = 0f; @@ -154,10 +155,10 @@ public enum ScrimState { mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha; mNotifAlpha = 1f; mFrontAlpha = 0f; - mBehindTint = mClipQsScrim ? Color.TRANSPARENT : Color.BLACK; + mBehindTint = mClipQsScrim ? Color.TRANSPARENT : mBackgroundColor; if (mClipQsScrim) { - updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK); + updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor); } } }, @@ -184,11 +185,11 @@ public enum ScrimState { final boolean isDocked = mDockManager.isDocked(); mBlankScreen = mDisplayRequiresBlanking; - mFrontTint = Color.BLACK; + mFrontTint = mBackgroundColor; mFrontAlpha = (alwaysOnEnabled || isDocked || quickPickupEnabled) ? mAodFrontScrimAlpha : 1f; - mBehindTint = Color.BLACK; + mBehindTint = mBackgroundColor; mBehindAlpha = ScrimController.TRANSPARENT; mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG; @@ -222,8 +223,8 @@ public enum ScrimState { @Override public void prepare(ScrimState previousState) { mFrontAlpha = mAodFrontScrimAlpha; - mBehindTint = Color.BLACK; - mFrontTint = Color.BLACK; + mBehindTint = mBackgroundColor; + mFrontTint = mBackgroundColor; mBlankScreen = mDisplayRequiresBlanking; mAnimationDuration = mWakeLockScreenSensorActive ? ScrimController.ANIMATION_DURATION_LONG : ScrimController.ANIMATION_DURATION; @@ -231,7 +232,7 @@ public enum ScrimState { @Override public float getMaxLightRevealScrimAlpha() { return mWakeLockScreenSensorActive ? ScrimController.WAKE_SENSOR_SCRIM_ALPHA - : AOD.getMaxLightRevealScrimAlpha(); + : AOD.getMaxLightRevealScrimAlpha(); } }, @@ -245,7 +246,6 @@ public enum ScrimState { mBehindAlpha = mClipQsScrim ? 1 : 0; mNotifAlpha = 0; mFrontAlpha = 0; - mAnimationDuration = mKeyguardFadingAway ? mKeyguardFadingAwayDuration : CentralSurfaces.FADE_KEYGUARD_DURATION; @@ -259,22 +259,22 @@ public enum ScrimState { && !fromAod; mFrontTint = Color.TRANSPARENT; - mBehindTint = Color.BLACK; + mBehindTint = mBackgroundColor; mBlankScreen = false; if (mDisplayRequiresBlanking && previousState == ScrimState.AOD) { // Set all scrims black, before they fade transparent. - updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */); - updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */); + updateScrimColor(mScrimInFront, 1f /* alpha */, mBackgroundColor /* tint */); + updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor /* tint */); // Scrims should still be black at the end of the transition. - mFrontTint = Color.BLACK; - mBehindTint = Color.BLACK; + mFrontTint = mBackgroundColor; + mBehindTint = mBackgroundColor; mBlankScreen = true; } if (mClipQsScrim) { - updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK); + updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor); } } }, @@ -283,8 +283,8 @@ public enum ScrimState { @Override public void prepare(ScrimState previousState) { mFrontTint = Color.TRANSPARENT; - mBehindTint = Color.BLACK; - mNotifTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT; + mBehindTint = mBackgroundColor; + mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT; mFrontAlpha = 0; mBehindAlpha = mClipQsScrim ? 1 : 0; @@ -293,7 +293,7 @@ public enum ScrimState { mBlankScreen = false; if (mClipQsScrim) { - updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK); + updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor); } } }; @@ -327,9 +327,11 @@ public enum ScrimState { boolean mKeyguardFadingAway; long mKeyguardFadingAwayDuration; boolean mClipQsScrim; + int mBackgroundColor; public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters, DockManager dockManager) { + mBackgroundColor = scrimBehind.getContext().getColor(R.color.shade_scrim_background_dark); mScrimInFront = scrimInFront; mScrimBehind = scrimBehind; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 88347ab90606..4c83ca28b3cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -69,6 +69,7 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.KeyguardWmStateRefactor; import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor; @@ -474,7 +475,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mIsDocked = mDockManager.isDocked(); } - if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (KeyguardWmStateRefactor.isEnabled()) { // Show the keyguard views whenever we've told WM that the lockscreen is visible. mShadeViewController.postToView(() -> collectFlow( @@ -1428,7 +1429,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb executeAfterKeyguardGoneAction(); } - if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (KeyguardWmStateRefactor.isEnabled()) { mKeyguardTransitionInteractor.startDismissKeyguardTransition(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java index 9bcab57bec87..90878169c201 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java @@ -16,10 +16,12 @@ package com.android.systemui.accessibility.floatingmenu; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.annotation.NonNull; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.WindowManager; @@ -27,10 +29,12 @@ import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.utils.TestUtils; import com.android.systemui.util.settings.SecureSettings; -import com.android.wm.shell.bubbles.DismissViewUtils; import com.android.wm.shell.common.bubbles.DismissView; +import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import org.junit.Before; import org.junit.Rule; @@ -46,6 +50,7 @@ import org.mockito.junit.MockitoRule; @TestableLooper.RunWithLooper public class DragToInteractAnimationControllerTest extends SysuiTestCase { private DragToInteractAnimationController mDragToInteractAnimationController; + private DragToInteractView mInteractView; private DismissView mDismissView; @Rule @@ -57,29 +62,72 @@ public class DragToInteractAnimationControllerTest extends SysuiTestCase { @Before public void setUp() throws Exception { final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); + final SecureSettings mockSecureSettings = TestUtils.mockSecureSettings(); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager, - mock(SecureSettings.class)); + mockSecureSettings); final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager); - final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel, - stubMenuViewAppearance); + final MenuView stubMenuView = spy(new MenuView(mContext, stubMenuViewModel, + stubMenuViewAppearance, mockSecureSettings)); + mInteractView = spy(new DragToInteractView(mContext)); mDismissView = spy(new DismissView(mContext)); - DismissViewUtils.setup(mDismissView); - mDragToInteractAnimationController = new DragToInteractAnimationController( - mDismissView, stubMenuView); + + if (Flags.floatingMenuDragToEdit()) { + mDragToInteractAnimationController = new DragToInteractAnimationController( + mInteractView, stubMenuView); + } else { + mDragToInteractAnimationController = new DragToInteractAnimationController( + mDismissView, stubMenuView); + } + + mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() { + @Override + public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { + + } + + @Override + public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, + float velX, float velY, boolean wasFlungOut) { + + } + + @Override + public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { + + } + }); } @Test - public void showDismissView_success() { - mDragToInteractAnimationController.showDismissView(true); + @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void showDismissView_success_old() { + mDragToInteractAnimationController.showInteractView(true); verify(mDismissView).show(); } @Test - public void hideDismissView_success() { - mDragToInteractAnimationController.showDismissView(false); + @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void hideDismissView_success_old() { + mDragToInteractAnimationController.showInteractView(false); verify(mDismissView).hide(); } + + @Test + @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void showDismissView_success() { + mDragToInteractAnimationController.showInteractView(true); + + verify(mInteractView).show(); + } + + @Test + @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void hideDismissView_success() { + mDragToInteractAnimationController.showInteractView(false); + + verify(mInteractView).hide(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java index 215f93d1e163..e0df1e0e5586 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.accessibility.floatingmenu; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -42,6 +43,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.Flags; import com.android.systemui.Prefs; import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.utils.TestUtils; import com.android.systemui.util.settings.SecureSettings; import org.junit.After; @@ -79,10 +81,12 @@ public class MenuAnimationControllerTest extends SysuiTestCase { final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager); + final SecureSettings secureSettings = TestUtils.mockSecureSettings(); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager, - mock(SecureSettings.class)); + secureSettings); - mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance)); + mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance, + secureSettings)); mViewPropertyAnimator = spy(mMenuView.animate()); doReturn(mViewPropertyAnimator).when(mMenuView).animate(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java index 9c8de302c5e1..c2ed7d4a9d3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java @@ -22,10 +22,13 @@ import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTIO import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.graphics.Rect; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.WindowManager; @@ -37,7 +40,9 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.utils.TestUtils; import com.android.systemui.res.R; import com.android.systemui.util.settings.SecureSettings; @@ -49,6 +54,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.concurrent.atomic.AtomicBoolean; + /** Tests for {@link MenuItemAccessibilityDelegate}. */ @SmallTest @TestableLooper.RunWithLooper @@ -59,17 +66,16 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase { @Mock private AccessibilityManager mAccessibilityManager; - @Mock - private SecureSettings mSecureSettings; - @Mock - private DragToInteractAnimationController.DismissCallback mStubDismissCallback; - + private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings(); private RecyclerView mStubListView; private MenuView mMenuView; + private MenuViewLayer mMenuViewLayer; private MenuItemAccessibilityDelegate mMenuItemAccessibilityDelegate; private MenuAnimationController mMenuAnimationController; private final Rect mDraggableBounds = new Rect(100, 200, 300, 400); + private final AtomicBoolean mEditReceived = new AtomicBoolean(false); + @Before public void setUp() { final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); @@ -80,20 +86,28 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase { final int halfScreenHeight = stubWindowManager.getCurrentWindowMetrics().getBounds().height() / 2; - mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance)); + mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance, + mSecureSettings)); mMenuView.setTranslationY(halfScreenHeight); + mMenuViewLayer = spy(new MenuViewLayer( + mContext, stubWindowManager, mAccessibilityManager, + stubMenuViewModel, stubMenuViewAppearance, mMenuView, + mock(IAccessibilityFloatingMenu.class), mSecureSettings)); + doReturn(mDraggableBounds).when(mMenuView).getMenuDraggableBounds(); mStubListView = new RecyclerView(mContext); mMenuAnimationController = spy(new MenuAnimationController(mMenuView, stubMenuViewAppearance)); mMenuItemAccessibilityDelegate = new MenuItemAccessibilityDelegate(new RecyclerViewAccessibilityDelegate( - mStubListView), mMenuAnimationController); + mStubListView), mMenuAnimationController, mMenuViewLayer); + mEditReceived.set(false); } @Test - public void getAccessibilityActionList_matchSize() { + @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void getAccessibilityActionList_matchSize_withoutEdit() { final AccessibilityNodeInfoCompat info = new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo()); @@ -103,6 +117,17 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void getAccessibilityActionList_matchSize() { + final AccessibilityNodeInfoCompat info = + new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo()); + + mMenuItemAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mStubListView, info); + + assertThat(info.getActionList().size()).isEqualTo(7); + } + + @Test public void performMoveTopLeftAction_matchPosition() { final boolean moveTopLeftAction = mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView, @@ -169,13 +194,22 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase { @Test public void performRemoveMenuAction_success() { - mMenuAnimationController.setDismissCallback(mStubDismissCallback); final boolean removeMenuAction = mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView, R.id.action_remove_menu, null); assertThat(removeMenuAction).isTrue(); - verify(mMenuAnimationController).removeMenu(); + verify(mMenuViewLayer).dispatchAccessibilityAction(R.id.action_remove_menu); + } + + @Test + public void performEditAction_success() { + final boolean editAction = + mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView, + R.id.action_edit, null); + + assertThat(editAction).isTrue(); + verify(mMenuViewLayer).dispatchAccessibilityAction(R.id.action_edit); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java index e1522f5f6751..9e8c6b3395e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.accessibility.floatingmenu; +import static android.R.id.empty; import static android.view.View.OVER_SCROLL_NEVER; import static com.google.common.truth.Truth.assertThat; @@ -27,6 +28,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; @@ -38,10 +41,11 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.test.filters.SmallTest; import com.android.internal.accessibility.dialog.AccessibilityTarget; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.accessibility.MotionEventHelper; +import com.android.systemui.accessibility.utils.TestUtils; import com.android.systemui.util.settings.SecureSettings; -import com.android.wm.shell.bubbles.DismissViewUtils; import com.android.wm.shell.common.bubbles.DismissView; import org.junit.After; @@ -71,6 +75,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { private DragToInteractAnimationController mDragToInteractAnimationController; private RecyclerView mStubListView; private DismissView mDismissView; + private DragToInteractView mInteractView; @Rule public MockitoRule mockito = MockitoJUnit.rule(); @@ -81,19 +86,28 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { @Before public void setUp() throws Exception { final WindowManager windowManager = mContext.getSystemService(WindowManager.class); + final SecureSettings secureSettings = TestUtils.mockSecureSettings(); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager, - mock(SecureSettings.class)); + secureSettings); final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext, windowManager); - mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance); + mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance, + secureSettings); mStubMenuView.setTranslationX(0); mStubMenuView.setTranslationY(0); mMenuAnimationController = spy(new MenuAnimationController( mStubMenuView, stubMenuViewAppearance)); + mInteractView = spy(new DragToInteractView(mContext)); mDismissView = spy(new DismissView(mContext)); - DismissViewUtils.setup(mDismissView); - mDragToInteractAnimationController = - spy(new DragToInteractAnimationController(mDismissView, mStubMenuView)); + + if (Flags.floatingMenuDragToEdit()) { + mDragToInteractAnimationController = spy(new DragToInteractAnimationController( + mInteractView, mStubMenuView)); + } else { + mDragToInteractAnimationController = spy(new DragToInteractAnimationController( + mDismissView, mStubMenuView)); + } + mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController, mDragToInteractAnimationController); final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets); @@ -115,7 +129,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { @Test public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() { - doReturn(false).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent( + doReturn(empty).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent( any(MotionEvent.class)); final int offset = 100; final MotionEvent stubDownEvent = @@ -136,6 +150,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { } @Test + @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) public void onActionMoveEvent_shouldShowDismissView() { final int offset = 100; final MotionEvent stubDownEvent = @@ -154,6 +169,25 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void onActionMoveEvent_shouldShowInteractView() { + final int offset = 100; + final MotionEvent stubDownEvent = + mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1, + MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(), + mStubMenuView.getTranslationY()); + final MotionEvent stubMoveEvent = + mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 3, + MotionEvent.ACTION_MOVE, mStubMenuView.getTranslationX() + offset, + mStubMenuView.getTranslationY() + offset); + + mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent); + mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent); + + verify(mInteractView).show(); + } + + @Test public void dragAndDrop_shouldFlingMenuThenSpringToEdge() { final int offset = 100; final MotionEvent stubDownEvent = diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index bc9a0a5484ac..4a1bdbcc9b48 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -30,6 +30,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -72,6 +73,8 @@ import com.android.internal.messages.nano.SystemMessageProto; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestableContext; +import com.android.systemui.accessibility.utils.TestUtils; +import com.android.systemui.res.R; import com.android.systemui.util.settings.SecureSettings; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; @@ -81,6 +84,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; @@ -122,18 +126,17 @@ public class MenuViewLayerTest extends SysuiTestCase { private SysuiTestableContext mSpyContext = getContext(); @Mock private IAccessibilityFloatingMenu mFloatingMenu; - - @Mock - private SecureSettings mSecureSettings; - @Mock private WindowManager mStubWindowManager; - @Mock private AccessibilityManager mStubAccessibilityManager; + private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings(); private final NotificationManager mMockNotificationManager = mock(NotificationManager.class); + private final ArgumentMatcher<IntentFilter> mNotificationMatcher = + (arg) -> arg.hasAction(ACTION_UNDO) && arg.hasAction(ACTION_DELETE); + @Before public void setUp() throws Exception { mSpyContext.addMockSystemService(Context.NOTIFICATION_SERVICE, mMockNotificationManager); @@ -145,8 +148,16 @@ public class MenuViewLayerTest extends SysuiTestCase { new WindowMetrics(mDisplayBounds, fakeDisplayInsets(), /* density = */ 0.0f)); doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics(); - mMenuViewLayer = new MenuViewLayer(mSpyContext, mStubWindowManager, - mStubAccessibilityManager, mFloatingMenu, mSecureSettings); + MenuViewModel menuViewModel = new MenuViewModel( + mSpyContext, mStubAccessibilityManager, mSecureSettings); + MenuViewAppearance menuViewAppearance = new MenuViewAppearance( + mSpyContext, mStubWindowManager); + mMenuView = spy( + new MenuView(mSpyContext, menuViewModel, menuViewAppearance, mSecureSettings)); + + mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager, + mStubAccessibilityManager, menuViewModel, menuViewAppearance, mMenuView, + mFloatingMenu, mSecureSettings)); mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW); mMenuAnimationController = mMenuView.getMenuAnimationController(); @@ -236,6 +247,27 @@ public class MenuViewLayerTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void onEditAction_gotoEditScreen_isCalled() { + mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit); + verify(mMenuView).gotoEditScreen(); + } + + @Test + @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE) + public void onDismissAction_hideMenuAndShowNotification() { + mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu); + verify(mMenuViewLayer).hideMenuAndShowNotification(); + } + + @Test + @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE) + public void onDismissAction_hideMenuAndShowMessage() { + mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu); + verify(mMenuViewLayer).hideMenuAndShowMessage(); + } + + @Test public void showingImeInsetsChange_notOverlapOnIme_menuKeepOriginalPosition() { final float menuTop = STATUS_BAR_HEIGHT + 100; mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop)); @@ -307,19 +339,13 @@ public class MenuViewLayerTest extends SysuiTestCase { @Test @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE) public void onReleasedInTarget_hideMenuAndShowNotificationWithExpectedActions() { - dragMenuThenReleasedInTarget(); + dragMenuThenReleasedInTarget(R.id.action_remove_menu); verify(mMockNotificationManager).notify( eq(SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN), any(Notification.class)); - ArgumentCaptor<IntentFilter> intentFilterCaptor = ArgumentCaptor.forClass( - IntentFilter.class); verify(mSpyContext).registerReceiver( - any(BroadcastReceiver.class), - intentFilterCaptor.capture(), - anyInt()); - assertThat(intentFilterCaptor.getValue().matchAction(ACTION_UNDO)).isTrue(); - assertThat(intentFilterCaptor.getValue().matchAction(ACTION_DELETE)).isTrue(); + any(BroadcastReceiver.class), argThat(mNotificationMatcher), anyInt()); } @Test @@ -327,10 +353,10 @@ public class MenuViewLayerTest extends SysuiTestCase { public void receiveActionUndo_dismissNotificationAndMenuVisible() { ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass( BroadcastReceiver.class); - dragMenuThenReleasedInTarget(); + dragMenuThenReleasedInTarget(R.id.action_remove_menu); verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(), - any(IntentFilter.class), anyInt()); + argThat(mNotificationMatcher), anyInt()); broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_UNDO)); verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue()); @@ -344,10 +370,10 @@ public class MenuViewLayerTest extends SysuiTestCase { public void receiveActionDelete_dismissNotificationAndHideMenu() { ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass( BroadcastReceiver.class); - dragMenuThenReleasedInTarget(); + dragMenuThenReleasedInTarget(R.id.action_remove_menu); verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(), - any(IntentFilter.class), anyInt()); + argThat(mNotificationMatcher), anyInt()); broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_DELETE)); verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue()); @@ -423,10 +449,12 @@ public class MenuViewLayerTest extends SysuiTestCase { }); } - private void dragMenuThenReleasedInTarget() { + private void dragMenuThenReleasedInTarget(int id) { MagnetizedObject.MagnetListener magnetListener = - mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener(); + mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener(id); + View view = mock(View.class); + when(view.getId()).thenReturn(id); magnetListener.onReleasedInTarget( - new MagnetizedObject.MagneticTarget(mock(View.class), 200)); + new MagnetizedObject.MagneticTarget(view, 200)); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java index 8da6cf98d76f..7c97f53d539d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java @@ -17,15 +17,19 @@ package com.android.systemui.accessibility.floatingmenu; import static android.app.UiModeManager.MODE_NIGHT_YES; + import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; + +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.app.UiModeManager; +import android.content.Intent; import android.graphics.Rect; import android.graphics.drawable.GradientDrawable; import android.platform.test.annotations.EnableFlags; +import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.WindowManager; @@ -36,6 +40,8 @@ import androidx.test.filters.SmallTest; import com.android.systemui.Flags; import com.android.systemui.Prefs; import com.android.systemui.SysuiTestCase; +import com.android.systemui.SysuiTestableContext; +import com.android.systemui.accessibility.utils.TestUtils; import com.android.systemui.util.settings.SecureSettings; import org.junit.After; @@ -65,17 +71,23 @@ public class MenuViewTest extends SysuiTestCase { @Mock private AccessibilityManager mAccessibilityManager; + private SysuiTestableContext mSpyContext; + @Before public void setUp() throws Exception { mUiModeManager = mContext.getSystemService(UiModeManager.class); mNightMode = mUiModeManager.getNightMode(); mUiModeManager.setNightMode(MODE_NIGHT_YES); + + mSpyContext = spy(mContext); + final SecureSettings secureSettings = TestUtils.mockSecureSettings(); final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager, - mock(SecureSettings.class)); + secureSettings); final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); - mStubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager); - mMenuView = spy(new MenuView(mContext, stubMenuViewModel, mStubMenuViewAppearance)); - mLastPosition = Prefs.getString(mContext, + mStubMenuViewAppearance = new MenuViewAppearance(mSpyContext, stubWindowManager); + mMenuView = spy(new MenuView(mSpyContext, stubMenuViewModel, mStubMenuViewAppearance, + secureSettings)); + mLastPosition = Prefs.getString(mSpyContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null); } @@ -154,6 +166,25 @@ public class MenuViewTest extends SysuiTestCase { assertThat(radiiAnimator.isStarted()).isTrue(); } + @Test + public void getIntentForEditScreen_validate() { + Intent intent = mMenuView.getIntentForEditScreen(); + String[] targets = intent.getBundleExtra( + ":settings:show_fragment_args").getStringArray("targets"); + + assertThat(intent.getAction()).isEqualTo(Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS); + assertThat(targets).asList().containsExactlyElementsIn(TestUtils.TEST_BUTTON_TARGETS); + } + + @Test + @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT) + public void gotoEditScreen_sendsIntent() { + // Notably, this shouldn't crash the settings app, + // because the button target args are configured. + mMenuView.gotoEditScreen(); + verify(mSpyContext).startActivity(any()); + } + private InstantInsetLayerDrawable getMenuViewInsetLayer() { return (InstantInsetLayerDrawable) mMenuView.getBackground(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java index 10c8caa4fd27..8399fa85bfb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java @@ -16,11 +16,27 @@ package com.android.systemui.accessibility.utils; +import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; import android.os.SystemClock; +import android.os.UserHandle; +import android.provider.Settings; + +import com.android.systemui.util.settings.SecureSettings; +import java.util.Set; +import java.util.StringJoiner; import java.util.function.BooleanSupplier; public class TestUtils { + private static final ComponentName TEST_COMPONENT_A = new ComponentName("pkg", "A"); + private static final ComponentName TEST_COMPONENT_B = new ComponentName("pkg", "B"); + public static final String[] TEST_BUTTON_TARGETS = { + TEST_COMPONENT_A.flattenToString(), TEST_COMPONENT_B.flattenToString()}; public static long DEFAULT_CONDITION_DURATION = 5_000; /** @@ -55,4 +71,28 @@ public class TestUtils { SystemClock.sleep(sleepMs); } } + + /** + * Returns a mock secure settings configured to return information needed for tests. + * Currently, this only includes button targets. + */ + public static SecureSettings mockSecureSettings() { + SecureSettings secureSettings = mock(SecureSettings.class); + + final String targets = getShortcutTargets( + Set.of(TEST_COMPONENT_A, TEST_COMPONENT_B)); + when(secureSettings.getStringForUser( + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, + UserHandle.USER_CURRENT)).thenReturn(targets); + + return secureSettings; + } + + private static String getShortcutTargets(Set<ComponentName> components) { + final StringJoiner stringJoiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR)); + for (ComponentName target : components) { + stringJoiner.add(target.flattenToString()); + } + return stringJoiner.toString(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index a47e28801709..7c03d7899398 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -27,12 +27,13 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.logging.KeyguardLogger +import com.android.systemui.Flags import com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository -import com.android.systemui.log.logcatLogBuffer -import com.android.systemui.flags.FeatureFlags +import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.NotificationShadeWindowController @@ -42,7 +43,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.leak.RotationUtils import com.android.systemui.util.mockito.any -import javax.inject.Provider +import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.After import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -61,8 +62,10 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.MockitoSession import org.mockito.quality.Strictness +import javax.inject.Provider +@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidTestingRunner::class) class AuthRippleControllerTest : SysuiTestCase() { @@ -74,6 +77,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Mock private lateinit var configurationController: ConfigurationController @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var authController: AuthController + @Mock private lateinit var authRippleInteractor: AuthRippleInteractor @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle @@ -88,8 +92,6 @@ class AuthRippleControllerTest : SysuiTestCase() { @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock - private lateinit var featureFlags: FeatureFlags - @Mock private lateinit var lightRevealScrim: LightRevealScrim @Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal @@ -103,6 +105,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Before fun setUp() { + mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) MockitoAnnotations.initMocks(this) staticMockSession = mockitoSession() .mockStatic(RotationUtils::class.java) @@ -128,6 +131,7 @@ class AuthRippleControllerTest : SysuiTestCase() { KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)), biometricUnlockController, lightRevealScrim, + authRippleInteractor, facePropertyRepository, rippleView, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt index 0dfdeca60fcd..bdf0e06ce410 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.deviceentry.data.repository +package com.android.systemui.deviceentry.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -23,12 +23,13 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope @@ -158,9 +159,10 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { } private suspend fun enterDeviceFromBiometricUnlock() { - kosmos.fakeDeviceEntryRepository.enteringDeviceFromBiometricUnlock( + kosmos.fakeKeyguardRepository.setBiometricUnlockSource( BiometricUnlockSource.FINGERPRINT_SENSOR ) + kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) } private fun fingerprintFailure() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 8a3a4342915b..1183964c39d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -25,6 +25,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER; +import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR; import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION; import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT; import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE; @@ -270,8 +271,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mSceneContainerFlags, mKosmos::getCommunalInteractor); mFeatureFlags = new FakeFeatureFlags(); - mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false); mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER); + mSetFlagsRule.disableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR); DejankUtils.setImmediate(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 4f3a63dd2829..e93ad0be3e85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository @@ -29,7 +30,6 @@ import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository @@ -137,8 +137,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN) - featureFlags = FakeFeatureFlags().apply { set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) } mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) + featureFlags = FakeFeatureFlags() keyguardInteractor = createKeyguardInteractor() @@ -299,6 +299,10 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { powerInteractor = powerInteractor, ) .apply { start() } + + mSetFlagsRule.disableFlags( + FLAG_KEYGUARD_WM_STATE_REFACTOR, + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt index c864704f6997..699284e29ce3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt @@ -39,6 +39,7 @@ import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.VibratorHelper import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -71,6 +72,7 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { FakeFeatureFlagsClassic().apply { set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) } underTest = DefaultDeviceEntrySection( + TestScope().backgroundScope, keyguardUpdateMonitor, authController, windowManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt index 70a48f574949..fdbba905d94c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt @@ -27,6 +27,7 @@ import androidx.test.filters.SmallTest import com.android.internal.app.AssistUtils import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlags @@ -109,6 +110,8 @@ class OverviewProxyServiceTest : SysuiTestCase() { @Mock private lateinit var unfoldTransitionProgressForwarder: Optional<UnfoldTransitionProgressForwarder> + @Mock + private lateinit var broadcastDispatcher: BroadcastDispatcher @Before fun setUp() { @@ -131,7 +134,10 @@ class OverviewProxyServiceTest : SysuiTestCase() { whenever(packageManager.resolveServiceAsUser(any(), anyInt(), anyInt())) .thenReturn(mock(ResolveInfo::class.java)) - featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) + mSetFlagsRule.disableFlags( + com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, + ) + subject = OverviewProxyService( context, @@ -155,7 +161,8 @@ class OverviewProxyServiceTest : SysuiTestCase() { featureFlags, FakeSceneContainerFlags(), dumpManager, - unfoldTransitionProgressForwarder + unfoldTransitionProgressForwarder, + broadcastDispatcher ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 8dde9359bdfc..cb4531567e86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -182,8 +182,10 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback); mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false); - mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false); - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); + mSetFlagsRule.disableFlags( + com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, + com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR + ); when(mNotificationShadeWindowController.getWindowRootView()) .thenReturn(mNotificationShadeWindowView); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt index 6436a382eb7f..77caeaa6da4d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt @@ -16,25 +16,16 @@ package com.android.systemui.deviceentry.data.repository import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import dagger.Binds import dagger.Module import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow /** Fake implementation of [DeviceEntryRepository] */ @SysUISingleton class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository { - private val _enteringDeviceFromBiometricUnlock: MutableSharedFlow<BiometricUnlockSource> = - MutableSharedFlow() - override val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> = - _enteringDeviceFromBiometricUnlock.asSharedFlow() - private var isLockscreenEnabled = true private val _isBypassEnabled = MutableStateFlow(false) @@ -62,10 +53,6 @@ class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository { fun setBypassEnabled(isBypassEnabled: Boolean) { _isBypassEnabled.value = isBypassEnabled } - - suspend fun enteringDeviceFromBiometricUnlock(sourceType: BiometricUnlockSource) { - _enteringDeviceFromBiometricUnlock.emit(sourceType) - } } @Module diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt new file mode 100644 index 000000000000..3070cf4c06ad --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.deviceentry.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@OptIn(ExperimentalCoroutinesApi::class) +val Kosmos.authRippleInteractor by + Kosmos.Fixture { + AuthRippleInteractor( + deviceEntrySourceInteractor = deviceEntrySourceInteractor, + deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt index de58ae5e9452..878e38594fe1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt @@ -30,7 +30,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.deviceEntryHapticsInteractor by Kosmos.Fixture { DeviceEntryHapticsInteractor( - deviceEntryInteractor = deviceEntryInteractor, + deviceEntrySourceInteractor = deviceEntrySourceInteractor, deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor, fingerprintPropertyRepository = fingerprintPropertyRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt index 8dcdd3a9425c..0d1a31f9605e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.authentication.domain.interactor.authenticationInteractor @@ -28,6 +26,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.sceneContainerFlags import kotlinx.coroutines.ExperimentalCoroutinesApi +@ExperimentalCoroutinesApi val Kosmos.deviceEntryInteractor by Kosmos.Fixture { DeviceEntryInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt new file mode 100644 index 000000000000..0b9ec92af2b5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.deviceentry.domain.interactor + +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.kosmos.Kosmos +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +val Kosmos.deviceEntrySourceInteractor by + Kosmos.Fixture { + DeviceEntrySourceInteractor( + keyguardInteractor = keyguardInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 5766f7a9028c..793e2d7efcda 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -65,7 +65,7 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing private val _isKeyguardUnlocked = MutableStateFlow(false) - override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow() + override val isKeyguardDismissible: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow() private val _isKeyguardOccluded = MutableStateFlow(false) override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded @@ -165,7 +165,7 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _isKeyguardOccluded.value = isOccluded } - fun setKeyguardUnlocked(isUnlocked: Boolean) { + fun setKeyguardDismissible(isUnlocked: Boolean) { _isKeyguardUnlocked.value = isUnlocked } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt index 5ceefde32d2a..73fd9991945c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceEntrySourceInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.burnInInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor @@ -27,6 +28,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager +import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.fakeDeviceEntryIconViewModelTransition by Fixture { FakeDeviceEntryIconTransition() } @@ -34,6 +36,7 @@ val Kosmos.deviceEntryIconViewModelTransitionsMock by Fixture { setOf<DeviceEntryIconTransition>(fakeDeviceEntryIconViewModelTransition) } +@ExperimentalCoroutinesApi val Kosmos.deviceEntryIconViewModel by Fixture { DeviceEntryIconViewModel( transitions = deviceEntryIconViewModelTransitionsMock, @@ -46,5 +49,6 @@ val Kosmos.deviceEntryIconViewModel by Fixture { sceneContainerFlags = sceneContainerFlags, keyguardViewController = { statusBarKeyguardViewManager }, deviceEntryInteractor = deviceEntryInteractor, + deviceEntrySourceInteractor = deviceEntrySourceInteractor, ) } diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index 5c93991bef8c..f914ed54fbb1 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -31,9 +31,12 @@ import android.os.Handler; import android.os.ICancellationSignal; import android.os.RemoteException; import android.service.autofill.AutofillService; +import android.service.autofill.ConvertCredentialRequest; +import android.service.autofill.ConvertCredentialResponse; import android.service.autofill.FillRequest; import android.service.autofill.FillResponse; import android.service.autofill.IAutoFillService; +import android.service.autofill.IConvertCredentialCallback; import android.service.autofill.IFillCallback; import android.service.autofill.ISaveCallback; import android.service.autofill.SaveRequest; @@ -69,6 +72,7 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { private int mPendingFillRequestId = INVALID_REQUEST_ID; private AtomicReference<IFillCallback> mFillCallback; private AtomicReference<ISaveCallback> mSaveCallback; + private AtomicReference<IConvertCredentialCallback> mConvertCredentialCallback; private final ComponentName mComponentName; private final boolean mIsCredentialAutofillService; @@ -81,13 +85,20 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { extends AbstractRemoteService.VultureCallback<RemoteFillService> { void onFillRequestSuccess(int requestId, @Nullable FillResponse response, @NonNull String servicePackageName, int requestFlags); + void onFillRequestFailure(int requestId, @Nullable CharSequence message); + void onFillRequestTimeout(int requestId); + void onSaveRequestSuccess(@NonNull String servicePackageName, @Nullable IntentSender intentSender); + // TODO(b/80093094): add timeout here too? void onSaveRequestFailure(@Nullable CharSequence message, @NonNull String servicePackageName); + + void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse + convertCredentialResponse); } RemoteFillService(Context context, ComponentName componentName, int userId, @@ -211,6 +222,32 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { } } + static class IConvertCredentialCallbackDelegate extends IConvertCredentialCallback.Stub { + + private WeakReference<IConvertCredentialCallback> mCallbackWeakRef; + + IConvertCredentialCallbackDelegate(IConvertCredentialCallback callback) { + mCallbackWeakRef = new WeakReference(callback); + } + + @Override + public void onSuccess(ConvertCredentialResponse convertCredentialResponse) + throws RemoteException { + IConvertCredentialCallback callback = mCallbackWeakRef.get(); + if (callback != null) { + callback.onSuccess(convertCredentialResponse); + } + } + + @Override + public void onFailure(CharSequence message) throws RemoteException { + IConvertCredentialCallback callback = mCallbackWeakRef.get(); + if (callback != null) { + callback.onFailure(message); + } + } + } + /** * Wraps an {@link IFillCallback} object using weak reference. * @@ -237,6 +274,18 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { return callback; } + /** + * Wraps an {@link IConvertCredentialCallback} object using weak reference + */ + private IConvertCredentialCallback maybeWrapWithWeakReference( + IConvertCredentialCallback callback) { + if (remoteFillServiceUseWeakReference()) { + mConvertCredentialCallback = new AtomicReference<>(callback); + return new IConvertCredentialCallbackDelegate(callback); + } + return callback; + } + public void onFillCredentialRequest(@NonNull FillRequest request, IAutoFillManagerClient autofillCallback) { if (sVerbose) { @@ -378,6 +427,52 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> { })); } + public void onConvertCredentialRequest( + @NonNull ConvertCredentialRequest convertCredentialRequest) { + if (sVerbose) Slog.v(TAG, "calling onConvertCredentialRequest()"); + CompletableFuture<ConvertCredentialResponse> + connectThenConvertCredentialRequest = postAsync( + remoteService -> { + if (sVerbose) { + Slog.v(TAG, "calling onConvertCredentialRequest()"); + } + CompletableFuture<ConvertCredentialResponse> + convertCredentialCompletableFuture = new CompletableFuture<>(); + remoteService.onConvertCredentialRequest(convertCredentialRequest, + maybeWrapWithWeakReference( + new IConvertCredentialCallback.Stub() { + @Override + public void onSuccess(ConvertCredentialResponse + convertCredentialResponse) { + convertCredentialCompletableFuture + .complete(convertCredentialResponse); + } + + @Override + public void onFailure(CharSequence message) { + String errorMessage = + message == null ? "" : + String.valueOf(message); + convertCredentialCompletableFuture + .completeExceptionally( + new RuntimeException(errorMessage)); + } + }) + ); + return convertCredentialCompletableFuture; + }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS); + + connectThenConvertCredentialRequest.whenComplete( + (res, err) -> Handler.getMain().post(() -> { + if (err == null) { + mCallbacks.onConvertCredentialRequestSuccess(res); + } else { + // TODO: Add a callback function to log this failure + Slog.e(TAG, "Error calling on convert credential request", err); + } + })); + } + public void onSaveRequest(@NonNull SaveRequest request) { postAsync(service -> { if (sVerbose) Slog.v(TAG, "calling onSaveRequest()"); diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java index 123470304783..0af703e415ff 100644 --- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java +++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java @@ -25,6 +25,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.IntentSender; import android.os.Bundle; +import android.service.autofill.ConvertCredentialResponse; import android.service.autofill.FillRequest; import android.service.autofill.FillResponse; import android.util.Slog; @@ -98,6 +99,12 @@ final class SecondaryProviderHandler implements RemoteFillService.FillServiceCal } + @Override + public void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse + convertCredentialResponse) { + + } + /** * Requests a new fill response. */ diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index f3b74ea00a58..049feeed2fa1 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -24,6 +24,7 @@ import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_PREFERR import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_ONLY; import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC; import static android.service.autofill.Dataset.PICK_REASON_UNKNOWN; +import static android.service.autofill.FillEventHistory.Event.UI_TYPE_CREDMAN_BOTTOM_SHEET; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_MENU; @@ -44,6 +45,7 @@ import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED; import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED; import static android.view.autofill.AutofillManager.COMMIT_REASON_SESSION_DESTROYED; import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN; +import static android.view.autofill.AutofillManager.EXTRA_AUTOFILL_REQUEST_ID; import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM; import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; @@ -130,6 +132,7 @@ import android.service.assist.classification.FieldClassificationResponse; import android.service.autofill.AutofillFieldClassificationService.Scores; import android.service.autofill.AutofillService; import android.service.autofill.CompositeUserData; +import android.service.autofill.ConvertCredentialResponse; import android.service.autofill.Dataset; import android.service.autofill.Dataset.DatasetEligibleReason; import android.service.autofill.Field; @@ -2429,6 +2432,29 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState removeFromService(); } + // FillServiceCallbacks + @Override + public void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse + convertCredentialResponse) { + Dataset dataset = convertCredentialResponse.getDataset(); + Bundle clientState = convertCredentialResponse.getClientState(); + if (dataset != null) { + int requestId = -1; + if (clientState != null) { + requestId = clientState.getInt(EXTRA_AUTOFILL_REQUEST_ID); + } else { + Slog.e(TAG, "onConvertCredentialRequestSuccess(): client state is null, this " + + "would cause loss in logging."); + } + // TODO: Add autofill related logging; consider whether to log the index + fill(requestId, /* datasetIndex=*/ -1, dataset, UI_TYPE_CREDMAN_BOTTOM_SHEET); + } else { + // TODO: Add logging to log this error case + Slog.e(TAG, "onConvertCredentialRequestSuccess(): dataset inside response is " + + "null"); + } + } + /** * Gets the {@link FillContext} for a request. * diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 2e01ced2022b..5019428c5323 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -368,8 +368,10 @@ public class CompanionDeviceManagerService extends SystemService { if (blueToothDevices != null) { for (BluetoothDevice bluetoothDevice : blueToothDevices) { - final List<ParcelUuid> deviceUuids = bluetoothDevice.getUuids() == null - ? Collections.emptyList() : Arrays.asList(bluetoothDevice.getUuids()); + final ParcelUuid[] bluetoothDeviceUuids = bluetoothDevice.getUuids(); + + final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids) + ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids); for (AssociationInfo ai: mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) { diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java index 7eca1193ca12..c514f3ef29d0 100644 --- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java +++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java @@ -38,6 +38,7 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; import com.android.server.companion.AssociationStore; import com.android.server.companion.ObservableUuid; import com.android.server.companion.ObservableUuidStore; @@ -172,8 +173,10 @@ public class BluetoothCompanionDeviceConnectionListener mAssociationStore.getAssociationsByAddress(device.getAddress()); final List<ObservableUuid> observableUuids = mObservableUuidStore.getObservableUuidsForUser(userId); - final List<ParcelUuid> deviceUuids = device.getUuids() == null - ? Collections.emptyList() : Arrays.asList(device.getUuids()); + final ParcelUuid[] bluetoothDeviceUuids = device.getUuids(); + + final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids) + ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids); if (DEBUG) { Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device) diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 57c52c2cf408..45f657d713ad 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -3754,6 +3754,11 @@ final class ActivityManagerShellCommand extends ShellCommand { } @Override + public void onProcessStarted(int pid, int processUid, int packageUid, String packageName, + String processName) { + } + + @Override public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) { } diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java index 1f98aba5bbd7..fb89b8e4f3b4 100644 --- a/services/core/java/com/android/server/am/AppFGSTracker.java +++ b/services/core/java/com/android/server/am/AppFGSTracker.java @@ -102,6 +102,11 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac } @Override + public void onProcessStarted(int pid, int processUid, int packageUid, String packageName, + String processName) { + } + + @Override public void onProcessDied(int pid, int uid) { } }; diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index fa5dbd2543d3..f5c34a5da1c1 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2852,6 +2852,7 @@ public final class ProcessList { ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT); } } + dispatchProcessStarted(app, pid); checkSlow(app.getStartTime(), "startProcess: done updating pids map"); return true; } @@ -4977,6 +4978,22 @@ public final class ProcessList { } } + void dispatchProcessStarted(ProcessRecord app, int pid) { + int i = mProcessObservers.beginBroadcast(); + while (i > 0) { + i--; + final IProcessObserver observer = mProcessObservers.getBroadcastItem(i); + if (observer != null) { + try { + observer.onProcessStarted(pid, app.uid, app.info.uid, + app.info.packageName, app.processName); + } catch (RemoteException e) { + } + } + } + mProcessObservers.finishBroadcast(); + } + void dispatchProcessDied(int pid, int uid) { int i = mProcessObservers.beginBroadcast(); while (i > 0) { diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index 684d6a0fc596..cdd147a0ec37 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -177,6 +177,11 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } @Override + public void onProcessStarted(int pid, int processUid, int packageUid, String packageName, + String processName) { + } + + @Override public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) { } }; diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 21e6bac53cde..3f3540e2868c 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -20,7 +20,7 @@ package com.android.server.biometrics; // TODO(b/141025588): Create separate internal and external permissions for AuthService. // TODO(b/141025588): Get rid of the USE_FINGERPRINT permission. -import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG; +import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO; import static android.Manifest.permission.TEST_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; @@ -305,7 +305,7 @@ public class AuthService extends SystemService { if (promptInfo.containsPrivateApiConfigurations()) { checkInternalPermission(); } - if (promptInfo.containsManageBioApiConfigurations()) { + if (promptInfo.containsSetLogoApiConfigurations()) { checkManageBiometricPermission(); } @@ -997,8 +997,8 @@ public class AuthService extends SystemService { } private void checkManageBiometricPermission() { - getContext().enforceCallingOrSelfPermission(MANAGE_BIOMETRIC_DIALOG, - "Must have MANAGE_BIOMETRIC_DIALOG permission"); + getContext().enforceCallingOrSelfPermission(SET_BIOMETRIC_DIALOG_LOGO, + "Must have SET_BIOMETRIC_DIALOG_LOGO permission"); } private void checkPermission() { diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 6ec6a123a4e7..77cb08bc02bd 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -204,6 +204,10 @@ public final class DeviceStateManagerService extends SystemService { } @Override + public void onProcessStarted(int pid, int processUid, int packageUid, String packageName, + String processName) {} + + @Override public void onProcessDied(int pid, int uid) {} @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d72ca7d92894..4767ebd0aab0 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -115,7 +115,6 @@ import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; import android.util.SparseArray; -import android.util.SparseBooleanArray; import android.util.proto.ProtoOutputStream; import android.view.InputChannel; import android.view.InputDevice; @@ -281,8 +280,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @NonNull private InputMethodSettings mSettings; final SettingsObserver mSettingsObserver; - private final SparseBooleanArray mLoggedDeniedGetInputMethodWindowVisibleHeightForUid = - new SparseBooleanArray(0); final WindowManagerInternal mWindowManagerInternal; private final ActivityManagerInternal mActivityManagerInternal; final PackageManagerInternal mPackageManagerInternal; @@ -1354,13 +1351,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub clearPackageChangeState(); } - @Override - public void onUidRemoved(int uid) { - synchronized (ImfLock.class) { - mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.delete(uid); - } - } - private void clearPackageChangeState() { // No need to lock them because we access these fields only on getRegisteredHandler(). mChangedPackages.clear(); @@ -2399,16 +2389,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @StartInputReason int startInputReason, int unverifiedTargetSdkVersion, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { - String selectedMethodId = getSelectedMethodIdLocked(); - - if (!mSystemReady) { - // If the system is not yet ready, we shouldn't be running third - // party code. - return new InputBindResult( - InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY, - null, null, null, selectedMethodId, getSequenceNumberLocked(), false); - } - if (!InputMethodUtils.checkIfPackageBelongsToUid(mPackageManagerInternal, cs.mUid, editorInfo.packageName)) { Slog.e(TAG, "Rejecting this client as it reported an invalid package name." @@ -2429,6 +2409,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Potentially override the selected input method if the new display belongs to a virtual // device with a custom IME. + String selectedMethodId = getSelectedMethodIdLocked(); if (oldDisplayIdToShowIme != mDisplayIdToShowIme) { final String deviceMethodId = computeCurrentDeviceMethodIdLocked(selectedMethodId); if (deviceMethodId == null) { @@ -3673,7 +3654,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub + "specified for cross-user startInputOrWindowGainedFocus()"); } } - if (windowToken == null) { Slog.e(TAG, "windowToken cannot be null."); return InputBindResult.NULL; @@ -3685,6 +3665,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub "InputMethodManagerService#startInputOrWindowGainedFocus"); final InputBindResult result; synchronized (ImfLock.class) { + if (!mSystemReady) { + // If the system is not yet ready, we shouldn't be running third arty code. + return new InputBindResult( + InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY, + null /* method */, null /* accessibilitySessions */, null /* channel */, + getSelectedMethodIdLocked(), getSequenceNumberLocked(), + false /* isInputMethodSuppressingSpellChecker */); + } final long ident = Binder.clearCallingIdentity(); try { result = startInputOrWindowGainedFocusInternalLocked(startInputReason, @@ -4276,10 +4264,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub synchronized (ImfLock.class) { if (!canInteractWithImeLocked(callingUid, client, "getInputMethodWindowVisibleHeight", null /* statsToken */)) { - if (!mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.get(callingUid)) { - EventLog.writeEvent(0x534e4554, "204906124", callingUid, ""); - mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.put(callingUid, true); - } return 0; } // This should probably use the caller's display id, but because this is unsupported diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java index 5e38bca78a7c..2522f7b82353 100644 --- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java @@ -27,6 +27,7 @@ import static java.lang.Math.max; import android.annotation.Nullable; import android.location.Location; +import android.location.LocationRequest; import android.location.LocationResult; import android.location.provider.ProviderRequest; import android.os.SystemClock; @@ -179,6 +180,7 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation private void onThrottlingChangedLocked(boolean deliverImmediate) { long throttlingIntervalMs = INTERVAL_DISABLED; if (mDeviceStationary && mDeviceIdle && !mIncomingRequest.isLocationSettingsIgnored() + && mIncomingRequest.getQuality() != LocationRequest.QUALITY_HIGH_ACCURACY && mLastLocation != null && mLastLocation.getElapsedRealtimeAgeMillis(mDeviceStationaryRealtimeMs) <= MAX_STATIONARY_LOCATION_AGE_MS) { diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 550aed51c8e2..978f46808e3b 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -214,6 +214,11 @@ public final class MediaProjectionManagerService extends SystemService } @Override + public void onProcessStarted(int pid, int processUid, int packageUid, + String packageName, String processName) { + } + + @Override public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) { MediaProjectionManagerService.this.handleForegroundServicesChanged(pid, uid, serviceTypes); diff --git a/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS b/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS new file mode 100644 index 000000000000..baa41a55c519 --- /dev/null +++ b/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS @@ -0,0 +1,2 @@ +georgechan@google.com +wenhaowang@google.com
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS index 84324f2524fc..c8bc56ce7dcd 100644 --- a/services/core/java/com/android/server/pm/OWNERS +++ b/services/core/java/com/android/server/pm/OWNERS @@ -51,3 +51,5 @@ per-file ShortcutRequestPinProcessor.java = omakoto@google.com, yamasani@google. per-file ShortcutService.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com per-file ShortcutUser.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +# background install control service +per-file BackgroundInstall* = file:BACKGROUND_INSTALL_OWNERS
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index a97652c8e167..7e3254de2385 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -3437,7 +3437,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } str.close(); - } catch (IOException | XmlPullParserException e) { + } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) { // Remove corrupted file and retry. atomicFile.failRead(str, e); diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java index f0ff85df13d1..dd2b409c7100 100644 --- a/services/core/java/com/android/server/pm/VerifyingSession.java +++ b/services/core/java/com/android/server/pm/VerifyingSession.java @@ -357,7 +357,8 @@ final class VerifyingSession { verifierUser = UserHandle.of(mPm.mUserManager.getCurrentUserId()); } // TODO(b/300965895): Remove when inconsistencies loading classpaths from apex for - // user > 1 are fixed. + // user > 1 are fixed. Tests should cover verifiers from apex classpaths run on + // primary user, secondary user and work profile. if (pkgLite.isSdkLibrary) { verifierUser = UserHandle.SYSTEM; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d9fa01e64a68..03d55d9edfda 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -53,6 +53,7 @@ import static android.app.WindowConfiguration.activityTypeToString; import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION; import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE; import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; +import static android.content.Context.CONTEXT_RESTRICTED; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_HOME; import static android.content.Intent.CATEGORY_LAUNCHER; @@ -2231,7 +2232,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mOptInOnBackInvoked = WindowOnBackInvokedDispatcher .isOnBackInvokedCallbackEnabled(info, info.applicationInfo, - () -> ent != null ? ent.array : null, false); + () -> { + Context appContext = null; + try { + appContext = mAtmService.mContext.createPackageContextAsUser( + info.packageName, CONTEXT_RESTRICTED, + UserHandle.of(mUserId)); + appContext.setTheme(theme); + } catch (PackageManager.NameNotFoundException ignore) { + } + return appContext; + }); } /** diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 10405ec7cd88..e027eb63f1d5 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -1135,16 +1135,17 @@ class RecentTasks { if (!mFreezeTaskListReordering) { // Simple case: this is not an affiliated task, so we just move it to the // front unless overridden by the provided activity options + int indexToAdd = findIndexToAdd(task); mTasks.remove(taskIndex); - mTasks.add(0, task); + mTasks.add(indexToAdd, task); if (taskIndex != 0) { // Only notify when position changes mTaskNotificationController.notifyTaskListUpdated(); } if (DEBUG_RECENTS) { - Slog.d(TAG_RECENTS, "addRecent: moving to top " + task - + " from " + taskIndex); + Slog.d(TAG_RECENTS, "addRecent: moving " + task + " to index " + + indexToAdd + " from " + taskIndex); } } notifyTaskPersisterLocked(task, false); @@ -1231,6 +1232,37 @@ class RecentTasks { notifyTaskPersisterLocked(task, false /* flush */); } + // Looks for a new index to move the recent Task. Note that the recent Task should not be + // placed higher than another recent Task that has higher hierarchical z-ordering. + private int findIndexToAdd(Task task) { + int indexToAdd = 0; + for (int i = 0; i < mTasks.size(); i++) { + final Task otherTask = mTasks.get(i); + if (task == otherTask) { + break; + } + + if (!otherTask.isAttached()) { + // Stop searching if not attached. + break; + } + + if (otherTask.inPinnedWindowingMode()) { + // Skip pip task without increasing index since pip is always on screen. + continue; + } + + // Stop searching if the task has higher z-ordering, or increase the index and + // continue the search. + if (task.compareTo(otherTask) > 0) { + break; + } + + indexToAdd = i + 1; + } + return indexToAdd; + } + /** * Add the task to the bottom if possible. */ diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f288103bd954..519c9bb16eed 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -71,6 +71,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_STATUS_BAR; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER; @@ -484,6 +485,7 @@ import com.android.internal.widget.PasswordValidationError; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.net.module.util.ProxyUtils; +import com.android.net.thread.flags.Flags; import com.android.server.AlarmManagerInternal; import com.android.server.LocalManagerRegistry; import com.android.server.LocalServices; @@ -13339,6 +13341,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS}); USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS}); + if (Flags.threadUserRestrictionEnabled()) { + USER_RESTRICTION_PERMISSIONS.put( + UserManager.DISALLOW_THREAD_NETWORK, + new String[]{MANAGE_DEVICE_POLICY_THREAD_NETWORK}); + } USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION}); USER_RESTRICTION_PERMISSIONS.put( diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt index 24d49523b9d1..3284cf19db43 100644 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt @@ -129,7 +129,10 @@ class DevicePermissionPolicy : SchemePolicy() { val packageState = newState.externalState.packageStates[packageName] ?: return val androidPackage = packageState.androidPackage ?: return val appId = packageState.appId - val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags + // The user may happen removed due to DeletePackageHelper.removeUnusedPackagesLPw() calling + // deletePackageX() asynchronously. + val userState = newState.userStates[userId] ?: return + val devicePermissionFlags = userState.appIdDevicePermissionFlags[appId] ?: return androidPackage.requestedPermissions.forEach { permissionName -> val isRequestedByOtherPackages = anyPackageInAppId(appId) { @@ -139,7 +142,7 @@ class DevicePermissionPolicy : SchemePolicy() { if (isRequestedByOtherPackages) { return@forEach } - appIdPermissionFlags[appId]?.forEachIndexed { _, deviceId, _ -> + devicePermissionFlags.forEachIndexed { _, deviceId, _ -> setPermissionFlags(appId, deviceId, userId, permissionName, 0) } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java new file mode 100644 index 000000000000..fcf761fb6607 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.os.Process.myPid; +import static android.os.Process.myUid; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManagerInternal; +import android.app.IApplicationThread; +import android.app.IProcessObserver; +import android.app.usage.UsageStatsManagerInternal; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManagerInternal; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.util.Log; + +import androidx.test.filters.MediumTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.DropBoxManagerInternal; +import com.android.server.LocalServices; +import com.android.server.am.ActivityManagerService.Injector; +import com.android.server.appop.AppOpsService; +import com.android.server.wm.ActivityTaskManagerInternal; +import com.android.server.wm.ActivityTaskManagerService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.util.Arrays; + + +/** + * Tests to verify that process events are dispatched to process observers. + */ +@MediumTest +@SuppressWarnings("GuardedBy") +public class ProcessObserverTest { + private static final String TAG = "ProcessObserverTest"; + + private static final String PACKAGE = "com.foo"; + + @Rule + public final ApplicationExitInfoTest.ServiceThreadRule + mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule(); + + private Context mContext; + private HandlerThread mHandlerThread; + + @Mock + private AppOpsService mAppOpsService; + @Mock + private DropBoxManagerInternal mDropBoxManagerInt; + @Mock + private PackageManagerInternal mPackageManagerInt; + @Mock + private UsageStatsManagerInternal mUsageStatsManagerInt; + @Mock + private ActivityManagerInternal mActivityManagerInt; + @Mock + private ActivityTaskManagerInternal mActivityTaskManagerInt; + @Mock + private BatteryStatsService mBatteryStatsService; + + private ActivityManagerService mRealAms; + private ActivityManagerService mAms; + + private ProcessList mRealProcessList = new ProcessList(); + private ProcessList mProcessList; + + final IProcessObserver mProcessObserver = mock(IProcessObserver.Stub.class); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + + LocalServices.removeServiceForTest(DropBoxManagerInternal.class); + LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt); + + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); + + LocalServices.removeServiceForTest(ActivityManagerInternal.class); + LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt); + + LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class); + LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt); + + doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); + doReturn(true).when(mActivityTaskManagerInt).attachApplication(any()); + doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any()); + + mRealAms = new ActivityManagerService( + new TestInjector(mContext), mServiceThreadRule.getThread()); + mRealAms.mConstants.loadDeviceConfigConstants(); + mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); + mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); + mRealAms.mAtmInternal = mActivityTaskManagerInt; + mRealAms.mPackageManagerInt = mPackageManagerInt; + mRealAms.mUsageStatsService = mUsageStatsManagerInt; + mRealAms.mProcessesReady = true; + mAms = spy(mRealAms); + mRealProcessList.mService = mAms; + mProcessList = spy(mRealProcessList); + + doReturn(mProcessObserver).when(mProcessObserver).asBinder(); + mProcessList.registerProcessObserver(mProcessObserver); + + doAnswer((invocation) -> { + Log.v(TAG, "Intercepting isProcStartValidLocked() for " + + Arrays.toString(invocation.getArguments())); + return null; + }).when(mProcessList).isProcStartValidLocked(any(), anyLong()); + } + + @After + public void tearDown() throws Exception { + mHandlerThread.quit(); + } + + private class TestInjector extends Injector { + TestInjector(Context context) { + super(context); + } + + @Override + public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile, + Handler handler) { + return mAppOpsService; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return mHandlerThread.getThreadHandler(); + } + + @Override + public ProcessList getProcessList(ActivityManagerService service) { + return mRealProcessList; + } + + @Override + public BatteryStatsService getBatteryStatsService() { + return mBatteryStatsService; + } + } + + private ProcessRecord makeActiveProcessRecord(String packageName) + throws Exception { + final ApplicationInfo ai = makeApplicationInfo(packageName); + return makeActiveProcessRecord(ai); + } + + private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai) + throws Exception { + final IApplicationThread thread = mock(IApplicationThread.class); + final IBinder threadBinder = new Binder(); + doReturn(threadBinder).when(thread).asBinder(); + doAnswer((invocation) -> { + Log.v(TAG, "Intercepting bindApplication() for " + + Arrays.toString(invocation.getArguments())); + if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) { + mRealAms.finishAttachApplication(0); + } + return null; + }).when(thread).bindApplication( + any(), any(), + any(), any(), anyBoolean(), + any(), any(), + any(), any(), + any(), + any(), anyInt(), + anyBoolean(), anyBoolean(), + anyBoolean(), anyBoolean(), any(), + any(), any(), any(), + any(), any(), + any(), any(), + any(), + anyLong(), anyLong()); + final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid)); + r.setPid(myPid()); + r.setStartUid(myUid()); + r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST)); + r.makeActive(thread, mAms.mProcessStats); + doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(), + anyBoolean()); + return r; + } + + static ApplicationInfo makeApplicationInfo(String packageName) { + final ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = packageName; + ai.processName = packageName; + ai.uid = myUid(); + return ai; + } + + /** + * Verify that a process start event is dispatched to process observers. + */ + @Test + public void testNormal() throws Exception { + ProcessRecord app = startProcess(); + verify(mProcessObserver).onProcessStarted( + app.getPid(), app.uid, app.info.uid, PACKAGE, PACKAGE); + } + + private ProcessRecord startProcess() throws Exception { + final ProcessRecord app = makeActiveProcessRecord(PACKAGE); + final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE); + mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false, + /* expectedStartSeq */ 0, /* procAttached */ false); + app.getThread().bindApplication(PACKAGE, appInfo, + null, null, false, + null, + null, + null, null, + null, + null, 0, + false, false, + true, false, + null, + null, null, + null, + null, null, null, + null, null, + 0, 0); + return app; + } + + // TODO: [b/302724778] Remove manual JNI load + static { + System.loadLibrary("mockingservicestestjni"); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java index 293391f43828..c6608e61fc62 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -45,6 +45,7 @@ import static com.android.server.job.controllers.JobStatus.CONSTRAINT_WITHIN_QUO import static com.android.server.job.controllers.JobStatus.NO_EARLIEST_RUNTIME; import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -78,6 +79,7 @@ import org.mockito.quality.Strictness; import java.time.Clock; import java.time.ZoneOffset; +import java.util.Arrays; @RunWith(AndroidJUnit4.class) public class JobStatusTest { @@ -138,6 +140,35 @@ public class JobStatusTest { } @Test + public void testApplyBasicPiiFilters_email() { + assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("test@email.com")); + assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("test+plus@email.com")); + assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("t.e_st+plus-minus@email.com")); + + assertEquals("prefix:[EMAIL]", JobStatus.applyBasicPiiFilters("prefix:test@email.com")); + + assertEquals("not-an-email", JobStatus.applyBasicPiiFilters("not-an-email")); + } + + @Test + public void testApplyBasicPiiFilters_mixture() { + assertEquals("[PHONE]:[EMAIL]", + JobStatus.applyBasicPiiFilters("123-456-7890:test+plus@email.com")); + assertEquals("prefix:[PHONE]:[EMAIL]", + JobStatus.applyBasicPiiFilters("prefix:123-456-7890:test+plus@email.com")); + } + + @Test + public void testApplyBasicPiiFilters_phone() { + assertEquals("[PHONE]", JobStatus.applyBasicPiiFilters("123-456-7890")); + assertEquals("[PHONE]", JobStatus.applyBasicPiiFilters("+1-234-567-8900")); + + assertEquals("prefix:[PHONE]", JobStatus.applyBasicPiiFilters("prefix:123-456-7890")); + + assertEquals("not-a-phone-number", JobStatus.applyBasicPiiFilters("not-a-phone-number")); + } + + @Test public void testCanRunInBatterySaver_regular() { final JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")).build(); @@ -245,6 +276,42 @@ public class JobStatusTest { } @Test + public void testGetFilteredDebugTags() { + final JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .addDebugTag("test@email.com") + .addDebugTag("123-456-7890") + .addDebugTag("random") + .build(); + JobStatus job = createJobStatus(jobInfo); + String[] expected = new String[]{"[EMAIL]", "[PHONE]", "random"}; + String[] result = job.getFilteredDebugTags(); + Arrays.sort(expected); + Arrays.sort(result); + assertArrayEquals(expected, result); + } + + @Test + public void testGetFilteredTraceTag() { + JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setTraceTag("test@email.com") + .build(); + JobStatus job = createJobStatus(jobInfo); + assertEquals("[EMAIL]", job.getFilteredTraceTag()); + + jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setTraceTag("123-456-7890") + .build(); + job = createJobStatus(jobInfo); + assertEquals("[PHONE]", job.getFilteredTraceTag()); + + jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setTraceTag("random") + .build(); + job = createJobStatus(jobInfo); + assertEquals("random", job.getFilteredTraceTag()); + } + + @Test public void testIsUserVisibleJob() { JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) .setUserInitiated(false) diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java index 4eba21934a4e..efab19cc9663 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java @@ -29,6 +29,7 @@ import static org.mockito.MockitoAnnotations.initMocks; import android.content.Context; import android.location.Location; +import android.location.LocationRequest; import android.location.LocationResult; import android.location.provider.ProviderRequest; import android.platform.test.annotations.Presubmit; @@ -218,4 +219,22 @@ public class StationaryThrottlingLocationProviderTest { verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST); verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class)); } + + @Test + public void testNoThrottle_highAccuracy() { + ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis( + 50).setQuality(LocationRequest.QUALITY_HIGH_ACCURACY).build(); + + mProvider.getController().setRequest(request); + verify(mDelegate).onSetRequest(request); + + LocationResult loc = createLocationResult("test_provider", mRandom); + mDelegateProvider.reportLocation(loc); + verify(mListener, times(1)).onReportLocation(loc); + + mInjector.getDeviceStationaryHelper().setStationary(true); + mInjector.getDeviceIdleHelper().setIdle(true); + verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST); + verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class)); + } } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java index 3e4f1df0e1d4..81981e6b16ca 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java @@ -183,9 +183,8 @@ public class VirtualCameraControllerTest { private VirtualCameraConfig createVirtualCameraConfig( int width, int height, int format, int maximumFramesPerSecond, String name, int sensorOrientation, int lensFacing) { - return new VirtualCameraConfig.Builder() + return new VirtualCameraConfig.Builder(name) .addStreamConfig(width, height, format, maximumFramesPerSecond) - .setName(name) .setVirtualCameraCallback(mCallbackHandler, mVirtualCameraCallbackMock) .setSensorOrientation(sensorOrientation) .setLensFacing(lensFacing) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java index d2552718f218..e9ece5dbdcc4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; @@ -23,6 +26,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.times; import android.content.Intent; import android.platform.test.annotations.Presubmit; @@ -35,6 +39,9 @@ import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.List; + /** * Test class for {@link WindowContainerTransaction}. * @@ -45,7 +52,6 @@ import org.junit.runner.RunWith; @Presubmit @RunWith(WindowTestRunner.class) public class WindowContainerTransactionTests extends WindowTestsBase { - @Test public void testRemoveTask() { final Task rootTask = createTask(mDisplayContent); @@ -72,6 +78,123 @@ public class WindowContainerTransactionTests extends WindowTestsBase { verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(rootTask); } + @Test + public void testDesktopMode_tasksAreBroughtToFront() { + final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm); + TaskDisplayArea tda = desktopOrganizer.mDefaultTDA; + List<ActivityRecord> activityRecords = new ArrayList<>(); + int numberOfTasks = 4; + desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer, + activityRecords, numberOfTasks); + + final Task task = createTask(mDisplayContent); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + task.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + // Bring home to front of the tasks + desktopOrganizer.bringHomeToFront(); + + // Bring tasks in front of the home + WindowContainerTransaction wct = new WindowContainerTransaction(); + desktopOrganizer.bringDesktopTasksToFront(wct); + applyTransaction(wct); + + // Verify tasks are resumed and in correct z-order + verify(mRootWindowContainer, times(2)).ensureActivitiesVisible(); + for (int i = 0; i < numberOfTasks - 1; i++) { + assertTrue(tda.mChildren + .indexOf(desktopOrganizer.mTasks.get(i).getRootTask()) + < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(i + 1).getRootTask())); + } + } + + @Test + public void testDesktopMode_moveTaskToDesktop() { + final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm); + TaskDisplayArea tda = desktopOrganizer.mDefaultTDA; + List<ActivityRecord> activityRecords = new ArrayList<>(); + int numberOfTasks = 4; + desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer, + activityRecords, numberOfTasks); + + final Task task = createTask(mDisplayContent); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + task.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + // Bring home to front of the tasks + desktopOrganizer.bringHomeToFront(); + + // Bring tasks in front of the home and newly moved task to on top of them + WindowContainerTransaction wct = new WindowContainerTransaction(); + desktopOrganizer.bringDesktopTasksToFront(wct); + desktopOrganizer.addMoveToDesktopChanges(wct, task, true); + wct.setBounds(task.getTaskInfo().token, desktopOrganizer.getDefaultDesktopTaskBounds()); + applyTransaction(wct); + + // Verify tasks are resumed + verify(mRootWindowContainer, times(2)).ensureActivitiesVisible(); + + // Tasks are in correct z-order + for (int i = 0; i < numberOfTasks - 1; i++) { + assertTrue(tda.mChildren + .indexOf(desktopOrganizer.mTasks.get(i).getRootTask()) + < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(i + 1).getRootTask())); + } + // New task is on top of other tasks + assertTrue(tda.mChildren + .indexOf(desktopOrganizer.mTasks.get(3).getRootTask()) + < tda.mChildren.indexOf(task)); + + // New task is in freeform and has specified bounds + assertEquals(WINDOWING_MODE_FREEFORM, task.getWindowingMode()); + assertEquals(desktopOrganizer.getDefaultDesktopTaskBounds(), task.getBounds()); + } + + + @Test + public void testDesktopMode_moveTaskToFullscreen() { + final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm); + List<ActivityRecord> activityRecords = new ArrayList<>(); + int numberOfTasks = 4; + desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer, + activityRecords, numberOfTasks); + + Task taskToMove = desktopOrganizer.mTasks.get(numberOfTasks - 1); + + // Bring tasks in front of the home and newly moved task to on top of them + WindowContainerTransaction wct = new WindowContainerTransaction(); + desktopOrganizer.addMoveToFullscreen(wct, taskToMove, false); + applyTransaction(wct); + + // New task is in freeform + assertEquals(WINDOWING_MODE_FULLSCREEN, taskToMove.getWindowingMode()); + } + + @Test + public void testDesktopMode_moveTaskToFront() { + final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm); + TaskDisplayArea tda = desktopOrganizer.mDefaultTDA; + List<ActivityRecord> activityRecords = new ArrayList<>(); + int numberOfTasks = 5; + desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer, + activityRecords, numberOfTasks); + + // Bring task 2 on top of other tasks + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reorder(desktopOrganizer.mTasks.get(2).getTaskInfo().token, true /* onTop */); + applyTransaction(wct); + + // Tasks are in correct z-order + assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(0).getRootTask()) + < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(1).getRootTask())); + assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(1).getRootTask()) + < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(3).getRootTask())); + assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(3).getRootTask()) + < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(4).getRootTask())); + assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(4).getRootTask()) + < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(2).getRootTask())); + } + private Task createTask(int taskId) { return new Task.Builder(mAtm) .setTaskId(taskId) @@ -87,3 +210,4 @@ public class WindowContainerTransactionTests extends WindowTestsBase { } } } + diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index a0bafb64090f..be837441ef94 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -116,6 +116,7 @@ import android.window.StartingWindowRemovalInfo; import android.window.TaskFragmentOrganizer; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; import com.android.internal.policy.AttributeCache; import com.android.internal.util.ArrayUtils; @@ -1899,12 +1900,14 @@ class WindowTestsBase extends SystemServiceTestsBase { final int mDesktopModeDefaultWidthDp = 840; final int mDesktopModeDefaultHeightDp = 630; final int mDesktopDensity = 284; + final int mOverrideDensity = 285; final ActivityTaskManagerService mService; final TaskDisplayArea mDefaultTDA; List<Task> mTasks; final DisplayContent mDisplay; Rect mStableBounds; + Task mHomeTask; TestDesktopOrganizer(ActivityTaskManagerService service, DisplayContent display) { mService = service; @@ -1913,8 +1916,8 @@ class WindowTestsBase extends SystemServiceTestsBase { mService.mTaskOrganizerController.registerTaskOrganizer(this); mTasks = new ArrayList<>(); mStableBounds = display.getBounds(); + mHomeTask = mDefaultTDA.getRootHomeTask(); } - TestDesktopOrganizer(ActivityTaskManagerService service) { this(service, service.mTaskSupervisor.mRootWindowContainer.getDefaultDisplay()); } @@ -1929,8 +1932,10 @@ class WindowTestsBase extends SystemServiceTestsBase { } public Rect getDefaultDesktopTaskBounds() { - int width = (int) (mDesktopModeDefaultWidthDp * mDesktopDensity + 0.5f); - int height = (int) (mDesktopModeDefaultHeightDp * mDesktopDensity + 0.5f); + int width = (int) (mDesktopModeDefaultWidthDp + * (mOverrideDensity / mDesktopDensity) + 0.5f); + int height = (int) (mDesktopModeDefaultHeightDp + * (mOverrideDensity / mDesktopDensity) + 0.5f); Rect outBounds = new Rect(); outBounds.set(0, 0, width, height); @@ -1942,8 +1947,69 @@ class WindowTestsBase extends SystemServiceTestsBase { return outBounds; } + public void createFreeformTasksWithActivities(TestDesktopOrganizer desktopOrganizer, + List<ActivityRecord> activityRecords, int numberOfTasks) { + for (int i = 0; i < numberOfTasks; i++) { + Rect bounds = new Rect(desktopOrganizer.getDefaultDesktopTaskBounds()); + bounds.offset(20 * i, 20 * i); + desktopOrganizer.createTask(bounds); + } + + for (int i = 0; i < numberOfTasks; i++) { + activityRecords.add(new TaskBuilder(mService.mTaskSupervisor) + .setParentTask(desktopOrganizer.mTasks.get(i)) + .setCreateActivity(true) + .build() + .getTopMostActivity()); + } + + for (int i = 0; i < numberOfTasks; i++) { + activityRecords.get(i).setVisibleRequested(true); + } + + for (int i = 0; i < numberOfTasks; i++) { + assertEquals(desktopOrganizer.mTasks.get(i), activityRecords.get(i).getRootTask()); + } + } + + public void bringHomeToFront() { + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reorder(mHomeTask.getTaskInfo().token, true /* onTop */); + applyTransaction(wct); + } + + public void bringDesktopTasksToFront(WindowContainerTransaction wct) { + for (Task task: mTasks) { + wct.reorder(task.getTaskInfo().token, true /* onTop */); + } + } + + public void addMoveToDesktopChanges(WindowContainerTransaction wct, Task task, + boolean overrideDensity) { + wct.setWindowingMode(task.getTaskInfo().token, WINDOWING_MODE_FREEFORM); + wct.reorder(task.getTaskInfo().token, true /* onTop */); + if (overrideDensity) { + wct.setDensityDpi(task.getTaskInfo().token, mOverrideDensity); + } + } + + public void addMoveToFullscreen(WindowContainerTransaction wct, Task task, + boolean overrideDensity) { + wct.setWindowingMode(task.getTaskInfo().token, WINDOWING_MODE_FULLSCREEN); + wct.setBounds(task.getTaskInfo().token, new Rect()); + if (overrideDensity) { + wct.setDensityDpi(task.getTaskInfo().token, mOverrideDensity); + } + } + + private void applyTransaction(@androidx.annotation.NonNull WindowContainerTransaction wct) { + if (!wct.isEmpty()) { + mService.mWindowOrganizerController.applyTransaction(wct); + } + } } + static TestWindowToken createTestWindowToken(int type, DisplayContent dc) { return createTestWindowToken(type, dc, false /* persistOnEmpty */); } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index cbd552454642..c1ceaef64d5d 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -25,6 +25,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.Manifest; import android.annotation.BytesLong; import android.annotation.CallbackExecutor; +import android.annotation.CurrentTimeMillisLong; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.LongDef; @@ -18714,51 +18715,93 @@ public class TelephonyManager { * call diagnostic data * @hide */ - public static class EmergencyCallDiagnosticParams { + @SystemApi + @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) + public static final class EmergencyCallDiagnosticParams { + public static final class Builder { + private boolean mCollectTelecomDumpSys; + private boolean mCollectTelephonyDumpsys; + + // If this is set to a value other than -1L, then the logcat collection is enabled. + // Logcat lines with this time or greater are collected how much is collected is + // dependent on internal implementation. Time represented as milliseconds since boot. + private long mLogcatStartTimeMillis = sUnsetLogcatStartTime; + + /** + * Allows enabling of telecom dumpsys collection. + * @param collectTelecomDumpsys Determines whether telecom dumpsys should be collected. + * @return Builder instance corresponding to the configured call diagnostic params. + */ + public @NonNull Builder setTelecomDumpSysCollectionEnabled( + boolean collectTelecomDumpsys) { + mCollectTelecomDumpSys = collectTelecomDumpsys; + return this; + } + + /** + * Allows enabling of telephony dumpsys collection. + * @param collectTelephonyDumpsys Determines if telephony dumpsys should be collected. + * @return Builder instance corresponding to the configured call diagnostic params. + */ + public @NonNull Builder setTelephonyDumpSysCollectionEnabled( + boolean collectTelephonyDumpsys) { + mCollectTelephonyDumpsys = collectTelephonyDumpsys; + return this; + } + + /** + * Allows enabling of logcat (system,radio) collection. + * @param startTimeMillis Enables logcat collection as of the indicated timestamp. + * @return Builder instance corresponding to the configured call diagnostic params. + */ + public @NonNull Builder setLogcatCollectionStartTimeMillis( + @CurrentTimeMillisLong long startTimeMillis) { + mLogcatStartTimeMillis = startTimeMillis; + return this; + } - private boolean mCollectTelecomDumpSys; - private boolean mCollectTelephonyDumpsys; - private boolean mCollectLogcat; + /** + * Build the EmergencyCallDiagnosticParams from the provided Builder config. + * @return {@link EmergencyCallDiagnosticParams} instance from provided builder. + */ + public @NonNull EmergencyCallDiagnosticParams build() { + return new EmergencyCallDiagnosticParams(mCollectTelecomDumpSys, + mCollectTelephonyDumpsys, mLogcatStartTimeMillis); + } + } - //logcat lines with this time or greater are collected - //how much is collected is dependent on internal implementation. - //Time represented as milliseconds since January 1, 1970 UTC + private boolean mCollectTelecomDumpSys; + private boolean mCollectTelephonyDumpsys; + private boolean mCollectLogcat; private long mLogcatStartTimeMillis; + private static long sUnsetLogcatStartTime = -1L; - public boolean isTelecomDumpSysCollectionEnabled() { - return mCollectTelecomDumpSys; + private EmergencyCallDiagnosticParams(boolean collectTelecomDumpSys, + boolean collectTelephonyDumpsys, long logcatStartTimeMillis) { + mCollectTelecomDumpSys = collectTelecomDumpSys; + mCollectTelephonyDumpsys = collectTelephonyDumpsys; + mLogcatStartTimeMillis = logcatStartTimeMillis; + mCollectLogcat = logcatStartTimeMillis != sUnsetLogcatStartTime; } - public void setTelecomDumpSysCollection(boolean collectTelecomDumpSys) { - mCollectTelecomDumpSys = collectTelecomDumpSys; + public boolean isTelecomDumpSysCollectionEnabled() { + return mCollectTelecomDumpSys; } public boolean isTelephonyDumpSysCollectionEnabled() { return mCollectTelephonyDumpsys; } - public void setTelephonyDumpSysCollection(boolean collectTelephonyDumpsys) { - mCollectTelephonyDumpsys = collectTelephonyDumpsys; - } - public boolean isLogcatCollectionEnabled() { return mCollectLogcat; } - public long getLogcatStartTime() + public long getLogcatCollectionStartTimeMillis() { return mLogcatStartTimeMillis; } - public void setLogcatCollection(boolean collectLogcat, long startTimeMillis) { - mCollectLogcat = collectLogcat; - if(mCollectLogcat) - { - mLogcatStartTimeMillis = startTimeMillis; - } - } - @Override public String toString() { return "EmergencyCallDiagnosticParams{" + @@ -18774,11 +18817,12 @@ public class TelephonyManager { * Request telephony to persist state for debugging emergency call failures. * * @param dropboxTag Tag to use when persisting data to dropbox service. - * - * @see params Parameters controlling what is collected + * @param params Parameters controlling what is collected. * * @hide */ + @SystemApi + @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag, @NonNull EmergencyCallDiagnosticParams params) { @@ -18791,7 +18835,7 @@ public class TelephonyManager { if (telephony != null) { telephony.persistEmergencyCallDiagnosticData(dropboxTag, params.isLogcatCollectionEnabled(), - params.getLogcatStartTime(), + params.getLogcatCollectionStartTimeMillis(), params.isTelecomDumpSysCollectionEnabled(), params.isTelephonyDumpSysCollectionEnabled()); } diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java index 5aaf30a5b3a7..14230fe4c323 100644 --- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java @@ -134,7 +134,7 @@ public class EmbeddedWindowService extends Service { c.drawText("Remote", 250, 250, paint); surface.unlockCanvasAndPost(c); WindowManager wm = getSystemService(WindowManager.class); - mInputToken = wm.registerBatchedSurfaceControlInputReceiver(displayId, hostToken, + wm.registerBatchedSurfaceControlInputReceiver(displayId, hostToken, mSurfaceControl, Choreographer.getInstance(), event -> { Log.d(TAG, "onInputEvent-remote " + event); @@ -147,11 +147,9 @@ public class EmbeddedWindowService extends Service { @Override public void tearDownEmbeddedSurfaceControl() { if (mSurfaceControl != null) { - new SurfaceControl.Transaction().remove(mSurfaceControl); - } - if (mInputToken != null) { WindowManager wm = getSystemService(WindowManager.class); - wm.unregisterSurfaceControlInputReceiver(mInputToken); + wm.unregisterSurfaceControlInputReceiver(mSurfaceControl); + new SurfaceControl.Transaction().remove(mSurfaceControl).apply(); } } } diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java index e5f8f47aeecd..7330ec14011b 100644 --- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java @@ -50,10 +50,11 @@ public class SurfaceInputTestActivity extends Activity { private static final String TAG = "SurfaceInputTestActivity"; private SurfaceView mLocalSurfaceView; private SurfaceView mRemoteSurfaceView; - private IBinder mInputToken; private IAttachEmbeddedWindow mIAttachEmbeddedWindow; private SurfaceControl mParentSurfaceControl; + private SurfaceControl mLocalSurfaceControl; + private final ServiceConnection mConnection = new ServiceConnection() { // Called when the connection with the service is established public void onServiceConnected(ComponentName className, IBinder service) { @@ -112,30 +113,33 @@ public class SurfaceInputTestActivity extends Activity { @Override protected void onDestroy() { super.onDestroy(); - getWindowManager().unregisterSurfaceControlInputReceiver(mInputToken); + if (mLocalSurfaceControl != null) { + getWindowManager().unregisterSurfaceControlInputReceiver(mLocalSurfaceControl); + new SurfaceControl.Transaction().remove(mLocalSurfaceControl).apply(); + } } private void addLocalChildSurfaceControl(AttachedSurfaceControl attachedSurfaceControl) { - SurfaceControl surfaceControl = new SurfaceControl.Builder().setName("LocalSC") + mLocalSurfaceControl = new SurfaceControl.Builder().setName("LocalSC") .setBufferSize(100, 100).build(); - attachedSurfaceControl.buildReparentTransaction(surfaceControl) - .setVisibility(surfaceControl, true) - .setCrop(surfaceControl, new Rect(0, 0, 100, 100)) - .setPosition(surfaceControl, 250, 1000) - .setLayer(surfaceControl, 1).apply(); + attachedSurfaceControl.buildReparentTransaction(mLocalSurfaceControl) + .setVisibility(mLocalSurfaceControl, true) + .setCrop(mLocalSurfaceControl, new Rect(0, 0, 100, 100)) + .setPosition(mLocalSurfaceControl, 250, 1000) + .setLayer(mLocalSurfaceControl, 1).apply(); Paint paint = new Paint(); paint.setColor(Color.WHITE); paint.setTextSize(20); - Surface surface = new Surface(surfaceControl); + Surface surface = new Surface(mLocalSurfaceControl); Canvas c = surface.lockCanvas(null); c.drawColor(Color.GREEN); c.drawText("Local SC", 0, 0, paint); surface.unlockCanvasAndPost(c); WindowManager wm = getSystemService(WindowManager.class); - mInputToken = wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(), - attachedSurfaceControl.getHostToken(), surfaceControl, + wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(), + attachedSurfaceControl.getHostToken(), mLocalSurfaceControl, Choreographer.getInstance(), event -> { Log.d(TAG, "onInputEvent-sc " + event); return false; @@ -143,8 +147,6 @@ public class SurfaceInputTestActivity extends Activity { } private final SurfaceHolder.Callback mLocalSurfaceViewCallback = new SurfaceHolder.Callback() { - private IBinder mInputToken; - @Override public void surfaceCreated(@NonNull SurfaceHolder holder) { Paint paint = new Paint(); @@ -157,7 +159,7 @@ public class SurfaceInputTestActivity extends Activity { holder.unlockCanvasAndPost(c); WindowManager wm = getSystemService(WindowManager.class); - mInputToken = wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(), + wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(), mLocalSurfaceView.getHostToken(), mLocalSurfaceView.getSurfaceControl(), Choreographer.getInstance(), event -> { Log.d(TAG, "onInputEvent-local " + event); @@ -173,9 +175,8 @@ public class SurfaceInputTestActivity extends Activity { @Override public void surfaceDestroyed(@NonNull SurfaceHolder holder) { - if (mInputToken != null) { - getWindowManager().unregisterSurfaceControlInputReceiver(mInputToken); - } + getWindowManager().unregisterSurfaceControlInputReceiver( + mLocalSurfaceView.getSurfaceControl()); } }; |