diff options
122 files changed, 2416 insertions, 552 deletions
diff --git a/core/api/Android.bp b/core/api/Android.bp index 907916a125da..8d8a82b69b55 100644 --- a/core/api/Android.bp +++ b/core/api/Android.bp @@ -96,21 +96,3 @@ filegroup { name: "non-updatable-test-lint-baseline.txt", srcs: ["test-lint-baseline.txt"], } - -java_api_contribution { - name: "api-stubs-docs-non-updatable-public-stubs", - api_surface: "public", - api_file: "current.txt", - visibility: [ - "//build/orchestrator/apis", - ], -} - -java_api_contribution { - name: "frameworks-base-core-api-module-lib-stubs", - api_surface: "module-lib", - api_file: "module-lib-current.txt", - visibility: [ - "//build/orchestrator/apis", - ], -} diff --git a/core/api/system-current.txt b/core/api/system-current.txt index d18001d4d5bf..39eb6a32f176 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3388,11 +3388,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); } 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/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/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index 7ea74d375ffb..09ec933880d4 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -28,6 +28,7 @@ import android.annotation.StringRes; import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.Activity; +import android.app.PendingIntent; import android.content.Intent; import android.content.IntentSender; import android.content.pm.ParceledListSlice; @@ -116,6 +117,7 @@ public final class FillResponse implements Parcelable { private final boolean mShowFillDialogIcon; private final boolean mShowSaveDialogIcon; private final @Nullable FieldClassification[] mDetectedFieldTypes; + private final @Nullable PendingIntent mDialogPendingIntent; /** * Creates a shollow copy of the provided FillResponse. @@ -150,7 +152,8 @@ public final class FillResponse implements Parcelable { r.mServiceDisplayNameResourceId, r.mShowFillDialogIcon, r.mShowSaveDialogIcon, - r.mDetectedFieldTypes); + r.mDetectedFieldTypes, + r.mDialogPendingIntent); } private FillResponse(ParceledListSlice<Dataset> datasets, SaveInfo saveInfo, Bundle clientState, @@ -163,7 +166,7 @@ public final class FillResponse implements Parcelable { int[] cancelIds, boolean supportsInlineSuggestions, int iconResourceId, int serviceDisplayNameResourceId, boolean showFillDialogIcon, boolean showSaveDialogIcon, - FieldClassification[] detectedFieldTypes) { + FieldClassification[] detectedFieldTypes, PendingIntent dialogPendingIntent) { mDatasets = datasets; mSaveInfo = saveInfo; mClientState = clientState; @@ -190,6 +193,7 @@ public final class FillResponse implements Parcelable { mShowFillDialogIcon = showFillDialogIcon; mShowSaveDialogIcon = showSaveDialogIcon; mDetectedFieldTypes = detectedFieldTypes; + mDialogPendingIntent = dialogPendingIntent; } private FillResponse(@NonNull Builder builder) { @@ -219,6 +223,7 @@ public final class FillResponse implements Parcelable { mShowFillDialogIcon = builder.mShowFillDialogIcon; mShowSaveDialogIcon = builder.mShowSaveDialogIcon; mDetectedFieldTypes = builder.mDetectedFieldTypes; + mDialogPendingIntent = builder.mDialogPendingIntent; } /** @hide */ @@ -399,6 +404,7 @@ public final class FillResponse implements Parcelable { private boolean mShowFillDialogIcon = true; private boolean mShowSaveDialogIcon = true; private FieldClassification[] mDetectedFieldTypes; + private PendingIntent mDialogPendingIntent; /** * Adds a new {@link FieldClassification} to this response, to @@ -1079,6 +1085,24 @@ public final class FillResponse implements Parcelable { } /** + * Sets credential dialog pending intent. Framework will use the intent to launch the + * selector UI. A replacement for previous fill bottom sheet. + * + * @throws IllegalStateException if {@link #build()} was already called. + * @throws NullPointerException if {@code pendingIntent} is {@code null}. + * + * @hide + */ + @NonNull + public Builder setDialogPendingIntent(@NonNull PendingIntent pendingIntent) { + throwIfDestroyed(); + Preconditions.checkNotNull(pendingIntent, + "can't pass a null object to setDialogPendingIntent"); + mDialogPendingIntent = pendingIntent; + return this; + } + + /** * Builds a new {@link FillResponse} instance. * * @throws IllegalStateException if any of the following conditions occur: @@ -1187,6 +1211,9 @@ public final class FillResponse implements Parcelable { if (mAuthentication != null) { builder.append(", hasAuthentication"); } + if (mDialogPendingIntent != null) { + builder.append(", hasDialogPendingIntent"); + } if (mAuthenticationIds != null) { builder.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds)); } @@ -1232,6 +1259,7 @@ public final class FillResponse implements Parcelable { parcel.writeParcelable(mInlineTooltipPresentation, flags); parcel.writeParcelable(mDialogPresentation, flags); parcel.writeParcelable(mDialogHeader, flags); + parcel.writeParcelable(mDialogPendingIntent, flags); parcel.writeParcelableArray(mFillDialogTriggerIds, flags); parcel.writeParcelable(mHeader, flags); parcel.writeParcelable(mFooter, flags); @@ -1282,6 +1310,11 @@ public final class FillResponse implements Parcelable { if (dialogHeader != null) { builder.setDialogHeader(dialogHeader); } + final PendingIntent dialogPendingIntent = parcel.readParcelable(null, + PendingIntent.class); + if (dialogPendingIntent != null) { + builder.setDialogPendingIntent(dialogPendingIntent); + } final AutofillId[] triggerIds = parcel.readParcelableArray(null, AutofillId.class); if (triggerIds != null) { builder.setFillDialogTriggerIds(triggerIds); 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/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index edfbea4f51a4..1de77f6d29e7 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -58,3 +58,11 @@ flag { bug: "319808237" is_fixed_read_only: true } + +flag { + name: "camera_compat_for_freeform" + namespace: "large_screen_experiences_app_compat" + description: "Whether to apply Camera Compat treatment to fixed-orientation apps in freeform windowing mode" + bug: "314952133" + is_fixed_read_only: true +} 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/res/res/values/config.xml b/core/res/res/values/config.xml index 0d1a98785695..23c78fdf108e 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -6611,7 +6611,7 @@ </string-array> <!-- Whether or not the monitoring on the apps' background battery drain is enabled --> - <bool name="config_bg_current_drain_monitor_enabled">true</bool> + <bool name="config_bg_current_drain_monitor_enabled">false</bool> <!-- The threshold of the background current drain (in percentage) to the restricted standby bucket. 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/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/libs/hwui/Android.bp b/libs/hwui/Android.bp index b40b73c111d0..0abb6f5ed011 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -229,15 +229,6 @@ filegroup { path: "apex/java", } -java_api_contribution { - name: "framework-graphics-public-stubs", - api_surface: "public", - api_file: "api/current.txt", - visibility: [ - "//build/orchestrator/apis", - ], -} - // ------------------------ // APEX // ------------------------ diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl index e3dba03d6093..7b5853169923 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl @@ -61,7 +61,8 @@ oneway interface ITvInteractiveAppClient { void onSetTvRecordingInfo(in String recordingId, in TvRecordingInfo recordingInfo, int seq); void onRequestTvRecordingInfo(in String recordingId, int seq); void onRequestTvRecordingInfoList(in int type, int seq); - void onRequestSigning( - in String id, in String algorithm, in String alias, in byte[] data, int seq); + void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data, + int seq); + void onRequestCertificate(in String host, int port, int seq); void onAdRequest(in AdRequest request, int Seq); } diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl index 0f58b29247bb..1b9450b240ac 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl @@ -58,6 +58,8 @@ interface ITvInteractiveAppManager { void sendAvailableSpeeds(in IBinder sessionToken, in float[] speeds, int userId); void sendSigningResult(in IBinder sessionToken, in String signingId, in byte[] result, int userId); + void sendCertificate(in IBinder sessionToken, in String host, int port, + in Bundle certBundle, int userId); void sendTvRecordingInfo(in IBinder sessionToken, in TvRecordingInfo recordingInfo, int userId); void sendTvRecordingInfoList(in IBinder sessionToken, in List<TvRecordingInfo> recordingInfoList, int userId); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl index 06808c9ff915..3969315ab655 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl @@ -49,6 +49,7 @@ oneway interface ITvInteractiveAppSession { void sendTimeShiftMode(int mode); void sendAvailableSpeeds(in float[] speeds); void sendSigningResult(in String signingId, in byte[] result); + void sendCertificate(in String host, int port, in Bundle certBundle); void sendTvRecordingInfo(in TvRecordingInfo recordingInfo); void sendTvRecordingInfoList(in List<TvRecordingInfo> recordingInfoList); void notifyError(in String errMsg, in Bundle params); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl index 416b8f12d5ea..cb89181fd714 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl @@ -61,5 +61,6 @@ oneway interface ITvInteractiveAppSessionCallback { void onRequestTvRecordingInfo(in String recordingId); void onRequestTvRecordingInfoList(in int type); void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data); + void onRequestCertificate(in String host, int port); void onAdRequest(in AdRequest request); } diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java index 77730aa46d0a..ec6c2bfab576 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java @@ -104,6 +104,7 @@ public class ITvInteractiveAppSessionWrapper private static final int DO_SEND_AVAILABLE_SPEEDS = 47; private static final int DO_SEND_SELECTED_TRACK_INFO = 48; private static final int DO_NOTIFY_VIDEO_FREEZE_UPDATED = 49; + private static final int DO_SEND_CERTIFICATE = 50; private final HandlerCaller mCaller; private Session mSessionImpl; @@ -369,6 +370,13 @@ public class ITvInteractiveAppSessionWrapper mSessionImpl.notifyVideoFreezeUpdated((Boolean) msg.obj); break; } + case DO_SEND_CERTIFICATE: { + SomeArgs args = (SomeArgs) msg.obj; + mSessionImpl.sendCertificate((String) args.arg1, (Integer) args.arg2, + (Bundle) args.arg3); + args.recycle(); + break; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); break; @@ -483,6 +491,12 @@ public class ITvInteractiveAppSessionWrapper } @Override + public void sendCertificate(@NonNull String host, int port, @NonNull Bundle certBundle) { + mCaller.executeOrSendMessage( + mCaller.obtainMessageOOO(DO_SEND_CERTIFICATE, host, port, certBundle)); + } + + @Override public void notifyError(@NonNull String errMsg, @NonNull Bundle params) { mCaller.executeOrSendMessage( mCaller.obtainMessageOO(DO_NOTIFY_ERROR, errMsg, params)); diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java index 8a340f6862bb..011744f94edb 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java @@ -34,6 +34,7 @@ import android.media.tv.TvInputManager; import android.media.tv.TvRecordingInfo; import android.media.tv.TvTrackInfo; import android.net.Uri; +import android.net.http.SslCertificate; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -656,6 +657,18 @@ public final class TvInteractiveAppManager { } @Override + public void onRequestCertificate(String host, int port, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postRequestCertificate(host, port); + } + } + + @Override public void onSessionStateChanged(int state, int err, int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); @@ -1328,6 +1341,19 @@ public final class TvInteractiveAppManager { } } + void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.sendCertificate(mToken, host, port, SslCertificate.saveState(cert), + mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + void notifyError(@NonNull String errMsg, @NonNull Bundle params) { if (mToken == null) { Log.w(TAG, "The session has been already released"); @@ -2232,6 +2258,15 @@ public final class TvInteractiveAppManager { }); } + void postRequestCertificate(String host, int port) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onRequestCertificate(mSession, host, port); + } + }); + } + void postRequestTvRecordingInfo(String recordingId) { mHandler.post(new Runnable() { @Override @@ -2574,6 +2609,17 @@ public final class TvInteractiveAppManager { } /** + * This is called when the service requests a SSL certificate for client validation. + * + * @param session A {@link TvInteractiveAppService.Session} associated with this callback. + * @param host the host name of the SSL authentication server. + * @param port the port of the SSL authentication server. E.g., 443 + * @hide + */ + public void onRequestCertificate(Session session, String host, int port) { + } + + /** * This is called when {@link TvInteractiveAppService.Session#notifySessionStateChanged} is * called. * diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index 5247a0ebe6e0..054b272d820f 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -46,6 +46,7 @@ import android.media.tv.TvTrackInfo; import android.media.tv.TvView; import android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback; import android.net.Uri; +import android.net.http.SslCertificate; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; @@ -734,6 +735,17 @@ public abstract class TvInteractiveAppService extends Service { } /** + * Receives the requested Certificate + * + * @param host the host name of the SSL authentication server. + * @param port the port of the SSL authentication server. E.g., 443 + * @param cert the SSL certificate received. + * @hide + */ + public void onCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) { + } + + /** * Called when the application sends information of an error. * * @param errMsg the message of the error. @@ -1633,6 +1645,32 @@ public abstract class TvInteractiveAppService extends Service { } /** + * Requests a SSL certificate for client validation. + * + * @param host the host name of the SSL authentication server. + * @param port the port of the SSL authentication server. E.g., 443 + * @hide + */ + public void requestCertificate(@NonNull String host, int port) { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) { + Log.d(TAG, "requestCertificate"); + } + if (mSessionCallback != null) { + mSessionCallback.onRequestCertificate(host, port); + } + } catch (RemoteException e) { + Log.w(TAG, "error in requestCertificate", e); + } + } + }); + } + + /** * Sends an advertisement request to be processed by the related TV input. * * @param request The advertisement request @@ -1725,6 +1763,11 @@ public abstract class TvInteractiveAppService extends Service { onSigningResult(signingId, result); } + void sendCertificate(String host, int port, Bundle certBundle) { + SslCertificate cert = SslCertificate.restoreState(certBundle); + onCertificate(host, port, cert); + } + void notifyError(String errMsg, Bundle params) { onError(errMsg, params); } diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java index 5bb61c261ae2..3b295742c244 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java @@ -34,6 +34,7 @@ import android.media.tv.interactive.TvInteractiveAppManager.Session; import android.media.tv.interactive.TvInteractiveAppManager.Session.FinishedInputEventCallback; import android.media.tv.interactive.TvInteractiveAppManager.SessionCallback; import android.net.Uri; +import android.net.http.SslCertificate; import android.os.Bundle; import android.os.Handler; import android.util.AttributeSet; @@ -756,6 +757,22 @@ public class TvInteractiveAppView extends ViewGroup { } /** + * Send the requested SSL certificate to the TV Interactive App + * @param host the host name of the SSL authentication server. + * @param port the port of the SSL authentication server. E.g., 443 + * @param cert the SSL certificate requested + * @hide + */ + public void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) { + if (DEBUG) { + Log.d(TAG, "sendCertificate"); + } + if (mSession != null) { + mSession.sendCertificate(host, port, cert); + } + } + + /** * Notifies the corresponding {@link TvInteractiveAppService} when there is an error. * * @param errMsg the message of the error. 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/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..a9dd25bc403d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -85,8 +85,8 @@ 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; @@ -616,7 +616,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mDisplayTracker = displayTracker; mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder; - if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + if (!KeyguardWmStateRefactor.isEnabled()) { mSysuiUnlockAnimationController = sysuiUnlockAnimationController; } else { mSysuiUnlockAnimationController = inWindowLauncherUnlockAnimationManager; 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..e9f21329bfbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt @@ -131,7 +131,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, 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/companion/TEST_MAPPING b/services/companion/TEST_MAPPING index 37c47baa813b..ae6d59129adb 100644 --- a/services/companion/TEST_MAPPING +++ b/services/companion/TEST_MAPPING @@ -9,5 +9,10 @@ { "name": "CtsCompanionDeviceManagerNoCompanionServicesTestCases" } + ], + "postsubmit": [ + { + "name": "CtsCompanionDeviceManagerMultiProcessTestCases" + } ] } diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index 3b9d92dc3d02..8962bf02ff2e 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -163,7 +163,7 @@ class InputController { createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId, deviceToken, displayId, phys, () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys)); - mInputManagerInternal.setVirtualMousePointerDisplayId(displayId); + setVirtualMousePointerDisplayId(displayId); } void createTouchscreen(@NonNull String deviceName, int vendorId, int productId, @@ -235,8 +235,7 @@ class InputController { // id if there's another mouse (choose the most recent). The inputDeviceDescriptor must be // removed from the mInputDeviceDescriptors instance variable prior to this point. if (inputDeviceDescriptor.isMouse()) { - if (mInputManagerInternal.getVirtualMousePointerDisplayId() - == inputDeviceDescriptor.getDisplayId()) { + if (getVirtualMousePointerDisplayId() == inputDeviceDescriptor.getDisplayId()) { updateActivePointerDisplayIdLocked(); } } @@ -271,6 +270,7 @@ class InputController { mWindowManager.setDisplayImePolicy(displayId, policy); } + // TODO(b/293587049): Remove after pointer icon refactor is complete. @GuardedBy("mLock") private void updateActivePointerDisplayIdLocked() { InputDeviceDescriptor mostRecentlyCreatedMouse = null; @@ -285,11 +285,11 @@ class InputController { } } if (mostRecentlyCreatedMouse != null) { - mInputManagerInternal.setVirtualMousePointerDisplayId( + setVirtualMousePointerDisplayId( mostRecentlyCreatedMouse.getDisplayId()); } else { // All mice have been unregistered - mInputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY); + setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY); } } @@ -349,10 +349,8 @@ class InputController { if (inputDeviceDescriptor == null) { return false; } - if (inputDeviceDescriptor.getDisplayId() - != mInputManagerInternal.getVirtualMousePointerDisplayId()) { - mInputManagerInternal.setVirtualMousePointerDisplayId( - inputDeviceDescriptor.getDisplayId()); + if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) { + setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId()); } return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getNativePointer(), event.getButtonCode(), event.getAction(), event.getEventTimeNanos()); @@ -380,10 +378,8 @@ class InputController { if (inputDeviceDescriptor == null) { return false; } - if (inputDeviceDescriptor.getDisplayId() - != mInputManagerInternal.getVirtualMousePointerDisplayId()) { - mInputManagerInternal.setVirtualMousePointerDisplayId( - inputDeviceDescriptor.getDisplayId()); + if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) { + setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId()); } return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getNativePointer(), event.getRelativeX(), event.getRelativeY(), event.getEventTimeNanos()); @@ -397,10 +393,8 @@ class InputController { if (inputDeviceDescriptor == null) { return false; } - if (inputDeviceDescriptor.getDisplayId() - != mInputManagerInternal.getVirtualMousePointerDisplayId()) { - mInputManagerInternal.setVirtualMousePointerDisplayId( - inputDeviceDescriptor.getDisplayId()); + if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) { + setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId()); } return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getNativePointer(), event.getXAxisMovement(), event.getYAxisMovement(), event.getEventTimeNanos()); @@ -415,12 +409,11 @@ class InputController { throw new IllegalArgumentException( "Could not get cursor position for input device for given token"); } - if (inputDeviceDescriptor.getDisplayId() - != mInputManagerInternal.getVirtualMousePointerDisplayId()) { - mInputManagerInternal.setVirtualMousePointerDisplayId( - inputDeviceDescriptor.getDisplayId()); + if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) { + setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId()); } - return LocalServices.getService(InputManagerInternal.class).getCursorPosition(); + return LocalServices.getService(InputManagerInternal.class).getCursorPosition( + inputDeviceDescriptor.getDisplayId()); } } @@ -847,4 +840,22 @@ class InputController { /** Returns true if the calling thread is a valid thread for device creation. */ boolean isValidThread(); } + + // TODO(b/293587049): Remove after pointer icon refactor is complete. + private void setVirtualMousePointerDisplayId(int displayId) { + if (com.android.input.flags.Flags.enablePointerChoreographer()) { + // We no longer need to set the pointer display when pointer choreographer is enabled. + return; + } + mInputManagerInternal.setVirtualMousePointerDisplayId(displayId); + } + + // TODO(b/293587049): Remove after pointer icon refactor is complete. + private int getVirtualMousePointerDisplayId() { + if (com.android.input.flags.Flags.enablePointerChoreographer()) { + // We no longer need to get the pointer display when pointer choreographer is enabled. + return Display.INVALID_DISPLAY; + } + return mInputManagerInternal.getVirtualMousePointerDisplayId(); + } } 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/AppBatteryExemptionTracker.java b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java index b07d9a6b258c..9c2e69be7685 100644 --- a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java +++ b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java @@ -520,7 +520,7 @@ final class AppBatteryExemptionTracker /** * Default value to {@link #mTrackerEnabled}. */ - static final boolean DEFAULT_BG_BATTERY_EXEMPTION_ENABLED = true; + static final boolean DEFAULT_BG_BATTERY_EXEMPTION_ENABLED = false; AppBatteryExemptionPolicy(@NonNull Injector injector, @NonNull AppBatteryExemptionTracker tracker) { 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/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/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java index 380106ba486d..b963a4b614e8 100644 --- a/services/core/java/com/android/server/input/InputManagerInternal.java +++ b/services/core/java/com/android/server/input/InputManagerInternal.java @@ -87,12 +87,16 @@ public abstract class InputManagerInternal { * connected, the caller may be blocked for an arbitrary period of time. * * @return true if the pointer displayId was set successfully, or false if it fails. + * + * @deprecated TODO(b/293587049): Not needed - remove after Pointer Icon Refactor is complete. */ public abstract boolean setVirtualMousePointerDisplayId(int pointerDisplayId); /** * Gets the display id that the MouseCursorController is being forced to target. Returns * {@link android.view.Display#INVALID_DISPLAY} if there is no override + * + * @deprecated TODO(b/293587049): Not needed - remove after Pointer Icon Refactor is complete. */ public abstract int getVirtualMousePointerDisplayId(); @@ -101,7 +105,7 @@ public abstract class InputManagerInternal { * * Returns NaN-s as the coordinates if the cursor is not available. */ - public abstract PointF getCursorPosition(); + public abstract PointF getCursorPosition(int displayId); /** * Enables or disables pointer acceleration for mouse movements. diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 338744bfc227..687def05b1d7 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1448,6 +1448,10 @@ public class InputManagerService extends IInputManager.Stub } private boolean setVirtualMousePointerDisplayIdBlocking(int overrideDisplayId) { + if (com.android.input.flags.Flags.enablePointerChoreographer()) { + throw new IllegalStateException( + "This must not be used when PointerChoreographer is enabled"); + } final boolean isRemovingOverride = overrideDisplayId == Display.INVALID_DISPLAY; // Take care to not make calls to window manager while holding internal locks. @@ -1486,6 +1490,10 @@ public class InputManagerService extends IInputManager.Stub } private int getVirtualMousePointerDisplayId() { + if (com.android.input.flags.Flags.enablePointerChoreographer()) { + throw new IllegalStateException( + "This must not be used when PointerChoreographer is enabled"); + } synchronized (mAdditionalDisplayInputPropertiesLock) { return mOverriddenPointerDisplayId; } @@ -3332,8 +3340,8 @@ public class InputManagerService extends IInputManager.Stub } @Override - public PointF getCursorPosition() { - final float[] p = mNative.getMouseCursorPosition(); + public PointF getCursorPosition(int displayId) { + final float[] p = mNative.getMouseCursorPosition(displayId); if (p == null || p.length != 2) { throw new IllegalStateException("Failed to get mouse cursor position"); } diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index a79a1354771c..bc8207835a6e 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -233,14 +233,15 @@ interface NativeInputManagerService { void setStylusButtonMotionEventsEnabled(boolean enabled); /** - * Get the current position of the mouse cursor. + * Get the current position of the mouse cursor on the given display. * - * If the mouse cursor is not currently shown, the coordinate values will be NaN-s. + * If the mouse cursor is not currently shown, the coordinate values will be NaN-s. Use + * {@link android.view.Display#INVALID_DISPLAY} to get the position of the default mouse cursor. * * NOTE: This will grab the PointerController's lock, so we must be careful about calling this * from the InputReader or Display threads, which may result in a deadlock. */ - float[] getMouseCursorPosition(); + float[] getMouseCursorPosition(int displayId); /** Set whether showing a pointer icon for styluses is enabled. */ void setStylusPointerIconEnabled(boolean enabled); @@ -519,7 +520,7 @@ interface NativeInputManagerService { public native void setStylusButtonMotionEventsEnabled(boolean enabled); @Override - public native float[] getMouseCursorPosition(); + public native float[] getMouseCursorPosition(int displayId); @Override public native void setStylusPointerIconEnabled(boolean enabled); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d72ca7d92894..5def4288253f 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(); @@ -4276,10 +4266,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/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 2cd3ab1ddbbb..1d516e2931d7 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -287,10 +287,14 @@ public class MediaSessionService extends SystemService implements Monitor { } user.mPriorityStack.onSessionActiveStateChanged(record); } - setForegroundServiceAllowance( - record, - /* allowRunningInForeground= */ record.isActive() - && (playbackState == null || playbackState.isActive())); + boolean allowRunningInForeground = record.isActive() + && (playbackState == null || playbackState.isActive()); + + Log.d(TAG, "onSessionActiveStateChanged: " + + "record=" + record + + "playbackState=" + playbackState + + "allowRunningInForeground=" + allowRunningInForeground); + setForegroundServiceAllowance(record, allowRunningInForeground); mHandler.postSessionsChanged(record); } } @@ -388,10 +392,12 @@ public class MediaSessionService extends SystemService implements Monitor { } user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority); if (playbackState != null) { - setForegroundServiceAllowance( - record, - /* allowRunningInForeground= */ playbackState.isActive() - && record.isActive()); + boolean allowRunningInForeground = playbackState.isActive() && record.isActive(); + Log.d(TAG, "onSessionPlaybackStateChanged: " + + "record=" + record + + "playbackState=" + playbackState + + "allowRunningInForeground=" + allowRunningInForeground); + setForegroundServiceAllowance(record, allowRunningInForeground); } } } @@ -556,6 +562,8 @@ public class MediaSessionService extends SystemService implements Monitor { } session.close(); + + Log.d(TAG, "destroySessionLocked: record=" + session); setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false); mHandler.postSessionsChanged(session); } 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/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java index d7b8495929a2..b6d0ca19d484 100644 --- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java @@ -2390,6 +2390,33 @@ public class TvInteractiveAppManagerService extends SystemService { } @Override + public void sendCertificate(IBinder sessionToken, String host, int port, + Bundle certBundle, int userId) { + if (DEBUG) { + Slogf.d(TAG, "sendCertificate(host=%s port=%d cert=%s)", host, port, + certBundle); + } + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "sendCertificate"); + SessionState sessionState = null; + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).sendCertificate(host, port, certBundle); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in sendCertificate", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void notifyError(IBinder sessionToken, String errMsg, Bundle params, int userId) { if (DEBUG) { Slogf.d(TAG, "notifyError(errMsg=%s)", errMsg); @@ -4125,6 +4152,24 @@ public class TvInteractiveAppManagerService extends SystemService { } @Override + public void onRequestCertificate(String host, int port) { + synchronized (mLock) { + if (DEBUG) { + Slogf.d(TAG, "onRequestCertificate"); + } + if (mSessionState.mSession == null || mSessionState.mClient == null) { + return; + } + try { + mSessionState.mClient.onRequestCertificate(host, port, mSessionState.mSeq); + } catch (RemoteException e) { + Slogf.e(TAG, "error in onRequestCertificate", e); + } + } + } + + + @Override public void onAdRequest(AdRequest request) { synchronized (mLock) { if (DEBUG) { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 82ff36acfac3..cbc301b87295 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -306,7 +306,7 @@ public: void setMotionClassifierEnabled(bool enabled); std::optional<std::string> getBluetoothAddress(int32_t deviceId); void setStylusButtonMotionEventsEnabled(bool enabled); - FloatPoint getMouseCursorPosition(); + FloatPoint getMouseCursorPosition(int32_t displayId); void setStylusPointerIconEnabled(bool enabled); /* --- InputReaderPolicyInterface implementation --- */ @@ -1784,10 +1784,12 @@ void NativeInputManager::setStylusButtonMotionEventsEnabled(bool enabled) { InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING); } -FloatPoint NativeInputManager::getMouseCursorPosition() { +FloatPoint NativeInputManager::getMouseCursorPosition(int32_t displayId) { if (ENABLE_POINTER_CHOREOGRAPHER) { - return mInputManager->getChoreographer().getMouseCursorPosition(ADISPLAY_ID_NONE); + return mInputManager->getChoreographer().getMouseCursorPosition(displayId); } + // To maintain the status-quo, the displayId parameter (used when PointerChoreographer is + // enabled) is ignored in the old pipeline. std::scoped_lock _l(mLock); const auto pc = mLocked.legacyPointerController.lock(); if (!pc) return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION}; @@ -2751,9 +2753,10 @@ static void nativeSetStylusButtonMotionEventsEnabled(JNIEnv* env, jobject native im->setStylusButtonMotionEventsEnabled(enabled); } -static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj) { +static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj, + jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - const auto p = im->getMouseCursorPosition(); + const auto p = im->getMouseCursorPosition(displayId); const std::array<float, 2> arr = {{p.x, p.y}}; jfloatArray outArr = env->NewFloatArray(2); env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data()); @@ -2892,7 +2895,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"getBluetoothAddress", "(I)Ljava/lang/String;", (void*)nativeGetBluetoothAddress}, {"setStylusButtonMotionEventsEnabled", "(Z)V", (void*)nativeSetStylusButtonMotionEventsEnabled}, - {"getMouseCursorPosition", "()[F", (void*)nativeGetMouseCursorPosition}, + {"getMouseCursorPosition", "(I)[F", (void*)nativeGetMouseCursorPosition}, {"setStylusPointerIconEnabled", "(Z)V", (void*)nativeSetStylusPointerIconEnabled}, {"setAccessibilityBounceKeysThreshold", "(I)V", (void*)nativeSetAccessibilityBounceKeysThreshold}, diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index e3f2c530f14e..2b8bcc77281a 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -434,6 +434,9 @@ public final class SystemServer implements Dumpable { private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService"; private static final String GAME_MANAGER_SERVICE_CLASS = "com.android.server.app.GameManagerService$Lifecycle"; + private static final String ENHANCED_CONFIRMATION_SERVICE_CLASS = + "com.android.ecm.EnhancedConfirmationService"; + private static final String UWB_APEX_SERVICE_JAR_PATH = "/apex/com.android.uwb/javalib/service-uwb.jar"; private static final String UWB_SERVICE_CLASS = "com.android.server.uwb.UwbService"; @@ -1593,6 +1596,12 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(DropBoxManagerService.class); t.traceEnd(); + if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) { + t.traceBegin("StartEnhancedConfirmationService"); + mSystemServiceManager.startService(ENHANCED_CONFIRMATION_SERVICE_CLASS); + t.traceEnd(); + } + // Grants default permissions and defines roles t.traceBegin("StartRoleManagerService"); LocalManagerRegistry.addManager(RoleServicePlatformHelper.class, 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/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java index ccbbaa52ac21..5943832586b3 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java @@ -33,19 +33,21 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.view.Display; import android.view.DisplayInfo; import android.view.WindowManager; import androidx.test.InstrumentationRegistry; +import com.android.input.flags.Flags; import com.android.server.LocalServices; import com.android.server.input.InputManagerInternal; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -58,6 +60,9 @@ public class InputControllerTest { private static final String LANGUAGE_TAG = "en-US"; private static final String LAYOUT_TYPE = "qwerty"; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private InputManagerInternal mInputManagerInternalMock; @Mock @@ -72,11 +77,12 @@ public class InputControllerTest { @Before public void setUp() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER); + MockitoAnnotations.initMocks(this); mInputManagerMockHelper = new InputManagerMockHelper( TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock); - doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); LocalServices.removeServiceForTest(InputManagerInternal.class); LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); @@ -129,11 +135,7 @@ public class InputControllerTest { mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, /* displayId= */ 1); verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString()); - verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); - doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mInputController.unregisterInputDevice(deviceToken); - verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId( - eq(Display.INVALID_DISPLAY)); } @Test @@ -143,14 +145,11 @@ public class InputControllerTest { mInputController.createMouse("mouse1", /*vendorId= */ 1, /*productId= */ 1, deviceToken, /* displayId= */ 1); verify(mNativeWrapperMock).openUinputMouse(eq("mouse1"), eq(1), eq(1), anyString()); - verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); final IBinder deviceToken2 = new Binder(); mInputController.createMouse("mouse2", /*vendorId= */ 1, /*productId= */ 1, deviceToken2, /* displayId= */ 2); verify(mNativeWrapperMock).openUinputMouse(eq("mouse2"), eq(1), eq(1), anyString()); - verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2)); mInputController.unregisterInputDevice(deviceToken); - verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 9ff29d208dc0..5442af875e86 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -339,8 +339,8 @@ public class VirtualDeviceManagerServiceTest { LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); mSetFlagsRule.initAllFlagsToReleaseConfigDefault(); + mSetFlagsRule.enableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER); - doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); doNothing().when(mInputManagerInternalMock) .setMousePointerAccelerationEnabled(anyBoolean(), anyInt()); doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt()); @@ -1333,7 +1333,6 @@ public class VirtualDeviceManagerServiceTest { mInputController.addDeviceForTesting(BINDER, fd, InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); - doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); assertThat(mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() .setButtonCode(buttonCode) @@ -1363,7 +1362,6 @@ public class VirtualDeviceManagerServiceTest { mInputController.addDeviceForTesting(BINDER, fd, InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); - doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); assertThat(mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder() .setRelativeX(x) @@ -1394,7 +1392,6 @@ public class VirtualDeviceManagerServiceTest { mInputController.addDeviceForTesting(BINDER, fd, InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1, INPUT_DEVICE_ID); - doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); assertThat(mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() .setXAxisMovement(x) 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/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index 256a4696b763..566e51a9062a 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -24,6 +24,7 @@ import android.hardware.input.InputManager import android.hardware.input.InputManagerGlobal import android.os.test.TestLooper import android.platform.test.annotations.Presubmit +import android.platform.test.flag.junit.SetFlagsRule import android.provider.Settings import android.test.mock.MockContentResolver import android.view.Display @@ -72,6 +73,9 @@ class InputManagerServiceTests { @get:Rule val fakeSettingsProviderRule = FakeSettingsProvider.rule()!! + @get:Rule + val setFlagsRule = SetFlagsRule() + @Mock private lateinit var native: NativeInputManagerService @@ -170,6 +174,8 @@ class InputManagerServiceTests { @Test fun testSetVirtualMousePointerDisplayId() { + setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER) + // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked // until the native callback happens. var countDownLatch = CountDownLatch(1) @@ -221,6 +227,8 @@ class InputManagerServiceTests { @Test fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() { + setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER) + // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked // until the native callback happens. val countDownLatch = CountDownLatch(1) @@ -246,6 +254,8 @@ class InputManagerServiceTests { @Test fun testSetVirtualMousePointerDisplayId_competingRequests() { + setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER) + val firstRequestSyncLatch = CountDownLatch(1) doAnswer { firstRequestSyncLatch.countDown() @@ -289,6 +299,8 @@ class InputManagerServiceTests { @Test fun onDisplayRemoved_resetAllAdditionalInputProperties() { + setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER) + setVirtualMousePointerDisplayIdAndVerify(10) localService.setPointerIconVisible(false, 10) @@ -313,6 +325,8 @@ class InputManagerServiceTests { @Test fun updateAdditionalInputPropertiesForOverrideDisplay() { + setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER) + setVirtualMousePointerDisplayIdAndVerify(10) localService.setPointerIconVisible(false, 10) @@ -341,6 +355,8 @@ class InputManagerServiceTests { @Test fun setAdditionalInputPropertiesBeforeOverride() { + setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER) + localService.setPointerIconVisible(false, 10) localService.setMousePointerAccelerationEnabled(false, 10) |