Merge changes from topic "revert-27057737-VHSLSLTGYG" into main
* changes:
Revert "Flag off all edges leading KTF out of the Lockscreen scene"
Revert "Fix WindowManagerLockscreenVisibility"
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0ccdf37..ab5d503 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -1043,20 +1043,12 @@
name: "device_policy_aconfig_flags",
package: "android.app.admin.flags",
container: "system",
- exportable: true,
srcs: [
"core/java/android/app/admin/flags/flags.aconfig",
],
}
java_aconfig_library {
- name: "device_policy_exported_aconfig_flags_lib",
- aconfig_declarations: "device_policy_aconfig_flags",
- defaults: ["framework-minus-apex-aconfig-java-defaults"],
- mode: "exported",
-}
-
-java_aconfig_library {
name: "device_policy_aconfig_flags_lib",
aconfig_declarations: "device_policy_aconfig_flags",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 02e8eec..e680103 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -262,6 +262,7 @@
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/OsuLogin)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system_other/system/app/OsuLogin)
+$(call add-clean-step, rm -rf $(OUT_DIR)/host/linux-x86/testcases/ravenwood-runtime)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
# ******************************************************************
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 255ec92..74b34fb 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -246,12 +246,20 @@
visibility: ["//visibility:private"],
}
+java_genrule {
+ name: "z00-all-updatable-modules-system-stubs",
+ cmd: "cp $(in) $(out)",
+ srcs: [":all-updatable-modules-system-stubs"],
+ out: ["z00-all-updatable-modules-system-stubs.jar"],
+ visibility: ["//visibility:private"],
+}
+
android_ravenwood_libgroup {
name: "ravenwood-runtime",
libs: [
"100-framework-minus-apex.ravenwood",
"200-kxml2-android",
- "all-updatable-modules-system-stubs",
+
"android.test.mock.ravenwood",
"ravenwood-helper-runtime",
"hoststubgen-helper-runtime.ravenwood",
@@ -267,6 +275,9 @@
"ravenwood-junit-impl-flag",
"mockito-ravenwood-prebuilt",
"inline-mockito-ravenwood-prebuilt",
+
+ // It's a stub, so it should be towards the end.
+ "z00-all-updatable-modules-system-stubs",
],
jni_libs: [
"libandroid_runtime",
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
index a4a2e80..9b0f5c9 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
@@ -133,7 +133,7 @@
pw.println(" --tag: Tag of the blob to delete.");
pw.println("idle-maintenance");
pw.println(" Run idle maintenance which takes care of removing stale data.");
- pw.println("query-blob-existence [-b BLOB_ID]");
+ pw.println("query-blob-existence [-b BLOB_ID] [-u | --user USER_ID]");
pw.println(" Prints 1 if blob exists, otherwise 0.");
pw.println();
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 2437be8..e225c5b0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -966,7 +966,6 @@
ctor public AttributionSource(int, @Nullable String, @Nullable String);
ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder);
ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource);
- ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource);
ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
method public void enforceCallingPid();
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4ac6bac..0caea7f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -114,6 +114,7 @@
import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ContrastColorUtil;
+import com.android.internal.util.NewlineNormalizer;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -3190,7 +3191,7 @@
return charSequence;
}
- return charSequence.toString().replaceAll("[\r\n]+", "\n");
+ return NewlineNormalizer.normalizeNewlines(charSequence.toString());
}
private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
@@ -6594,6 +6595,11 @@
* @hide
*/
public RemoteViews createCompactHeadsUpContentView() {
+ // Don't show compact heads up for FSI notifications.
+ if (mN.fullScreenIntent != null) {
+ return createHeadsUpContentView(/* increasedHeight= */ false);
+ }
+
if (mStyle != null) {
final RemoteViews styleView = mStyle.makeCompactHeadsUpContentView();
if (styleView != null) {
@@ -10351,7 +10357,7 @@
@Nullable
@Override
public RemoteViews makeCompactHeadsUpContentView() {
- // TODO(b/336228700): Apply minimal HUN treatment for Call Style.
+ // Use existing heads up for call style.
return makeHeadsUpContentView(false);
}
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index efd5a45..ef8501f 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -351,6 +351,12 @@
}
/** @hide */
+ public boolean isFreeform() {
+ return configuration.windowConfiguration.getWindowingMode()
+ == WindowConfiguration.WINDOWING_MODE_FREEFORM;
+ }
+
+ /** @hide */
@WindowConfiguration.ActivityType
public int getActivityType() {
return configuration.windowConfiguration.getActivityType();
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 46c9e78..9ef8b38 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -21,7 +21,6 @@
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.app.admin.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -177,10 +176,6 @@
* provisioned into "affiliated" mode when on a Headless System User Mode device.
*
* <p>This mode adds a Profile Owner to all users other than the user the Device Owner is on.
- *
- * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
- * DPCs should set the value of attribute "headless-device-owner-mode" inside the
- * "headless-system-user" tag as "affiliated".
*/
public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1;
@@ -190,10 +185,6 @@
*
* <p>This mode only allows a single secondary user on the device blocking the creation of
* additional secondary users.
- *
- * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
- * DPCs should set the value of attribute "headless-device-owner-mode" inside the
- * "headless-system-user" tag as "single_user".
*/
@FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
@@ -392,30 +383,17 @@
}
mSupportsTransferOwnership = true;
} else if (tagName.equals("headless-system-user")) {
- String deviceOwnerModeStringValue = null;
- if (Flags.headlessSingleUserCompatibilityFix()) {
- deviceOwnerModeStringValue = parser.getAttributeValue(
- null, "headless-device-owner-mode");
- }
- if (deviceOwnerModeStringValue == null) {
- deviceOwnerModeStringValue =
- parser.getAttributeValue(null, "device-owner-mode");
- }
+ String deviceOwnerModeStringValue =
+ parser.getAttributeValue(null, "device-owner-mode");
- if ("unsupported".equalsIgnoreCase(deviceOwnerModeStringValue)) {
+ if (deviceOwnerModeStringValue.equalsIgnoreCase("unsupported")) {
mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
- } else if ("affiliated".equalsIgnoreCase(deviceOwnerModeStringValue)) {
+ } else if (deviceOwnerModeStringValue.equalsIgnoreCase("affiliated")) {
mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
- } else if ("single_user".equalsIgnoreCase(deviceOwnerModeStringValue)) {
+ } else if (deviceOwnerModeStringValue.equalsIgnoreCase("single_user")) {
mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
} else {
- if (Flags.headlessSingleUserCompatibilityFix()) {
- Log.e(TAG, "Unknown headless-system-user mode: "
- + deviceOwnerModeStringValue);
- } else {
- throw new XmlPullParserException(
- "headless-system-user mode must be valid");
- }
+ throw new XmlPullParserException("headless-system-user mode must be valid");
}
}
}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 83daa45..6da96c1 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -217,6 +217,16 @@
}
flag {
+ name: "disallow_user_control_stopped_state_fix"
+ namespace: "enterprise"
+ description: "Ensure DPM.setUserControlDisabledPackages() clears FLAG_STOPPED for the app"
+ bug: "330688482"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "esim_management_ux_enabled"
namespace: "enterprise"
description: "Enable UX changes for esim management"
@@ -303,24 +313,3 @@
purpose: PURPOSE_BUGFIX
}
}
-
-flag {
- name: "headless_single_user_compatibility_fix"
- namespace: "enterprise"
- description: "Fix for compatibility issue introduced from using single_user mode on pre-Android V builds"
- bug: "338050276"
- is_exported: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- name: "headless_single_min_target_sdk"
- namespace: "enterprise"
- description: "Only allow DPCs targeting Android V to provision into single user mode"
- bug: "338588825"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index b070742..37f419d 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -162,17 +162,6 @@
/** @hide */
@TestApi
- @FlaggedApi(Flags.FLAG_ATTRIBUTION_SOURCE_CONSTRUCTOR)
- public AttributionSource(int uid, int pid, @Nullable String packageName,
- @Nullable String attributionTag, @NonNull IBinder token,
- @Nullable String[] renouncedPermissions,
- @Nullable AttributionSource next) {
- this(uid, pid, packageName, attributionTag, token, renouncedPermissions,
- Context.DEVICE_ID_DEFAULT, next);
- }
-
- /** @hide */
- @TestApi
@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public AttributionSource(int uid, int pid, @Nullable String packageName,
@Nullable String attributionTag, @NonNull IBinder token,
@@ -473,6 +462,20 @@
}
/**
+ * @return The next package's device Id from its context.
+ * This device ID is used for permissions checking during attribution source validation.
+ *
+ * @hide
+ */
+ public int getNextDeviceId() {
+ if (mAttributionSourceState.next != null
+ && mAttributionSourceState.next.length > 0) {
+ return mAttributionSourceState.next[0].deviceId;
+ }
+ return Context.DEVICE_ID_DEFAULT;
+ }
+
+ /**
* Checks whether this attribution source can be trusted. That is whether
* the app it refers to created it and provided to the attribution chain.
*
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index ac043d3..91b05c2 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -1353,9 +1353,6 @@
/**
* Get a snapshot of the real-time status of the devices on the CEC bus.
*
- * <p>This only applies to devices with switch functionality, which are devices with one
- * or more than one HDMI inputs.
- *
* @return a list of {@link HdmiDeviceInfo} of the connected CEC devices on the CEC bus. An
* empty list will be returned if there is none.
*/
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 243ae14..8f78032 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -148,8 +148,6 @@
IInputDeviceBatteryState getBatteryState(int deviceId);
- void setPointerIconType(int typeId);
- void setCustomPointerIcon(in PointerIcon icon);
boolean setPointerIcon(in PointerIcon icon, int displayId, int deviceId, int pointerId,
in IBinder inputToken);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index dd4ea31..57004bc 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -992,21 +992,14 @@
}
/**
- * Changes the mouse pointer's icon shape into the specified id.
+ * This method exists for backwards-compatibility, and is a no-op.
*
- * @param iconId The id of the pointer graphic, as a value between
- * {@link PointerIcon#TYPE_ARROW} and {@link PointerIcon#TYPE_HANDWRITING}.
- *
+ * @deprecated
* @hide
*/
@UnsupportedAppUsage
public void setPointerIconType(int iconId) {
- mGlobal.setPointerIconType(iconId);
- }
-
- /** @hide */
- public void setCustomPointerIcon(PointerIcon icon) {
- mGlobal.setCustomPointerIcon(icon);
+ Log.e(TAG, "setPointerIcon: Unsupported app usage!");
}
/** @hide */
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index a9c97b1..cb3af2b 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1411,28 +1411,6 @@
}
/**
- * @see InputManager#setPointerIconType(int)
- */
- public void setPointerIconType(int iconId) {
- try {
- mIm.setPointerIconType(iconId);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
-
- /**
- * @see InputManager#setCustomPointerIcon(PointerIcon)
- */
- public void setCustomPointerIcon(PointerIcon icon) {
- try {
- mIm.setCustomPointerIcon(icon);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
-
- /**
* @see InputManager#setPointerIcon(PointerIcon, int, int, int, IBinder)
*/
public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId,
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index b588308..0e28560 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -44,14 +44,6 @@
}
flag {
- name: "attribution_source_constructor"
- is_exported: true
- namespace: "permissions"
- description: "enable AttributionSource(int, int, String, String, IBinder, String[], AttributionSource)"
- bug: "304478648"
-}
-
-flag {
name: "enhanced_confirmation_mode_apis_enabled"
is_exported: true
is_fixed_read_only: true
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e6ddf35..009713f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5123,13 +5123,6 @@
public static final String SCREEN_BRIGHTNESS = "screen_brightness";
/**
- * The screen backlight brightness between 0.0f and 1.0f.
- * @hide
- */
- @Readable
- public static final String SCREEN_BRIGHTNESS_FLOAT = "screen_brightness_float";
-
- /**
* Control whether to enable automatic brightness mode.
*/
@Readable
@@ -6273,7 +6266,6 @@
PUBLIC_SETTINGS.add(DIM_SCREEN);
PUBLIC_SETTINGS.add(SCREEN_OFF_TIMEOUT);
PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS);
- PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_FLOAT);
PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_MODE);
PUBLIC_SETTINGS.add(MODE_RINGER_STREAMS_AFFECTED);
PUBLIC_SETTINGS.add(MUTE_STREAMS_AFFECTED);
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 51758aa..ee5e533 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -81,8 +81,15 @@
}
flag {
- name: "report_primary_auth_attempts"
- namespace: "biometrics"
- description: "Report primary auth attempts from LockSettingsService"
- bug: "285053096"
+ name: "report_primary_auth_attempts"
+ namespace: "biometrics"
+ description: "Report primary auth attempts from LockSettingsService"
+ bug: "285053096"
+}
+
+flag {
+ name: "dump_attestation_verifications"
+ namespace: "hardware_backed_security"
+ description: "Add a dump capability for attestation_verification service"
+ bug: "335498868"
}
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index db665a9..c4becea 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -1392,10 +1392,6 @@
private static Rect computeSafeInsets(int displayW, int displayH, Insets waterFallInsets,
Rect[] bounds) {
- if (displayW == displayH) {
- throw new UnsupportedOperationException("not implemented: display=" + displayW + "x"
- + displayH + " bounding rects=" + Arrays.toString(bounds));
- }
int leftInset = Math.max(waterFallInsets.left, findCutoutInsetForSide(
displayW, displayH, bounds[BOUNDS_POSITION_LEFT], Gravity.LEFT));
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 498be2f..3743035 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -108,11 +108,6 @@
void dispatchDragEvent(in DragEvent event);
/**
- * Pointer icon events
- */
- void updatePointerIcon(float x, float y);
-
- /**
* Called for non-application windows when the enter animation has completed.
*/
void dispatchWindowShown();
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 86264eb..e3e4fc0 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -288,8 +288,6 @@
oneway void finishMovingTask(IWindow window);
- oneway void updatePointerIcon(IWindow window);
-
/**
* Update a tap exclude region identified by provided id in the window. Touches on this region
* will neither be dispatched to this window nor change the focus to this window. Passing an
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 60ad926..1cb2765 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -30695,21 +30695,11 @@
*/
public void setPointerIcon(PointerIcon pointerIcon) {
mMousePointerIcon = pointerIcon;
- if (com.android.input.flags.Flags.enablePointerChoreographer()) {
- final ViewRootImpl viewRootImpl = getViewRootImpl();
- if (viewRootImpl == null) {
- return;
- }
- viewRootImpl.refreshPointerIcon();
- } else {
- if (mAttachInfo == null || mAttachInfo.mHandlingPointerEvent) {
- return;
- }
- try {
- mAttachInfo.mSession.updatePointerIcon(mAttachInfo.mWindow);
- } catch (RemoteException e) {
- }
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl == null) {
+ return;
}
+ viewRootImpl.refreshPointerIcon();
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 42b40a1..155c053 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -74,7 +74,6 @@
import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
import static android.view.ViewRootImplProto.WIN_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -96,6 +95,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -122,7 +122,6 @@
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
-import static com.android.input.flags.Flags.enablePointerChoreographer;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.window.flags.Flags.activityWindowInfoFlag;
import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
@@ -7980,46 +7979,20 @@
if (event.isStylusPointer() && mIsStylusPointerIconEnabled) {
pointerIcon = mHandwritingInitiator.onResolvePointerIcon(mContext, event);
}
-
if (pointerIcon == null) {
pointerIcon = mView.onResolvePointerIcon(event, pointerIndex);
}
-
- if (enablePointerChoreographer()) {
- if (pointerIcon == null) {
- pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED);
- }
- if (Objects.equals(mResolvedPointerIcon, pointerIcon)) {
- return true;
- }
- mResolvedPointerIcon = pointerIcon;
-
- InputManagerGlobal.getInstance()
- .setPointerIcon(pointerIcon, event.getDisplayId(),
- event.getDeviceId(), event.getPointerId(0), getInputToken());
+ if (pointerIcon == null) {
+ pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED);
+ }
+ if (Objects.equals(mResolvedPointerIcon, pointerIcon)) {
return true;
}
+ mResolvedPointerIcon = pointerIcon;
- final int pointerType = (pointerIcon != null) ?
- pointerIcon.getType() : PointerIcon.TYPE_NOT_SPECIFIED;
-
- if (mPointerIconType == null || mPointerIconType != pointerType) {
- mPointerIconType = pointerType;
- mCustomPointerIcon = null;
- if (mPointerIconType != PointerIcon.TYPE_CUSTOM) {
- InputManagerGlobal
- .getInstance()
- .setPointerIconType(pointerType);
- return true;
- }
- }
- if (mPointerIconType == PointerIcon.TYPE_CUSTOM &&
- !pointerIcon.equals(mCustomPointerIcon)) {
- mCustomPointerIcon = pointerIcon;
- InputManagerGlobal
- .getInstance()
- .setCustomPointerIcon(mCustomPointerIcon);
- }
+ InputManagerGlobal.getInstance()
+ .setPointerIcon(pointerIcon, event.getDisplayId(),
+ event.getDeviceId(), event.getPointerId(0), getInputToken());
return true;
}
@@ -10623,16 +10596,6 @@
mHandler.sendMessage(msg);
}
- public void updatePointerIcon(float x, float y) {
- final int what = MSG_UPDATE_POINTER_ICON;
- mHandler.removeMessages(what);
- final long now = SystemClock.uptimeMillis();
- final MotionEvent event = MotionEvent.obtain(
- 0, now, MotionEvent.ACTION_HOVER_MOVE, x, y, 0);
- Message msg = mHandler.obtainMessage(what, event);
- mHandler.sendMessage(msg);
- }
-
public void dispatchCheckFocus() {
if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {
// This will result in a call to checkFocus() below.
@@ -11496,14 +11459,6 @@
}
@Override
- public void updatePointerIcon(float x, float y) {
- final ViewRootImpl viewAncestor = mViewAncestor.get();
- if (viewAncestor != null) {
- viewAncestor.updatePointerIcon(x, y);
- }
- }
-
- @Override
public void dispatchWindowShown() {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index e6367ff..d7d764b 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -603,10 +603,6 @@
}
@Override
- public void updatePointerIcon(android.view.IWindow window) {
- }
-
- @Override
public void updateTapExcludeRegion(android.view.IWindow window,
android.graphics.Region region) {
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index a073873..cf128fb 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2530,18 +2530,6 @@
view, /* delegatorPackageName= */ null, /* handwritingDelegateFlags= */ 0);
}
- private void startStylusHandwritingInternalAsync(
- @NonNull View view, @Nullable String delegatorPackageName,
- @HandwritingDelegateFlags int handwritingDelegateFlags,
- @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
- Objects.requireNonNull(view);
- Objects.requireNonNull(executor);
- Objects.requireNonNull(callback);
-
- startStylusHandwritingInternal(
- view, delegatorPackageName, handwritingDelegateFlags, executor, callback);
- }
-
private void sendFailureCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<Boolean> callback) {
if (executor == null || callback == null) {
@@ -2891,7 +2879,7 @@
if (Flags.homeScreenHandwritingDelegator()) {
flags = delegateView.getHandwritingDelegateFlags();
}
- startStylusHandwritingInternalAsync(
+ acceptStylusHandwritingDelegation(
delegateView, delegatorPackageName, flags, executor, callback);
}
@@ -2926,6 +2914,9 @@
@HandwritingDelegateFlags int flags, @NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<Boolean> callback) {
Objects.requireNonNull(delegatorPackageName);
+ Objects.requireNonNull(delegateView);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
startStylusHandwritingInternal(
delegateView, delegatorPackageName, flags, executor, callback);
diff --git a/core/java/com/android/internal/util/NewlineNormalizer.java b/core/java/com/android/internal/util/NewlineNormalizer.java
new file mode 100644
index 0000000..0104d1f
--- /dev/null
+++ b/core/java/com/android/internal/util/NewlineNormalizer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+
+import java.util.regex.Pattern;
+
+/**
+ * Utility class that replaces consecutive empty lines with single new line.
+ * @hide
+ */
+public class NewlineNormalizer {
+
+ private static final Pattern MULTIPLE_NEWLINES = Pattern.compile("\\v(\\s*\\v)?");
+
+ // Private constructor to prevent instantiation
+ private NewlineNormalizer() {}
+
+ /**
+ * Replaces consecutive newlines with a single newline in the input text.
+ */
+ public static String normalizeNewlines(String text) {
+ return MULTIPLE_NEWLINES.matcher(text).replaceAll("\n");
+ }
+}
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index c0616d0..3fc4fff 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -18,7 +18,6 @@
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
-import android.hardware.input.InputManagerGlobal;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -29,7 +28,6 @@
import android.view.IWindowSession;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.PointerIcon;
import android.view.ScrollCaptureResponse;
import android.view.WindowInsets.Type.InsetsType;
import android.view.inputmethod.ImeTracker;
@@ -128,12 +126,6 @@
}
@Override
- public void updatePointerIcon(float x, float y) {
- InputManagerGlobal.getInstance()
- .setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
- }
-
- @Override
public void dispatchWallpaperCommand(String action, int x, int y,
int z, Bundle extras, boolean sync) {
if (sync) {
diff --git a/core/jni/android_hardware_display_DisplayViewport.cpp b/core/jni/android_hardware_display_DisplayViewport.cpp
index 7f630cb..5d7b33e 100644
--- a/core/jni/android_hardware_display_DisplayViewport.cpp
+++ b/core/jni/android_hardware_display_DisplayViewport.cpp
@@ -59,7 +59,8 @@
static const jclass intClass = FindClassOrDie(env, "java/lang/Integer");
static const jmethodID byteValue = env->GetMethodID(intClass, "byteValue", "()B");
- viewport->displayId = env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId);
+ viewport->displayId = ui::LogicalDisplayId{
+ env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId)};
viewport->isActive = env->GetBooleanField(viewportObj, gDisplayViewportClassInfo.isActive);
jint orientation = env->GetIntField(viewportObj, gDisplayViewportClassInfo.orientation);
viewport->orientation = static_cast<ui::Rotation>(orientation);
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index bed7768..69f6334 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -165,8 +165,8 @@
mInfo.ownerUid = gui::Uid{
static_cast<uid_t>(env->GetIntField(obj, gInputWindowHandleClassInfo.ownerUid))};
mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
- mInfo.displayId = env->GetIntField(obj,
- gInputWindowHandleClassInfo.displayId);
+ mInfo.displayId =
+ ui::LogicalDisplayId{env->GetIntField(obj, gInputWindowHandleClassInfo.displayId)};
jobject inputApplicationHandleObj = env->GetObjectField(obj,
gInputWindowHandleClassInfo.inputApplicationHandle);
diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp
index ca8752f..06e0d2d 100644
--- a/core/jni/android_view_KeyEvent.cpp
+++ b/core/jni/android_view_KeyEvent.cpp
@@ -135,8 +135,8 @@
jlong eventTime = env->GetLongField(eventObj, gKeyEventClassInfo.mEventTime);
KeyEvent event;
- event.initialize(id, deviceId, source, displayId, *hmac, action, flags, keyCode, scanCode,
- metaState, repeatCount, downTime, eventTime);
+ event.initialize(id, deviceId, source, ui::LogicalDisplayId{displayId}, *hmac, action, flags,
+ keyCode, scanCode, metaState, repeatCount, downTime, eventTime);
return event;
}
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 3e3af40..f914bee 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -22,7 +22,6 @@
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <attestation/HmacKeyManager.h>
-#include <gui/constants.h>
#include <input/Input.h>
#include <log/log.h>
#include <nativehelper/JNIHelp.h>
@@ -367,8 +366,8 @@
ui::Transform transform;
transform.set(xOffset, yOffset);
ui::Transform identityTransform;
- event->initialize(InputEvent::nextId(), deviceId, source, displayId, INVALID_HMAC, action, 0,
- flags, edgeFlags, metaState, buttonState,
+ event->initialize(InputEvent::nextId(), deviceId, source, ui::LogicalDisplayId{displayId},
+ INVALID_HMAC, action, 0, flags, edgeFlags, metaState, buttonState,
static_cast<MotionClassification>(classification), transform, xPrecision,
yPrecision, AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, downTimeNanos,
@@ -646,13 +645,13 @@
static jint android_view_MotionEvent_nativeGetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getDisplayId();
+ return static_cast<jint>(event->getDisplayId().val());
}
static void android_view_MotionEvent_nativeSetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
jint displayId) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->setDisplayId(displayId);
+ event->setDisplayId(ui::LogicalDisplayId{displayId});
}
static jint android_view_MotionEvent_nativeGetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp
index bc69d1e6..c39d5e2 100644
--- a/core/jni/android_window_WindowInfosListener.cpp
+++ b/core/jni/android_window_WindowInfosListener.cpp
@@ -60,7 +60,7 @@
}
ScopedLocalRef<jobject> matrixObj(env, AMatrix_newInstance(env, transformValues));
return env->NewObject(gDisplayInfoClassInfo.clazz, gDisplayInfoClassInfo.ctor,
- displayInfo.displayId, displayInfo.logicalWidth,
+ displayInfo.displayId.val(), displayInfo.logicalWidth,
displayInfo.logicalHeight, matrixObj.get());
}
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 0433855..bf2fdda 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -329,7 +329,8 @@
InputDeviceInfo info = InputDeviceInfo();
info.initialize(keyboardId, 0, 0, InputDeviceIdentifier(),
- "keyboard " + std::to_string(keyboardId), true, false, 0);
+ "keyboard " + std::to_string(keyboardId), true, false,
+ ui::ADISPLAY_ID_DEFAULT);
info.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
info.setKeyCharacterMap(*charMap);
diff --git a/core/res/res/drawable-car/car_activity_resolver_list_background.xml b/core/res/res/drawable-car/car_activity_resolver_list_background.xml
new file mode 100644
index 0000000..dbbadd8
--- /dev/null
+++ b/core/res/res/drawable-car/car_activity_resolver_list_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?attr/colorBackgroundFloating" />
+ <corners android:radius="@dimen/car_activity_resolver_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/core/res/res/layout-car/car_resolver_list.xml b/core/res/res/layout-car/car_resolver_list.xml
index 755cbfe..75266b2 100644
--- a/core/res/res/layout-car/car_resolver_list.xml
+++ b/core/res/res/layout-car/car_resolver_list.xml
@@ -18,147 +18,129 @@
-->
<com.android.internal.widget.ResolverDrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_width="@dimen/car_activity_resolver_width"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
android:id="@id/contentPanel">
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:weightSum="5"
- android:layout_alwaysShow="true"
- android:orientation="vertical"
- android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp">
+ android:layout_height="wrap_content"
+ android:weightSum="2"
+ android:orientation="vertical" >
+
+ <View android:id="@+id/empty_top"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:background="@android:color/transparent" />
<LinearLayout
- android:id="@+id/button_bar"
- android:visibility="gone"
- style="?attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_ignoreOffset="true"
- android:layout_alwaysShow="true"
- android:layout_hasNestedScrollIndicator="true"
- android:background="?attr/colorBackgroundFloating"
- android:orientation="horizontal"
- android:paddingTop="8dp"
- android:paddingStart="12dp"
- android:weightSum="4"
- android:paddingEnd="12dp"
- android:elevation="8dp">
+ android:orientation="vertical"
+ android:background="@drawable/car_activity_resolver_list_background">
- <TextView
- android:id="@+id/profile_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:textSize="40sp"
- android:layout_weight="4"
- android:layout_gravity="left"
- android:visibility="gone"
- android:textColor="?attr/colorAccent"
- android:singleLine="true"/>
-
- <TextView
- android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="left"
- android:layout_weight="3"
- android:paddingTop="8dp"
- android:layout_below="@id/profile_button"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:paddingBottom="8dp"/>
-
- <Button
- android:id="@+id/button_once"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:enabled="false"
- android:layout_gravity="right"
- style="?attr/buttonBarButtonStyle"
- android:text="@string/activity_resolver_use_once"
- android:layout_weight="0.5"
- android:onClick="onButtonClick"/>
-
- <Button
- android:id="@+id/button_always"
- android:layout_marginLeft="2dp"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:enabled="false"
- android:layout_gravity="right"
- style="?attr/buttonBarButtonStyle"
- android:text="@string/activity_resolver_use_always"
- android:layout_weight="0.5"
- android:onClick="onButtonClick"/>
- </LinearLayout>
-
- <FrameLayout
- android:id="@+id/stub"
- android:visibility="gone"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="?attr/colorBackgroundFloating"/>
-
- <TabHost
- android:id="@+id/profile_tabhost"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:background="?attr/colorBackgroundFloating">
<LinearLayout
- android:orientation="vertical"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <TabWidget
- android:id="@android:id/tabs"
- android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/car_activity_resolver_list_background"
+ android:orientation="horizontal"
+ android:paddingVertical="@dimen/car_padding_4"
+ android:paddingHorizontal="@dimen/car_padding_4" >
+ <TextView
+ android:id="@+id/profile_button"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:visibility="gone">
- </TabWidget>
- <View
- android:id="@+id/resolver_tab_divider"
- android:visibility="gone"
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?attr/colorBackgroundFloating"
- android:foreground="?attr/dividerVertical"
- android:layout_marginBottom="8dp"/>
- <FrameLayout
- android:id="@android:id/tabcontent"
+ android:visibility="gone" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" />
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/stub"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <TabHost
+ android:id="@+id/profile_tabhost"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"
+ android:background="?android:attr/colorBackgroundFloating">
+ <LinearLayout
+ android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
- <com.android.internal.app.ResolverViewPager
- android:id="@+id/profile_pager"
+ <TabWidget
+ android:id="@android:id/tabs"
+ android:visibility="gone"
android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
- </FrameLayout>
+ android:layout_height="wrap_content">
+ </TabWidget>
+ <View
+ android:id="@+id/resolver_tab_divider"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ <FrameLayout
+ android:id="@android:id/tabcontent"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <com.android.internal.app.ResolverViewPager
+ android:id="@+id/profile_pager"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+ </LinearLayout>
+ </TabHost>
+
+ <LinearLayout
+ android:id="@+id/button_bar"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginVertical="@dimen/car_padding_4"
+ android:layout_marginHorizontal="@dimen/car_padding_4"
+ android:padding="0dp"
+ android:gravity="center"
+ android:background="@drawable/car_activity_resolver_list_background"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/button_once"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_button_height"
+ android:enabled="false"
+ android:layout_gravity="center"
+ android:layout_marginBottom="@dimen/car_padding_2"
+ android:text="@string/activity_resolver_use_once"
+ android:onClick="onButtonClick"/>
+
+ <Button
+ android:id="@+id/button_always"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_button_height"
+ android:enabled="false"
+ android:layout_gravity="center"
+ android:text="@string/activity_resolver_use_always"
+ android:onClick="onButtonClick"/>
</LinearLayout>
- </TabHost>
+ </LinearLayout>
- <View
- android:layout_alwaysShow="true"
+ <View android:id="@+id/empty_bottom"
android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?attr/colorBackgroundFloating"
- android:foreground="?attr/dividerVertical"/>
-
- <TextView android:id="@+id/empty"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp"
- android:layout_alwaysShow="true"
- android:text="@string/noApplications"
- android:padding="32dp"
- android:gravity="center"
- android:visibility="gone"/>
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:background="@android:color/transparent" />
</LinearLayout>
-</com.android.internal.widget.ResolverDrawerLayout>
+</com.android.internal.widget.ResolverDrawerLayout>
\ No newline at end of file
diff --git a/core/res/res/layout-car/car_resolver_list_with_default.xml b/core/res/res/layout-car/car_resolver_list_with_default.xml
index 5e450b2..0beb95b 100644
--- a/core/res/res/layout-car/car_resolver_list_with_default.xml
+++ b/core/res/res/layout-car/car_resolver_list_with_default.xml
@@ -18,179 +18,162 @@
-->
<com.android.internal.widget.ResolverDrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:maxCollapsedHeight="200dp"
+ android:layout_width="@dimen/car_activity_resolver_width"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
android:id="@id/contentPanel">
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:weightSum="5"
- android:layout_alwaysShow="true"
- android:orientation="vertical"
- android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp">
+ android:layout_height="wrap_content"
+ android:weightSum="2"
+ android:orientation="vertical" >
+
+ <View android:id="@+id/empty_top"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:background="@android:color/transparent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="0.5"
- android:orientation="horizontal">
+ android:orientation="vertical"
+ android:layout_gravity="center"
+ android:background="@drawable/car_activity_resolver_list_background">
- <ImageView
- android:id="@+id/icon"
- android:layout_width="60dp"
- android:layout_height="60dp"
- android:layout_gravity="start|top"
- android:layout_marginStart="10dp"
- android:layout_marginEnd="5dp"
- android:layout_marginTop="10dp"
- android:src="@drawable/resolver_icon_placeholder"
- android:scaleType="fitCenter"/>
+ <FrameLayout
+ android:id="@+id/stub"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/car_activity_resolver_list_background"/>
- <TextView
- android:id="@+id/title"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:layout_height="?attr/listPreferredItemHeight"
- android:layout_marginStart="16dp"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:gravity="start|center_vertical"
- android:paddingEnd="16dp"/>
<LinearLayout
- android:id="@+id/profile_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:layout_marginTop="4dp"
- android:layout_marginEnd="4dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:focusable="true"
- android:visibility="gone"
- style="?attr/borderlessButtonStyle">
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/car_activity_resolver_list_item_height"
+ android:orientation="horizontal">
+
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="false"
+ android:clickable="false"
+ android:layout_marginStart="?attr/listPreferredItemPaddingStart"
+ android:layout_gravity="start|center_vertical"
+ android:checked="true"/>
<ImageView
android:id="@+id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
+ android:layout_width="@dimen/car_icon_size"
+ android:layout_height="@dimen/car_icon_size"
android:layout_gravity="start|center_vertical"
- android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="12dp"
+ android:layout_marginStart="@dimen/car_padding_4"
+ android:src="@drawable/resolver_icon_placeholder"
android:scaleType="fitCenter"/>
<TextView
- android:id="@id/text1"
+ android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="start|center_vertical"
- android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
- android:textAppearance="?attr/textAppearanceButton"
- android:textColor="?attr/textColorPrimary"
- android:minLines="1"
- android:maxLines="1"
- android:ellipsize="marquee"/>
- </LinearLayout>
- </LinearLayout>
+ android:layout_marginHorizontal="?attr/listPreferredItemPaddingStart"
+ style="?android:attr/textAppearanceListItem"
+ android:layout_gravity="start|center_vertical" />
- <LinearLayout
- android:id="@+id/button_bar"
- android:visibility="gone"
- style="?attr/buttonBarStyle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alwaysShow="true"
- android:gravity="end|center_vertical"
- android:layout_weight="0.5"
- android:orientation="horizontal"
- android:layoutDirection="locale"
- android:measureWithLargestChild="true"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:paddingStart="12dp"
- android:paddingEnd="12dp"
- android:elevation="8dp">
-
- <Button
- android:id="@+id/button_once"
- android:layout_width="wrap_content"
- android:layout_gravity="start"
- android:maxLines="2"
- style="?attr/buttonBarButtonStyle"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- android:layout_height="wrap_content"
- android:enabled="false"
- android:text="@string/activity_resolver_use_once"
- android:onClick="onButtonClick"/>
-
- <Button
- android:id="@+id/button_always"
- android:layout_width="wrap_content"
- android:layout_gravity="end"
- android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?attr/buttonBarButtonStyle"
- android:layout_height="wrap_content"
- android:enabled="false"
- android:text="@string/activity_resolver_use_always"
- android:onClick="onButtonClick"/>
- </LinearLayout>
-
- <FrameLayout
- android:id="@+id/stub"
- android:layout_alwaysShow="true"
- android:visibility="gone"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="?attr/colorBackgroundFloating"/>
-
- <TabHost
- android:layout_alwaysShow="true"
- android:id="@+id/profile_tabhost"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:background="?attr/colorBackgroundFloating">
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <TabWidget
- android:id="@android:id/tabs"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone">
- </TabWidget>
- <View
- android:id="@+id/resolver_tab_divider"
+ <LinearLayout
+ android:id="@+id/profile_button"
android:visibility="gone"
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?attr/colorBackgroundFloating"
- android:foreground="?attr/dividerVertical"
- android:layout_marginBottom="8dp"/>
- <FrameLayout
- android:id="@android:id/tabcontent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@id/text1"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <TabHost
+ android:id="@+id/profile_tabhost"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"
+ android:background="?attr/colorBackgroundFloating">
+ <LinearLayout
+ android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
- <com.android.internal.app.ResolverViewPager
- android:id="@+id/profile_pager"
+ <TabWidget
+ android:id="@android:id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+ </TabWidget>
+ <View
+ android:id="@+id/resolver_tab_divider"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ <FrameLayout
+ android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="wrap_content">
- </com.android.internal.app.ResolverViewPager>
- </FrameLayout>
- </LinearLayout>
- </TabHost>
+ <com.android.internal.app.ResolverViewPager
+ android:id="@+id/profile_pager"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ </com.android.internal.app.ResolverViewPager>
+ </FrameLayout>
+ </LinearLayout>
+ </TabHost>
- <View
+ <LinearLayout
+ android:id="@+id/button_bar"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginVertical="@dimen/car_padding_4"
+ android:layout_marginHorizontal="@dimen/car_padding_4"
+ android:gravity="center"
+ android:background="@drawable/car_activity_resolver_list_background"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/button_once"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_button_height"
+ android:enabled="false"
+ android:layout_gravity="center"
+ android:layout_marginBottom="@dimen/car_padding_2"
+ android:text="@string/activity_resolver_use_once"
+ android:onClick="onButtonClick"/>
+
+ <Button
+ android:id="@+id/button_always"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_button_height"
+ android:enabled="false"
+ android:layout_gravity="center"
+ android:text="@string/activity_resolver_use_always"
+ android:onClick="onButtonClick"/>
+ </LinearLayout>
+ </LinearLayout>
+
+ <View android:id="@+id/empty_bottom"
android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?attr/dividerVertical"/>
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:background="@android:color/transparent" />
+
</LinearLayout>
</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/values/dimens_car.xml b/core/res/res/values/dimens_car.xml
index c5dddb8..99377ff 100644
--- a/core/res/res/values/dimens_car.xml
+++ b/core/res/res/values/dimens_car.xml
@@ -151,4 +151,10 @@
<dimen name="action_bar_button_margin">@*android:dimen/car_padding_4</dimen>
<dimen name="action_bar_button_max_width">268dp</dimen>
<dimen name="action_bar_toggle_internal_padding">@*android:dimen/car_padding_3</dimen>
+
+ <!-- Intent Resolver -->
+ <dimen name="car_activity_resolver_width">706dp</dimen>
+ <dimen name="car_activity_resolver_list_item_height">96dp</dimen>
+ <dimen name="car_activity_resolver_list_max_height">256dp</dimen>
+ <dimen name="car_activity_resolver_corner_radius">24dp</dimen>
</resources>
diff --git a/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java b/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java
new file mode 100644
index 0000000..bcdac61
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static junit.framework.Assert.assertEquals;
+
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for {@link NewlineNormalizer}
+ * @hide
+ */
+@DisabledOnRavenwood(blockedBy = NewlineNormalizer.class)
+@RunWith(AndroidJUnit4.class)
+public class NewlineNormalizerTest {
+
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ @Test
+ public void testEmptyInput() {
+ assertEquals("", NewlineNormalizer.normalizeNewlines(""));
+ }
+
+ @Test
+ public void testSingleNewline() {
+ assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n"));
+ }
+
+ @Test
+ public void testMultipleConsecutiveNewlines() {
+ assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n\n\n\n\n"));
+ }
+
+ @Test
+ public void testNewlinesWithSpacesAndTabs() {
+ String input = "Line 1\n \n \t \n\tLine 2";
+ // Adjusted expected output to include the tab character
+ String expected = "Line 1\n\tLine 2";
+ assertEquals(expected, NewlineNormalizer.normalizeNewlines(input));
+ }
+
+ @Test
+ public void testMixedNewlineCharacters() {
+ String input = "Line 1\r\nLine 2\u000BLine 3\fLine 4\u2028Line 5\u2029Line 6";
+ String expected = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6";
+ assertEquals(expected, NewlineNormalizer.normalizeNewlines(input));
+ }
+}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
deleted file mode 100644
index d1d7c14..0000000
--- a/keystore/java/android/security/KeyStore.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security;
-
-/**
- * This class provides some constants and helper methods related to Android's Keystore service.
- * This class was originally much larger, but its functionality was superseded by other classes.
- * It now just contains a few remaining pieces for which the users haven't been updated yet.
- * You may be looking for {@link java.security.KeyStore} instead.
- *
- * @hide
- */
-public class KeyStore {
-
- // Used for UID field to indicate the calling UID.
- public static final int UID_SELF = -1;
-}
diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt
index fe47503..0ac6265 100644
--- a/ktfmt_includes.txt
+++ b/ktfmt_includes.txt
@@ -5,8 +5,6 @@
-packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
-packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
-packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
-packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
-packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
-packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 3c788b1..7bceb2c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -2341,8 +2341,8 @@
showScrim(true, null /* runnable */);
updateBubbleShadows(mIsExpanded);
- updateBadges(false /* setBadgeForCollapsedStack */);
mBubbleContainer.setActiveController(mExpandedAnimationController);
+ updateBadges(false /* setBadgeForCollapsedStack */);
updateOverflowVisibility();
updatePointerPosition(false /* forIme */);
mExpandedAnimationController.expandFromStack(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 5e063a2..da414cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -389,9 +389,6 @@
public void dispatchDragEvent(DragEvent event) {}
@Override
- public void updatePointerIcon(float x, float y) {}
-
- @Override
public void dispatchWindowShown() {}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index f195f95..3ab1fad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -81,6 +81,10 @@
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+ if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) {
+ // Don't show the SCM button for freeform tasks
+ mHasSizeCompat &= !taskInfo.isFreeform();
+ }
mCameraCompatControlState =
taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState;
mCompatUIHintsState = compatUIHintsState;
@@ -136,6 +140,10 @@
final boolean prevHasSizeCompat = mHasSizeCompat;
final int prevCameraCompatControlState = mCameraCompatControlState;
mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+ if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) {
+ // Don't show the SCM button for freeform tasks
+ mHasSizeCompat &= !taskInfo.isFreeform();
+ }
mCameraCompatControlState =
taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 9bf9fa7..b41454d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -65,7 +65,7 @@
"persist.wm.debug.desktop_use_window_shadows_focused_window", false);
/**
- * Flag to indicate whether to apply shadows to windows in desktop mode.
+ * Flag to indicate whether to use rounded corners for windows in desktop mode.
*/
private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean(
"persist.wm.debug.desktop_use_rounded_corners", true);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
new file mode 100644
index 0000000..6da3741
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("DesktopModeUtils")
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.pm.ActivityInfo.isFixedOrientationLandscape
+import android.content.pm.ActivityInfo.isFixedOrientationPortrait
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
+import android.graphics.Rect
+import android.os.SystemProperties
+import android.util.Size
+import com.android.wm.shell.common.DisplayLayout
+
+
+val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float = SystemProperties
+ .getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
+
+val DESKTOP_MODE_LANDSCAPE_APP_PADDING: Int = SystemProperties
+ .getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25)
+
+
+/**
+ * Calculates the initial bounds required for an application to fill a scale of the display bounds
+ * without any letterboxing. This is done by taking into account the applications fullscreen size,
+ * aspect ratio, orientation and resizability to calculate an area this is compatible with the
+ * applications previous configuration.
+ */
+fun calculateInitialBounds(
+ displayLayout: DisplayLayout,
+ taskInfo: RunningTaskInfo,
+ scale: Float = DESKTOP_MODE_INITIAL_BOUNDS_SCALE
+): Rect {
+ val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height())
+ val appAspectRatio = calculateAspectRatio(taskInfo)
+ val idealSize = calculateIdealSize(screenBounds, scale)
+ // If no top activity exists, apps fullscreen bounds and aspect ratio cannot be calculated.
+ // Instead default to the desired initial bounds.
+ val topActivityInfo = taskInfo.topActivityInfo
+ ?: return positionInScreen(idealSize, screenBounds)
+
+ val initialSize: Size = when (taskInfo.configuration.orientation) {
+ ORIENTATION_LANDSCAPE -> {
+ if (taskInfo.isResizeable) {
+ if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
+ // Respect apps fullscreen width
+ Size(taskInfo.appCompatTaskInfo.topActivityLetterboxWidth, idealSize.height)
+ } else {
+ idealSize
+ }
+ } else {
+ maximumSizeMaintainingAspectRatio(taskInfo, idealSize,
+ appAspectRatio)
+ }
+ }
+ ORIENTATION_PORTRAIT -> {
+ val customPortraitWidthForLandscapeApp = screenBounds.width() -
+ (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2)
+ if (taskInfo.isResizeable) {
+ if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
+ // Respect apps fullscreen height and apply custom app width
+ Size(customPortraitWidthForLandscapeApp,
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight)
+ } else {
+ idealSize
+ }
+ } else {
+ if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
+ // Apply custom app width and calculate maximum size
+ maximumSizeMaintainingAspectRatio(
+ taskInfo,
+ Size(customPortraitWidthForLandscapeApp, idealSize.height),
+ appAspectRatio)
+ } else {
+ maximumSizeMaintainingAspectRatio(taskInfo, idealSize,
+ appAspectRatio)
+ }
+ }
+ }
+ else -> {
+ idealSize
+ }
+ }
+
+ return positionInScreen(initialSize, screenBounds)
+}
+
+/**
+ * Calculates the largest size that can fit in a given area while maintaining a specific aspect
+ * ratio.
+ */
+private fun maximumSizeMaintainingAspectRatio(
+ taskInfo: RunningTaskInfo,
+ targetArea: Size,
+ aspectRatio: Float
+): Size {
+ val targetHeight = targetArea.height
+ val targetWidth = targetArea.width
+ val finalHeight: Int
+ val finalWidth: Int
+ if (isFixedOrientationPortrait(taskInfo.topActivityInfo!!.screenOrientation)) {
+ val tempWidth = (targetHeight / aspectRatio).toInt()
+ if (tempWidth <= targetWidth) {
+ finalHeight = targetHeight
+ finalWidth = tempWidth
+ } else {
+ finalWidth = targetWidth
+ finalHeight = (finalWidth * aspectRatio).toInt()
+ }
+ } else {
+ val tempWidth = (targetHeight * aspectRatio).toInt()
+ if (tempWidth <= targetWidth) {
+ finalHeight = targetHeight
+ finalWidth = tempWidth
+ } else {
+ finalWidth = targetWidth
+ finalHeight = (finalWidth / aspectRatio).toInt()
+ }
+ }
+ return Size(finalWidth, finalHeight)
+}
+
+/**
+ * Calculates the aspect ratio of an activity from its fullscreen bounds.
+ */
+private fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
+ if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
+ val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth
+ val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight
+ return maxOf(appLetterboxWidth, appLetterboxHeight) /
+ minOf(appLetterboxWidth, appLetterboxHeight).toFloat()
+ }
+ val appBounds = taskInfo.configuration.windowConfiguration.appBounds ?: return 1f
+ return maxOf(appBounds.height(), appBounds.width()) /
+ minOf(appBounds.height(), appBounds.width()).toFloat()
+}
+
+/**
+ * Calculates the desired initial bounds for applications in desktop windowing. This is done as a
+ * scale of the screen bounds.
+ */
+private fun calculateIdealSize(screenBounds: Rect, scale: Float): Size {
+ val width = (screenBounds.width() * scale).toInt()
+ val height = (screenBounds.height() * scale).toInt()
+ return Size(width, height)
+}
+
+/**
+ * Adjusts bounds to be positioned in the middle of the screen.
+ */
+private fun positionInScreen(desiredSize: Size, screenBounds: Rect): Rect {
+ // TODO(b/325240051): Position apps with bottom heavy offset
+ val heightOffset = (screenBounds.height() - desiredSize.height) / 2
+ val widthOffset = (screenBounds.width() - desiredSize.width) / 2
+ return Rect(widthOffset, heightOffset,
+ desiredSize.width + widthOffset, desiredSize.height + heightOffset)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index f7bfb86..b0d5923 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -47,6 +47,7 @@
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
+import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -85,7 +86,6 @@
import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
-import com.android.wm.shell.windowdecor.extension.isFreeform
import com.android.wm.shell.windowdecor.extension.isFullscreen
import java.io.PrintWriter
import java.util.Optional
@@ -203,6 +203,11 @@
dragAndDropController.addListener(this)
}
+ @VisibleForTesting
+ fun getVisualIndicator(): DesktopModeVisualIndicator? {
+ return visualIndicator
+ }
+
fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
@@ -605,8 +610,9 @@
}
/**
- * Quick-resizes a desktop task, toggling between the stable bounds and the last saved bounds
- * if available or the default bounds otherwise.
+ * Quick-resizes a desktop task, toggling between a fullscreen state (represented by the
+ * stable bounds) and a free floating state (either the last saved bounds if available or the
+ * default bounds otherwise).
*/
fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
@@ -623,7 +629,11 @@
if (taskBoundsBeforeMaximize != null) {
destinationBounds.set(taskBoundsBeforeMaximize)
} else {
- destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
+ if (Flags.enableWindowingDynamicInitialBounds()){
+ destinationBounds.set(calculateInitialBounds(displayLayout, taskInfo))
+ } else {
+ destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
+ }
}
} else {
// Save current bounds so that task can be restored back to original bounds if necessary
@@ -1011,6 +1021,7 @@
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
) {
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
@@ -1019,6 +1030,9 @@
} else {
WINDOWING_MODE_FREEFORM
}
+ if (Flags.enableWindowingDynamicInitialBounds()) {
+ wct.setBounds(taskInfo.token, calculateInitialBounds(displayLayout, taskInfo))
+ }
wct.setWindowingMode(taskInfo.token, targetWindowingMode)
wct.reorder(taskInfo.token, true /* onTop */)
if (isDesktopDensityOverrideSet()) {
@@ -1239,13 +1253,17 @@
* @param y height of drag, to be checked against status bar height.
*/
fun onDragPositioningEndThroughStatusBar(inputCoordinates: PointF, taskInfo: RunningTaskInfo) {
- val indicator = visualIndicator ?: return
+ val indicator = getVisualIndicator() ?: return
val indicatorType = indicator
.updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
when (indicatorType) {
DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
- finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
+ if (Flags.enableWindowingDynamicInitialBounds()) {
+ finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo))
+ } else {
+ finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
+ }
}
DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index ad29d15..19af3d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -52,7 +52,7 @@
WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
- Consts.TAG_WM_SHELL),
+ Consts.TAG_WM_DESKTOP_MODE),
WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
@@ -120,6 +120,7 @@
private static final String TAG_WM_SHELL = "WindowManagerShell";
private static final String TAG_WM_STARTING_WINDOW = "ShellStartingWindow";
private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen";
+ private static final String TAG_WM_DESKTOP_MODE = "ShellDesktopMode";
private static final boolean ENABLE_DEBUG = true;
private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 4d02ec2..968b27b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -426,7 +426,8 @@
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Converting mixed transition into a keyguard transition");
// Consume the original mixed transition
- onTransitionConsumed(transition, false, null);
+ mActiveTransitions.remove(mixed);
+ mixed.onTransitionConsumed(transition, false, null);
return true;
} else {
// Keyguard handler cannot handle it, process through original mixed
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index f3ef7c1..dfdb58a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -278,9 +278,11 @@
public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
if (visible && stage != STAGE_TYPE_UNDEFINED) {
DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
- if (decor != null && DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopTasksController.moveToSplit(decor.mTaskInfo);
+ if (decor == null || !DesktopModeStatus.canEnterDesktopMode(mContext)
+ || decor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ return;
}
+ mDesktopTasksController.moveToSplit(decor.mTaskInfo);
}
}
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index f7516da..4c347ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -110,6 +110,8 @@
private ResizeVeil mResizeVeil;
private Bitmap mAppIconBitmap;
+ private Bitmap mResizeVeilBitmap;
+
private CharSequence mAppName;
private ExclusionRegionListener mExclusionRegionListener;
@@ -468,11 +470,15 @@
PackageManager pm = mContext.getApplicationContext().getPackageManager();
final IconProvider provider = new IconProvider(mContext);
final Drawable appIconDrawable = provider.getIcon(activityInfo);
- final Resources resources = mContext.getResources();
- final BaseIconFactory factory = new BaseIconFactory(mContext,
- resources.getDisplayMetrics().densityDpi,
- resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius));
- mAppIconBitmap = factory.createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+ final BaseIconFactory headerIconFactory = createIconFactory(mContext,
+ R.dimen.desktop_mode_caption_icon_radius);
+ mAppIconBitmap = headerIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+
+ final BaseIconFactory resizeVeilIconFactory = createIconFactory(mContext,
+ R.dimen.desktop_mode_resize_veil_icon_size);
+ mResizeVeilBitmap = resizeVeilIconFactory
+ .createScaledBitmap(appIconDrawable, MODE_DEFAULT);
+
final ApplicationInfo applicationInfo = activityInfo.applicationInfo;
mAppName = pm.getApplicationLabel(applicationInfo);
} finally {
@@ -480,6 +486,13 @@
}
}
+ private BaseIconFactory createIconFactory(Context context, int dimensions) {
+ final Resources resources = context.getResources();
+ final int densityDpi = resources.getDisplayMetrics().densityDpi;
+ final int iconSize = resources.getDimensionPixelSize(dimensions);
+ return new BaseIconFactory(context, densityDpi, iconSize);
+ }
+
private void closeDragResizeListener() {
if (mDragResizeListener == null) {
return;
@@ -495,7 +508,7 @@
private void createResizeVeilIfNeeded() {
if (mResizeVeil != null) return;
loadAppInfoIfNeeded();
- mResizeVeil = new ResizeVeil(mContext, mDisplayController, mAppIconBitmap, mTaskInfo,
+ mResizeVeil = new ResizeVeil(mContext, mDisplayController, mResizeVeilBitmap, mTaskInfo,
mTaskSurface, mSurfaceControlTransactionSupplier);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 9624d46..5379ca6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -24,7 +24,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
-import static com.android.input.flags.Flags.enablePointerChoreographer;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
@@ -54,6 +54,7 @@
import android.view.WindowManagerGlobal;
import android.window.InputTransferToken;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
@@ -399,12 +400,17 @@
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
int ctrlType = mDragResizeWindowGeometry.calculateCtrlType(isTouch, x, y);
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "%s: Handling action down, update ctrlType to %d", TAG, ctrlType);
mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType,
rawX, rawY);
// Increase the input sink region to cover the whole screen; this is to
// prevent input and focus from going to other tasks during a drag resize.
updateInputSinkRegionForDrag(mDragStartTaskBounds);
result = true;
+ } else {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "%s: Handling action down, but ignore event", TAG);
}
break;
}
@@ -499,12 +505,10 @@
// where views in the task can receive input events because we can't set touch regions
// of input sinks to have rounded corners.
if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) {
- if (enablePointerChoreographer()) {
- mInputManager.setPointerIcon(PointerIcon.getSystemIcon(mContext, cursorType),
- displayId, deviceId, pointerId, mInputChannel.getToken());
- } else {
- mInputManager.setPointerIconType(cursorType);
- }
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: update pointer icon from %d to %d",
+ TAG, mLastCursorType, cursorType);
+ mInputManager.setPointerIcon(PointerIcon.getSystemIcon(mContext, cursorType),
+ displayId, deviceId, pointerId, mInputChannel.getToken());
mLastCursorType = cursorType;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index eafb569..4f513f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -33,6 +33,9 @@
import android.util.Size;
import android.view.MotionEvent;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
import com.android.wm.shell.R;
import java.util.Objects;
@@ -41,6 +44,11 @@
* Geometry for a drag resize region for a particular window.
*/
final class DragResizeWindowGeometry {
+ // TODO(b/337264971) clean up when no longer needed
+ @VisibleForTesting static final boolean DEBUG = true;
+ // The additional width to apply to edge resize bounds just for logging when a touch is
+ // close.
+ @VisibleForTesting static final int EDGE_DEBUG_BUFFER = 15;
private final int mTaskCornerRadius;
private final Size mTaskSize;
// The size of the handle applied to the edges of the window, for the user to drag resize.
@@ -51,10 +59,9 @@
// The task corners to permit drag resizing with a fine input, such as stylus or cursor.
private final @NonNull TaskCorners mFineTaskCorners;
// The bounds for each edge drag region, which can resize the task in one direction.
- private final @NonNull Rect mTopEdgeBounds;
- private final @NonNull Rect mLeftEdgeBounds;
- private final @NonNull Rect mRightEdgeBounds;
- private final @NonNull Rect mBottomEdgeBounds;
+ private final @NonNull TaskEdges mTaskEdges;
+ // Extra-large edge bounds for logging to help debug when an edge resize is ignored.
+ private final @Nullable TaskEdges mDebugTaskEdges;
DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
@@ -66,26 +73,12 @@
mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize);
// Save touch areas for each edge.
- mTopEdgeBounds = new Rect(
- -mResizeHandleThickness,
- -mResizeHandleThickness,
- mTaskSize.getWidth() + mResizeHandleThickness,
- 0);
- mLeftEdgeBounds = new Rect(
- -mResizeHandleThickness,
- 0,
- 0,
- mTaskSize.getHeight());
- mRightEdgeBounds = new Rect(
- mTaskSize.getWidth(),
- 0,
- mTaskSize.getWidth() + mResizeHandleThickness,
- mTaskSize.getHeight());
- mBottomEdgeBounds = new Rect(
- -mResizeHandleThickness,
- mTaskSize.getHeight(),
- mTaskSize.getWidth() + mResizeHandleThickness,
- mTaskSize.getHeight() + mResizeHandleThickness);
+ mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness);
+ if (DEBUG) {
+ mDebugTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness + EDGE_DEBUG_BUFFER);
+ } else {
+ mDebugTaskEdges = null;
+ }
}
/**
@@ -127,10 +120,13 @@
*/
void union(@NonNull Region region) {
// Apply the edge resize regions.
- region.union(mTopEdgeBounds);
- region.union(mLeftEdgeBounds);
- region.union(mRightEdgeBounds);
- region.union(mBottomEdgeBounds);
+ if (inDebugMode()) {
+ // Use the larger edge sizes if we are debugging, to be able to log if we ignored a
+ // touch due to the size of the edge region.
+ mDebugTaskEdges.union(region);
+ } else {
+ mTaskEdges.union(region);
+ }
if (enableWindowingEdgeDragResize()) {
// Apply the corners as well for the larger corners, to ensure we capture all possible
@@ -216,6 +212,10 @@
@DragPositioningCallback.CtrlType
private int calculateEdgeResizeCtrlType(float x, float y) {
+ if (inDebugMode() && (mDebugTaskEdges.contains((int) x, (int) y)
+ && !mTaskEdges.contains((int) x, (int) y))) {
+ return CTRL_TYPE_UNDEFINED;
+ }
int ctrlType = CTRL_TYPE_UNDEFINED;
// mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
// sides will use the bounds specified in setGeometry and not go into task bounds.
@@ -306,10 +306,9 @@
&& this.mResizeHandleThickness == other.mResizeHandleThickness
&& this.mFineTaskCorners.equals(other.mFineTaskCorners)
&& this.mLargeTaskCorners.equals(other.mLargeTaskCorners)
- && this.mTopEdgeBounds.equals(other.mTopEdgeBounds)
- && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds)
- && this.mRightEdgeBounds.equals(other.mRightEdgeBounds)
- && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds);
+ && (inDebugMode()
+ ? this.mDebugTaskEdges.equals(other.mDebugTaskEdges)
+ : this.mTaskEdges.equals(other.mTaskEdges));
}
@Override
@@ -320,10 +319,11 @@
mResizeHandleThickness,
mFineTaskCorners,
mLargeTaskCorners,
- mTopEdgeBounds,
- mLeftEdgeBounds,
- mRightEdgeBounds,
- mBottomEdgeBounds);
+ (inDebugMode() ? mDebugTaskEdges : mTaskEdges));
+ }
+
+ private boolean inDebugMode() {
+ return DEBUG && mDebugTaskEdges != null;
}
/**
@@ -431,4 +431,92 @@
mRightBottomCornerBounds);
}
}
+
+ /**
+ * Representation of the drag resize regions at the edges of the window.
+ */
+ private static class TaskEdges {
+ private final @NonNull Rect mTopEdgeBounds;
+ private final @NonNull Rect mLeftEdgeBounds;
+ private final @NonNull Rect mRightEdgeBounds;
+ private final @NonNull Rect mBottomEdgeBounds;
+ private final @NonNull Region mRegion;
+
+ private TaskEdges(@NonNull Size taskSize, int resizeHandleThickness) {
+ // Save touch areas for each edge.
+ mTopEdgeBounds = new Rect(
+ -resizeHandleThickness,
+ -resizeHandleThickness,
+ taskSize.getWidth() + resizeHandleThickness,
+ 0);
+ mLeftEdgeBounds = new Rect(
+ -resizeHandleThickness,
+ 0,
+ 0,
+ taskSize.getHeight());
+ mRightEdgeBounds = new Rect(
+ taskSize.getWidth(),
+ 0,
+ taskSize.getWidth() + resizeHandleThickness,
+ taskSize.getHeight());
+ mBottomEdgeBounds = new Rect(
+ -resizeHandleThickness,
+ taskSize.getHeight(),
+ taskSize.getWidth() + resizeHandleThickness,
+ taskSize.getHeight() + resizeHandleThickness);
+
+ mRegion = new Region();
+ mRegion.union(mTopEdgeBounds);
+ mRegion.union(mLeftEdgeBounds);
+ mRegion.union(mRightEdgeBounds);
+ mRegion.union(mBottomEdgeBounds);
+ }
+
+ /**
+ * Returns {@code true} if the edges contain the given point.
+ */
+ private boolean contains(int x, int y) {
+ return mRegion.contains(x, y);
+ }
+
+ /**
+ * Updates the region to include all four corners.
+ */
+ private void union(Region region) {
+ region.union(mTopEdgeBounds);
+ region.union(mLeftEdgeBounds);
+ region.union(mRightEdgeBounds);
+ region.union(mBottomEdgeBounds);
+ }
+
+ @Override
+ public String toString() {
+ return "TaskEdges for the"
+ + " top " + mTopEdgeBounds
+ + " left " + mLeftEdgeBounds
+ + " right " + mRightEdgeBounds
+ + " bottom " + mBottomEdgeBounds;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) return false;
+ if (this == obj) return true;
+ if (!(obj instanceof TaskEdges other)) return false;
+
+ return this.mTopEdgeBounds.equals(other.mTopEdgeBounds)
+ && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds)
+ && this.mRightEdgeBounds.equals(other.mRightEdgeBounds)
+ && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mTopEdgeBounds,
+ mLeftEdgeBounds,
+ mRightEdgeBounds,
+ mBottomEdgeBounds);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
index a2293d5..ec20471 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.windowdecor.extension
import android.app.TaskInfo
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND
@@ -36,6 +35,3 @@
val TaskInfo.isFullscreen: Boolean
get() = windowingMode == WINDOWING_MODE_FULLSCREEN
-
-val TaskInfo.isFreeform: Boolean
- get() = windowingMode == WINDOWING_MODE_FREEFORM
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index df8a222..3f76c4f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -24,6 +24,12 @@
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
@@ -101,7 +107,9 @@
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
import org.mockito.quality.Strictness
@@ -141,6 +149,7 @@
@Mock lateinit var dragAndDropController: DragAndDropController
@Mock lateinit var multiInstanceHelper: MultiInstanceHelper
@Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
+ @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -154,6 +163,15 @@
// Mock running tasks are registered here so we can get the list from mock shell task organizer
private val runningTasks = mutableListOf<RunningTaskInfo>()
+ private val DISPLAY_DIMENSION_SHORT = 1600
+ private val DISPLAY_DIMENSION_LONG = 2560
+ private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 200, 2240, 1400)
+ private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 320, 1400, 2240)
+ private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 680, 1575, 1880)
+ private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 200, 1880, 1400)
+ private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 699, 1575, 1861)
+ private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 200, 1730, 1400)
+
@Before
fun setUp() {
mockitoSession = mockitoSession().strictness(Strictness.LENIENT)
@@ -161,7 +179,7 @@
whenever(DesktopModeStatus.isEnabled()).thenReturn(true)
doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
- shellInit = Mockito.spy(ShellInit(testExecutor))
+ shellInit = spy(ShellInit(testExecutor))
desktopModeTaskRepository = DesktopModeTaskRepository()
desktopTasksLimiter =
DesktopTasksLimiter(transitions, desktopModeTaskRepository, shellTaskOrganizer)
@@ -464,6 +482,135 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask()
+ setUpLandscapeDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+ setUpLandscapeDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ shouldLetterbox = true)
+ setUpLandscapeDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+ setUpLandscapeDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
+ setUpLandscapeDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
+ setUpPortraitDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+ setUpPortraitDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+ setUpPortraitDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(isResizable = false,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+ setUpPortraitDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun moveToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = setUpFullscreenTask(isResizable = false,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+ setUpPortraitDisplay()
+
+ controller.moveToDesktop(task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
val task = setUpFullscreenTask()
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -1225,6 +1372,185 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask()
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ shouldLetterbox = true)
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(isResizable = false,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(isResizable = false,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true)
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() {
val task = setUpFreeformTask()
val mockSurface = mock(SurfaceControl::class.java)
@@ -1276,8 +1602,7 @@
controller.toggleDesktopTaskSize(task)
// Assert bounds set to stable bounds
val wct = getLatestToggleResizeDesktopTaskWct()
- assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds)
- .isEqualTo(STABLE_BOUNDS)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
}
@Test
@@ -1304,8 +1629,7 @@
// Assert bounds set to last bounds before maximize
val wct = getLatestToggleResizeDesktopTaskWct()
- assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds)
- .isEqualTo(boundsBeforeMaximize)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
}
@Test
@@ -1346,14 +1670,65 @@
return task
}
- private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+ private fun setUpFullscreenTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ isResizable: Boolean = true,
+ windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
+ deviceOrientation: Int = ORIENTATION_LANDSCAPE,
+ screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
+ shouldLetterbox: Boolean = false
+ ): RunningTaskInfo {
val task = createFullscreenTask(displayId)
+ val activityInfo = ActivityInfo()
+ activityInfo.screenOrientation = screenOrientation
+ with(task) {
+ topActivityInfo = activityInfo
+ isResizeable = isResizable
+ configuration.orientation = deviceOrientation
+ configuration.windowConfiguration.windowingMode = windowingMode
+
+ if (shouldLetterbox) {
+ if (deviceOrientation == ORIENTATION_LANDSCAPE &&
+ screenOrientation == SCREEN_ORIENTATION_PORTRAIT) {
+ // Letterbox to portrait size
+ appCompatTaskInfo.topActivityBoundsLetterboxed = true
+ appCompatTaskInfo.topActivityLetterboxWidth = 1200
+ appCompatTaskInfo.topActivityLetterboxHeight = 1600
+ } else if (deviceOrientation == ORIENTATION_PORTRAIT &&
+ screenOrientation == SCREEN_ORIENTATION_LANDSCAPE) {
+ // Letterbox to landscape size
+ appCompatTaskInfo.topActivityBoundsLetterboxed = true
+ appCompatTaskInfo.topActivityLetterboxWidth = 1600
+ appCompatTaskInfo.topActivityLetterboxHeight = 1200
+ }
+ } else {
+ appCompatTaskInfo.topActivityBoundsLetterboxed = false
+ }
+
+ if (deviceOrientation == ORIENTATION_LANDSCAPE) {
+ configuration.windowConfiguration.appBounds = Rect(0, 0,
+ DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT)
+ } else {
+ configuration.windowConfiguration.appBounds = Rect(0, 0,
+ DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG)
+ }
+ }
whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
runningTasks.add(task)
return task
}
+ private fun setUpLandscapeDisplay() {
+ whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG)
+ whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT)
+ }
+
+ private fun setUpPortraitDisplay() {
+ whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT)
+ whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG)
+ }
+
private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createSplitScreenTask(displayId)
whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
@@ -1418,6 +1793,17 @@
return arg.value
}
+ private fun getLatestDragToDesktopWct(): WindowContainerTransaction {
+ val arg: ArgumentCaptor<WindowContainerTransaction> =
+ ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ if (ENABLE_SHELL_TRANSITIONS) {
+ verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
+ } else {
+ verify(shellTaskOrganizer).applyTransaction(capture(arg))
+ }
+ return arg.value
+ }
+
private fun getLatestExitDesktopWct(): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
if (ENABLE_SHELL_TRANSITIONS) {
@@ -1429,6 +1815,10 @@
return arg.value
}
+ private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
+ wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds
+
+
private fun verifyWCTNotExecuted() {
if (ENABLE_SHELL_TRANSITIONS) {
verify(transitions, never()).startTransition(anyInt(), any(), isNull())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 82e5a1c..5464508 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -56,6 +56,8 @@
private static final Size TASK_SIZE = new Size(500, 1000);
private static final int TASK_CORNER_RADIUS = 10;
private static final int EDGE_RESIZE_THICKNESS = 15;
+ private static final int EDGE_RESIZE_DEBUG_THICKNESS = EDGE_RESIZE_THICKNESS
+ + (DragResizeWindowGeometry.DEBUG ? DragResizeWindowGeometry.EDGE_DEBUG_BUFFER : 0);
private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
@@ -90,13 +92,14 @@
EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
- .addEqualityGroup(
+ .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+ EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5),
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
- LARGE_CORNER_SIZE + 5),
+ EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5))
+ .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+ EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE),
new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
- EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
- LARGE_CORNER_SIZE + 5))
+ EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE))
.testEquals();
}
@@ -122,21 +125,21 @@
private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) {
assertThat(region.contains(point.x, point.y)).isTrue();
// Horizontally along the edge is still contained.
- assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue();
- assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue();
+ assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
+ assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue();
// Vertically along the edge is not contained.
- assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse();
- assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse();
+ assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
+ assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isFalse();
}
private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) {
assertThat(region.contains(point.x, point.y)).isTrue();
// Horizontally along the edge is not contained.
- assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse();
- assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+ assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
+ assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse();
// Vertically along the edge is contained.
- assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue();
- assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue();
+ assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
+ assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isTrue();
}
/**
@@ -148,7 +151,10 @@
public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() {
Region region = new Region();
GEOMETRY.union(region);
- final int cornerRadius = LARGE_CORNER_SIZE / 2;
+ // Make sure we're choosing a point outside of any debug region buffer.
+ final int cornerRadius = DragResizeWindowGeometry.DEBUG
+ ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
+ : LARGE_CORNER_SIZE / 2;
new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
}
@@ -162,7 +168,9 @@
public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() {
Region region = new Region();
GEOMETRY.union(region);
- final int cornerRadius = FINE_CORNER_SIZE / 2;
+ final int cornerRadius = DragResizeWindowGeometry.DEBUG
+ ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS)
+ : LARGE_CORNER_SIZE / 2;
new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
}
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 6a46544..5cf5a1d 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -117,7 +117,7 @@
return {mLocked.pointerX, mLocked.pointerY};
}
-int32_t MouseCursorController::getDisplayId() const {
+ui::LogicalDisplayId MouseCursorController::getDisplayId() const {
std::scoped_lock lock(mLock);
return mLocked.viewport.displayId;
}
@@ -467,10 +467,10 @@
std::function<bool(nsecs_t)> func = std::bind(&MouseCursorController::doAnimations, this, _1);
/*
- * Using -1 for displayId here to avoid removing the callback
+ * Using ui::ADISPLAY_ID_NONE for displayId here to avoid removing the callback
* if a TouchSpotController with the same display is removed.
*/
- mContext.addAnimationCallback(-1, func);
+ mContext.addAnimationCallback(ui::ADISPLAY_ID_NONE, func);
}
} // namespace android
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 00dc085..dc7e8ca 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -47,7 +47,7 @@
void move(float deltaX, float deltaY);
void setPosition(float x, float y);
FloatPoint getPosition() const;
- int32_t getDisplayId() const;
+ ui::LogicalDisplayId getDisplayId() const;
void fade(PointerControllerInterface::Transition transition);
void unfade(PointerControllerInterface::Transition transition);
void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources);
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index f97992f..cca1b07 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -142,7 +142,7 @@
}
void PointerController::move(float deltaX, float deltaY) {
- const int32_t displayId = mCursorController.getDisplayId();
+ const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
vec2 transformed;
{
std::scoped_lock lock(getLock());
@@ -153,7 +153,7 @@
}
void PointerController::setPosition(float x, float y) {
- const int32_t displayId = mCursorController.getDisplayId();
+ const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
vec2 transformed;
{
std::scoped_lock lock(getLock());
@@ -164,7 +164,7 @@
}
FloatPoint PointerController::getPosition() const {
- const int32_t displayId = mCursorController.getDisplayId();
+ const ui::LogicalDisplayId displayId = mCursorController.getDisplayId();
const auto p = mCursorController.getPosition();
{
std::scoped_lock lock(getLock());
@@ -173,7 +173,7 @@
}
}
-int32_t PointerController::getDisplayId() const {
+ui::LogicalDisplayId PointerController::getDisplayId() const {
return mCursorController.getDisplayId();
}
@@ -202,7 +202,7 @@
}
void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
- BitSet32 spotIdBits, int32_t displayId) {
+ BitSet32 spotIdBits, ui::LogicalDisplayId displayId) {
std::scoped_lock lock(getLock());
std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};
const ui::Transform& transform = getTransformForDisplayLocked(displayId);
@@ -286,7 +286,7 @@
mCursorController.setCustomPointerIcon(icon);
}
-void PointerController::setSkipScreenshot(int32_t displayId, bool skip) {
+void PointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) {
std::scoped_lock lock(getLock());
if (skip) {
mLocked.displaysToSkipScreenshot.insert(displayId);
@@ -300,14 +300,14 @@
}
void PointerController::onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports) {
- std::unordered_set<int32_t> displayIdSet;
+ std::unordered_set<ui::LogicalDisplayId> displayIdSet;
for (const DisplayViewport& viewport : viewports) {
displayIdSet.insert(viewport.displayId);
}
std::scoped_lock lock(getLock());
for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) {
- int32_t displayId = it->first;
+ ui::LogicalDisplayId displayId = it->first;
if (!displayIdSet.count(displayId)) {
/*
* Ensures that an in-progress animation won't dereference
@@ -326,7 +326,8 @@
mLocked.mDisplayInfos = displayInfo;
}
-const ui::Transform& PointerController::getTransformForDisplayLocked(int displayId) const {
+const ui::Transform& PointerController::getTransformForDisplayLocked(
+ ui::LogicalDisplayId displayId) const {
const auto& di = mLocked.mDisplayInfos;
auto it = std::find_if(di.begin(), di.end(), [displayId](const gui::DisplayInfo& info) {
return info.displayId == displayId;
@@ -339,7 +340,8 @@
std::scoped_lock lock(getLock());
dump += StringPrintf(INDENT2 "Presentation: %s\n",
ftl::enum_string(mLocked.presentation).c_str());
- dump += StringPrintf(INDENT2 "Pointer Display ID: %" PRIu32 "\n", mLocked.pointerDisplayId);
+ dump += StringPrintf(INDENT2 "Pointer Display ID: %s\n",
+ mLocked.pointerDisplayId.toString().c_str());
dump += StringPrintf(INDENT2 "Viewports:\n");
for (const auto& info : mLocked.mDisplayInfos) {
info.dump(dump, INDENT3);
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index eaf34d5..70e5c24 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -55,18 +55,18 @@
void move(float deltaX, float deltaY) override;
void setPosition(float x, float y) override;
FloatPoint getPosition() const override;
- int32_t getDisplayId() const override;
+ ui::LogicalDisplayId getDisplayId() const override;
void fade(Transition transition) override;
void unfade(Transition transition) override;
void setDisplayViewport(const DisplayViewport& viewport) override;
void setPresentation(Presentation presentation) override;
void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
- BitSet32 spotIdBits, int32_t displayId) override;
+ BitSet32 spotIdBits, ui::LogicalDisplayId displayId) override;
void clearSpots() override;
void updatePointerIcon(PointerIconStyle iconId) override;
void setCustomPointerIcon(const SpriteIcon& icon) override;
- void setSkipScreenshot(int32_t displayId, bool skip) override;
+ void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) override;
virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
void doInactivityTimeout();
@@ -109,11 +109,11 @@
struct Locked {
Presentation presentation;
- int32_t pointerDisplayId = ADISPLAY_ID_NONE;
+ ui::LogicalDisplayId pointerDisplayId = ui::ADISPLAY_ID_NONE;
std::vector<gui::DisplayInfo> mDisplayInfos;
- std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
- std::unordered_set<int32_t /* displayId */> displaysToSkipScreenshot;
+ std::unordered_map<ui::LogicalDisplayId, TouchSpotController> spotControllers;
+ std::unordered_set<ui::LogicalDisplayId> displaysToSkipScreenshot;
} mLocked GUARDED_BY(getLock());
class DisplayInfoListener : public gui::WindowInfosListener {
@@ -132,7 +132,8 @@
sp<DisplayInfoListener> mDisplayInfoListener;
const WindowListenerUnregisterConsumer mUnregisterWindowInfosListener;
- const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(getLock());
+ const ui::Transform& getTransformForDisplayLocked(ui::LogicalDisplayId displayId) const
+ REQUIRES(getLock());
void clearSpotsLocked() REQUIRES(getLock());
};
@@ -148,7 +149,7 @@
void setPresentation(Presentation) override {
LOG_ALWAYS_FATAL("Should not be called");
}
- void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override {
+ void setSpots(const PointerCoords*, const uint32_t*, BitSet32, ui::LogicalDisplayId) override {
LOG_ALWAYS_FATAL("Should not be called");
}
void clearSpots() override {
@@ -176,7 +177,7 @@
FloatPoint getPosition() const override {
LOG_ALWAYS_FATAL("Should not be called");
}
- int32_t getDisplayId() const override {
+ ui::LogicalDisplayId getDisplayId() const override {
LOG_ALWAYS_FATAL("Should not be called");
}
void fade(Transition) override {
@@ -212,7 +213,7 @@
void setPresentation(Presentation) override {
LOG_ALWAYS_FATAL("Should not be called");
}
- void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override {
+ void setSpots(const PointerCoords*, const uint32_t*, BitSet32, ui::LogicalDisplayId) override {
LOG_ALWAYS_FATAL("Should not be called");
}
void clearSpots() override {
diff --git a/libs/input/PointerControllerContext.cpp b/libs/input/PointerControllerContext.cpp
index 15c3517..747eb8e 100644
--- a/libs/input/PointerControllerContext.cpp
+++ b/libs/input/PointerControllerContext.cpp
@@ -138,12 +138,12 @@
return 1; // keep the callback
}
-void PointerControllerContext::addAnimationCallback(int32_t displayId,
+void PointerControllerContext::addAnimationCallback(ui::LogicalDisplayId displayId,
std::function<bool(nsecs_t)> callback) {
mAnimator.addCallback(displayId, callback);
}
-void PointerControllerContext::removeAnimationCallback(int32_t displayId) {
+void PointerControllerContext::removeAnimationCallback(ui::LogicalDisplayId displayId) {
mAnimator.removeCallback(displayId);
}
@@ -161,14 +161,14 @@
}
}
-void PointerControllerContext::PointerAnimator::addCallback(int32_t displayId,
+void PointerControllerContext::PointerAnimator::addCallback(ui::LogicalDisplayId displayId,
std::function<bool(nsecs_t)> callback) {
std::scoped_lock lock(mLock);
mLocked.callbacks[displayId] = callback;
startAnimationLocked();
}
-void PointerControllerContext::PointerAnimator::removeCallback(int32_t displayId) {
+void PointerControllerContext::PointerAnimator::removeCallback(ui::LogicalDisplayId displayId) {
std::scoped_lock lock(mLock);
auto it = mLocked.callbacks.find(displayId);
if (it == mLocked.callbacks.end()) {
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index e893c49..d422148 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -72,12 +72,13 @@
virtual ~PointerControllerPolicyInterface() {}
public:
- virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) = 0;
- virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) = 0;
+ virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) = 0;
+ virtual void loadPointerResources(PointerResources* outResources,
+ ui::LogicalDisplayId displayId) = 0;
virtual void loadAdditionalMouseResources(
std::map<PointerIconStyle, SpriteIcon>* outResources,
std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
- int32_t displayId) = 0;
+ ui::LogicalDisplayId displayId) = 0;
virtual PointerIconStyle getDefaultPointerIconId() = 0;
virtual PointerIconStyle getDefaultStylusIconId() = 0;
virtual PointerIconStyle getCustomPointerIconId() = 0;
@@ -102,7 +103,7 @@
nsecs_t getAnimationTime();
- void clearSpotsByDisplay(int32_t displayId);
+ void clearSpotsByDisplay(ui::LogicalDisplayId displayId);
void setHandlerController(std::shared_ptr<PointerController> controller);
void setCallbackController(std::shared_ptr<PointerController> controller);
@@ -112,8 +113,9 @@
void handleDisplayEvents();
- void addAnimationCallback(int32_t displayId, std::function<bool(nsecs_t)> callback);
- void removeAnimationCallback(int32_t displayId);
+ void addAnimationCallback(ui::LogicalDisplayId displayId,
+ std::function<bool(nsecs_t)> callback);
+ void removeAnimationCallback(ui::LogicalDisplayId displayId);
class MessageHandler : public virtual android::MessageHandler {
public:
@@ -136,8 +138,8 @@
public:
PointerAnimator(PointerControllerContext& context);
- void addCallback(int32_t displayId, std::function<bool(nsecs_t)> callback);
- void removeCallback(int32_t displayId);
+ void addCallback(ui::LogicalDisplayId displayId, std::function<bool(nsecs_t)> callback);
+ void removeCallback(ui::LogicalDisplayId displayId);
void handleVsyncEvents();
nsecs_t getAnimationTimeLocked();
@@ -148,7 +150,7 @@
bool animationPending{false};
nsecs_t animationTime{systemTime(SYSTEM_TIME_MONOTONIC)};
- std::unordered_map<int32_t, std::function<bool(nsecs_t)>> callbacks;
+ std::unordered_map<ui::LogicalDisplayId, std::function<bool(nsecs_t)>> callbacks;
} mLocked GUARDED_BY(mLock);
DisplayEventReceiver mDisplayEventReceiver;
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 0baa929..af49939 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -19,9 +19,9 @@
#include "SpriteController.h"
-#include <log/log.h>
-#include <utils/String8.h>
+#include <android-base/logging.h>
#include <gui/Surface.h>
+#include <utils/String8.h>
namespace android {
@@ -340,13 +340,14 @@
}
}
-sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height, int32_t displayId,
+sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height,
+ ui::LogicalDisplayId displayId,
bool hideOnMirrored) {
ensureSurfaceComposerClient();
const sp<SurfaceControl> parent = mParentSurfaceProvider(displayId);
if (parent == nullptr) {
- ALOGE("Failed to get the parent surface for pointers on display %d", displayId);
+ LOG(ERROR) << "Failed to get the parent surface for pointers on display " << displayId;
}
int32_t createFlags = ISurfaceComposerClient::eHidden | ISurfaceComposerClient::eCursorWindow;
@@ -475,7 +476,7 @@
}
}
-void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) {
+void SpriteController::SpriteImpl::setDisplayId(ui::LogicalDisplayId displayId) {
AutoMutex _l(mController.mLock);
if (mLocked.state.displayId != displayId) {
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index fdb15506..070c90c 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -95,7 +95,7 @@
virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
/* Sets the id of the display where the sprite should be shown. */
- virtual void setDisplayId(int32_t displayId) = 0;
+ virtual void setDisplayId(ui::LogicalDisplayId displayId) = 0;
/* Sets the flag to hide sprite on mirrored displays.
* This will add ISurfaceComposerClient::eSkipScreenshot flag to the sprite. */
@@ -115,7 +115,7 @@
*/
class SpriteController {
public:
- using ParentSurfaceProvider = std::function<sp<SurfaceControl>(int /*displayId*/)>;
+ using ParentSurfaceProvider = std::function<sp<SurfaceControl>(ui::LogicalDisplayId)>;
SpriteController(const sp<Looper>& looper, int32_t overlayLayer, ParentSurfaceProvider parent);
SpriteController(const SpriteController&) = delete;
SpriteController& operator=(const SpriteController&) = delete;
@@ -174,7 +174,7 @@
int32_t layer{0};
float alpha{1.0f};
SpriteTransformationMatrix transformationMatrix;
- int32_t displayId{ADISPLAY_ID_DEFAULT};
+ ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_DEFAULT};
sp<SurfaceControl> surfaceControl;
int32_t surfaceWidth{0};
@@ -208,7 +208,7 @@
virtual void setLayer(int32_t layer);
virtual void setAlpha(float alpha);
virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
- virtual void setDisplayId(int32_t displayId);
+ virtual void setDisplayId(ui::LogicalDisplayId displayId);
virtual void setSkipScreenshot(bool skip);
inline const SpriteState& getStateLocked() const {
@@ -273,7 +273,7 @@
void doDisposeSurfaces();
void ensureSurfaceComposerClient();
- sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, int32_t displayId,
+ sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, ui::LogicalDisplayId displayId,
bool hideOnMirrored);
};
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index 530d541..7462481 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -40,7 +40,7 @@
// --- Spot ---
void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float newX, float newY,
- int32_t displayId, bool skipScreenshot) {
+ ui::LogicalDisplayId displayId, bool skipScreenshot) {
sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
sprite->setAlpha(alpha);
sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
@@ -69,7 +69,8 @@
// --- TouchSpotController ---
-TouchSpotController::TouchSpotController(int32_t displayId, PointerControllerContext& context)
+TouchSpotController::TouchSpotController(ui::LogicalDisplayId displayId,
+ PointerControllerContext& context)
: mDisplayId(displayId), mContext(context) {
mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId);
}
@@ -94,7 +95,7 @@
const PointerCoords& c = spotCoords[spotIdToIndex[id]];
ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id,
c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y),
- c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId);
+ c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId.id);
}
#endif
@@ -274,7 +275,7 @@
out += prefix;
out += "SpotController:\n";
out += prefix;
- StringAppendF(&out, INDENT "DisplayId: %" PRId32 "\n", mDisplayId);
+ StringAppendF(&out, INDENT "DisplayId: %s\n", mDisplayId.toString().c_str());
std::scoped_lock lock(mLock);
out += prefix;
StringAppendF(&out, INDENT "Animating: %s\n", toString(mLocked.animating));
diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h
index 608653c..ac37fa4 100644
--- a/libs/input/TouchSpotController.h
+++ b/libs/input/TouchSpotController.h
@@ -29,7 +29,7 @@
*/
class TouchSpotController {
public:
- TouchSpotController(int32_t displayId, PointerControllerContext& context);
+ TouchSpotController(ui::LogicalDisplayId displayId, PointerControllerContext& context);
~TouchSpotController();
void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
BitSet32 spotIdBits, bool skipScreenshot);
@@ -59,7 +59,7 @@
y(0.0f),
mLastIcon(nullptr) {}
- void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId,
+ void updateSprite(const SpriteIcon* icon, float x, float y, ui::LogicalDisplayId displayId,
bool skipScreenshot);
void dump(std::string& out, const char* prefix = "") const;
@@ -67,7 +67,7 @@
const SpriteIcon* mLastIcon;
};
- int32_t mDisplayId;
+ ui::LogicalDisplayId mDisplayId;
mutable std::mutex mLock;
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 3bc0e24..7a13380 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -52,12 +52,13 @@
class MockPointerControllerPolicyInterface : public PointerControllerPolicyInterface {
public:
- virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) override;
- virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) override;
+ virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) override;
+ virtual void loadPointerResources(PointerResources* outResources,
+ ui::LogicalDisplayId displayId) override;
virtual void loadAdditionalMouseResources(
std::map<PointerIconStyle, SpriteIcon>* outResources,
std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
- int32_t displayId) override;
+ ui::LogicalDisplayId displayId) override;
virtual PointerIconStyle getDefaultPointerIconId() override;
virtual PointerIconStyle getDefaultStylusIconId() override;
virtual PointerIconStyle getCustomPointerIconId() override;
@@ -73,13 +74,13 @@
bool additionalMouseResourcesLoaded{false};
};
-void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) {
+void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId) {
loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT);
pointerIconLoaded = true;
}
void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources,
- int32_t) {
+ ui::LogicalDisplayId) {
loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER);
loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH);
loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR);
@@ -88,7 +89,7 @@
void MockPointerControllerPolicyInterface::loadAdditionalMouseResources(
std::map<PointerIconStyle, SpriteIcon>* outResources,
- std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t) {
+ std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, ui::LogicalDisplayId) {
SpriteIcon icon;
PointerAnimation anim;
@@ -165,7 +166,7 @@
PointerControllerTest();
~PointerControllerTest();
- void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT);
+ void ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT);
sp<MockSprite> mPointerSprite;
sp<MockPointerControllerPolicyInterface> mPolicy;
@@ -204,7 +205,7 @@
mThread.join();
}
-void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) {
+void PointerControllerTest::ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId) {
DisplayViewport viewport;
viewport.displayId = displayId;
viewport.logicalRight = 1600;
@@ -334,23 +335,23 @@
// Update spots to sync state with sprite
mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
- ADISPLAY_ID_DEFAULT);
+ ui::ADISPLAY_ID_DEFAULT);
testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
// Marking the display to skip screenshot should update sprite as well
- mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, true);
+ mPointerController->setSkipScreenshot(ui::ADISPLAY_ID_DEFAULT, true);
EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true));
// Update spots to sync state with sprite
mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
- ADISPLAY_ID_DEFAULT);
+ ui::ADISPLAY_ID_DEFAULT);
testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
// Reset flag and verify again
- mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, false);
+ mPointerController->setSkipScreenshot(ui::ADISPLAY_ID_DEFAULT, false);
EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false));
mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
- ADISPLAY_ID_DEFAULT);
+ ui::ADISPLAY_ID_DEFAULT);
testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
}
diff --git a/libs/input/tests/mocks/MockSprite.h b/libs/input/tests/mocks/MockSprite.h
index 0867221..21628fb 100644
--- a/libs/input/tests/mocks/MockSprite.h
+++ b/libs/input/tests/mocks/MockSprite.h
@@ -33,7 +33,7 @@
MOCK_METHOD(void, setLayer, (int32_t), (override));
MOCK_METHOD(void, setAlpha, (float), (override));
MOCK_METHOD(void, setTransformationMatrix, (const SpriteTransformationMatrix&), (override));
- MOCK_METHOD(void, setDisplayId, (int32_t), (override));
+ MOCK_METHOD(void, setDisplayId, (ui::LogicalDisplayId), (override));
MOCK_METHOD(void, setSkipScreenshot, (bool), (override));
};
diff --git a/libs/input/tests/mocks/MockSpriteController.h b/libs/input/tests/mocks/MockSpriteController.h
index 62f1d65..9ef6b7c 100644
--- a/libs/input/tests/mocks/MockSpriteController.h
+++ b/libs/input/tests/mocks/MockSpriteController.h
@@ -27,7 +27,7 @@
public:
MockSpriteController(sp<Looper> looper)
- : SpriteController(looper, 0, [](int) { return nullptr; }) {}
+ : SpriteController(looper, 0, [](ui::LogicalDisplayId) { return nullptr; }) {}
~MockSpriteController() {}
MOCK_METHOD(sp<Sprite>, createSprite, (), (override));
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index d969d1c..2e9b7b4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -446,6 +446,9 @@
// Returns InstallUserActionRequired stage if install details could be successfully
// computed, else it returns InstallAborted.
val confirmationSnippet: InstallStage = generateConfirmationSnippet()
+ if (confirmationSnippet.stageCode == InstallStage.STAGE_ABORTED) {
+ return confirmationSnippet
+ }
val existingUpdateOwner: CharSequence? = getExistingUpdateOwner(newPackageInfo!!)
return if (sessionId == SessionInfo.INVALID_ID &&
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
index 7c76ea1..221e8f5 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
@@ -38,7 +38,7 @@
<color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
<!-- Dialog background color. -->
- <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+ <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainer</color>
<color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
index 2a6499a..dc2d3dc 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
@@ -38,7 +38,7 @@
<color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
<!-- Dialog background color. -->
- <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+ <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainer</color>
<!-- Material next track outline color-->
<color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 3dffb27..8917412 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -10,8 +10,10 @@
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -37,12 +39,9 @@
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.settingslib.widget.AdaptiveOutlineDrawable;
-import com.google.common.collect.ImmutableSet;
-
import java.io.IOException;
import java.util.List;
import java.util.Locale;
-import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -56,8 +55,6 @@
public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
- private static final Set<String> EXCLUSIVE_MANAGERS =
- ImmutableSet.of("com.google.android.gms.dck");
private static ErrorListener sErrorListener;
@@ -740,14 +737,13 @@
/**
* Returns the BluetoothDevice's exclusive manager ({@link
- * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the given
- * set, otherwise null.
+ * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists, otherwise null.
*/
@Nullable
- private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) {
- byte[] exclusiveManagerNameBytes =
+ private static String getExclusiveManager(BluetoothDevice bluetoothDevice) {
+ byte[] exclusiveManagerBytes =
bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
- if (exclusiveManagerNameBytes == null) {
+ if (exclusiveManagerBytes == null) {
Log.d(
TAG,
"Bluetooth device "
@@ -755,47 +751,46 @@
+ " doesn't have exclusive manager");
return null;
}
- String exclusiveManagerName = new String(exclusiveManagerNameBytes);
- return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null;
+ return new String(exclusiveManagerBytes);
}
- /** Checks if given package is installed */
- private static boolean isPackageInstalled(Context context, String packageName) {
+ /** Checks if given package is installed and enabled */
+ private static boolean isPackageInstalledAndEnabled(Context context, String packageName) {
PackageManager packageManager = context.getPackageManager();
try {
- packageManager.getPackageInfo(packageName, 0);
- return true;
+ ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0);
+ return appInfo.enabled;
} catch (PackageManager.NameNotFoundException e) {
- Log.d(TAG, "Package " + packageName + " is not installed");
+ Log.d(TAG, "Package " + packageName + " is not installed/enabled");
}
return false;
}
/**
* A BluetoothDevice is exclusively managed if 1) it has field {@link
- * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app name is
- * in the allowlist. 3) the exclusive manager app is installed.
+ * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app is
+ * installed and enabled.
*/
public static boolean isExclusivelyManagedBluetoothDevice(
@NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) {
- String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice);
+ String exclusiveManagerName = getExclusiveManager(bluetoothDevice);
if (exclusiveManagerName == null) {
return false;
}
- if (!isPackageInstalled(context, exclusiveManagerName)) {
+
+ ComponentName exclusiveManagerComponent =
+ ComponentName.unflattenFromString(exclusiveManagerName);
+ String exclusiveManagerPackage = exclusiveManagerComponent != null
+ ? exclusiveManagerComponent.getPackageName() : exclusiveManagerName;
+
+ if (!isPackageInstalledAndEnabled(context, exclusiveManagerPackage)) {
return false;
} else {
- Log.d(TAG, "Found exclusively managed app " + exclusiveManagerName);
+ Log.d(TAG, "Found exclusively managed app " + exclusiveManagerPackage);
return true;
}
}
- /** Return the allowlist for exclusive manager names. */
- @NonNull
- public static Set<String> getExclusiveManagers() {
- return EXCLUSIVE_MANAGERS;
- }
-
/**
* Get CSIP group id for {@link CachedBluetoothDevice}.
*
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index f197f9e..7a2818d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -28,7 +28,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
-import android.content.pm.PackageInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
@@ -80,7 +80,8 @@
private static final String CONTROL_METADATA =
"<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + STRING_METADATA
+ "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
- private static final String FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name";
+ private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager";
+ private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component";
@Before
public void setUp() {
@@ -399,7 +400,7 @@
}
@Test
- public void isExclusivelyManagedBluetoothDevice_isNotExclusivelyManaged_returnFalse() {
+ public void isExclusivelyManaged_hasNoManager_returnFalse() {
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
null);
@@ -408,45 +409,85 @@
}
@Test
- public void isExclusivelyManagedBluetoothDevice_isNotInAllowList_returnFalse() {
+ public void isExclusivelyManaged_hasPackageName_packageNotInstalled_returnFalse()
+ throws Exception {
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- FAKE_EXCLUSIVE_MANAGER_NAME.getBytes());
+ TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager)
+ .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
mBluetoothDevice)).isEqualTo(false);
}
@Test
- public void isExclusivelyManagedBluetoothDevice_packageNotInstalled_returnFalse()
+ public void isExclusivelyManaged_hasComponentName_packageNotInstalled_returnFalse()
throws Exception {
- final String exclusiveManagerName =
- BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
- FAKE_EXCLUSIVE_MANAGER_NAME);
-
when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- exclusiveManagerName.getBytes());
+ TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getPackageInfo(
- exclusiveManagerName, 0);
+ doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager)
+ .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(false);
+ mBluetoothDevice)).isEqualTo(false);
}
@Test
- public void isExclusivelyManagedBluetoothDevice_isExclusivelyManaged_returnTrue()
- throws Exception {
- final String exclusiveManagerName =
- BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
- FAKE_EXCLUSIVE_MANAGER_NAME);
-
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- exclusiveManagerName.getBytes());
+ public void isExclusivelyManaged_hasPackageName_packageNotEnabled_returnFalse()
+ throws Exception {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.enabled = false;
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0);
+ doReturn(appInfo).when(mPackageManager).getApplicationInfo(
+ TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+ TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(true);
+ mBluetoothDevice)).isEqualTo(false);
+ }
+
+ @Test
+ public void isExclusivelyManaged_hasComponentName_packageNotEnabled_returnFalse()
+ throws Exception {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.enabled = false;
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ doReturn(appInfo).when(mPackageManager).getApplicationInfo(
+ TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+ TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
+
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+ mBluetoothDevice)).isEqualTo(false);
+ }
+
+ @Test
+ public void isExclusivelyManaged_hasPackageName_packageInstalledAndEnabled_returnTrue()
+ throws Exception {
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+ TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo(
+ TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+ mBluetoothDevice)).isEqualTo(true);
+ }
+
+ @Test
+ public void isExclusivelyManaged_hasComponentName_packageInstalledAndEnabled_returnTrue()
+ throws Exception {
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+ TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo(
+ TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+ mBluetoothDevice)).isEqualTo(true);
}
@Test
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 461b6b3..70ce202 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2949,9 +2949,6 @@
dumpSetting(s, p,
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
SystemSettingsProto.Screen.AUTO_BRIGHTNESS_ADJ);
- dumpSetting(s, p,
- Settings.System.SCREEN_BRIGHTNESS_FLOAT,
- SystemSettingsProto.Screen.BRIGHTNESS_FLOAT);
p.end(screenToken);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c891dfc..92167ee 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -935,7 +935,6 @@
Settings.System.VOLUME_VOICE, // deprecated since API 2?
Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
- Settings.System.SCREEN_BRIGHTNESS_FLOAT,
Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE,
Settings.System.WEAR_TTS_PREWARM_ENABLED,
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 21881f6..80398cd 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -550,6 +550,13 @@
}
flag {
+ name: "enable_contextual_tip_for_mute_volume"
+ namespace: "systemui"
+ description: "Enables the contextual tip for muting the volume."
+ bug: "337737048"
+}
+
+flag {
name: "disable_contextual_tips_frequency_check"
description: "Disables frequency capping check for contextual tips."
namespace: "systemui"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 1e60b98..d4660fa 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -44,6 +44,7 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.Flags.activityTransitionUseLargestWindow
+import java.util.concurrent.Executor
import kotlin.math.roundToInt
private const val TAG = "ActivityTransitionAnimator"
@@ -52,14 +53,19 @@
* A class that allows activities to be started in a seamless way from a view that is transforming
* nicely into the starting window.
*/
-class ActivityTransitionAnimator(
+class ActivityTransitionAnimator
+@JvmOverloads
+constructor(
+ /** The executor that runs on the main thread. */
+ private val mainExecutor: Executor,
+
/** The animator used when animating a View into an app. */
- private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
+ private val transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
/** The animator used when animating a Dialog into an app. */
// TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to
// TIMINGS.contentBeforeFadeOutDuration.
- private val dialogToAppAnimator: TransitionAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR,
+ private val dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor),
/**
* Whether we should disable the WindowManager timeout. This should be set to true in tests
@@ -100,10 +106,6 @@
// TODO(b/288507023): Remove this flag.
@JvmField val DEBUG_TRANSITION_ANIMATION = Build.IS_DEBUGGABLE
- private val DEFAULT_TRANSITION_ANIMATOR = TransitionAnimator(TIMINGS, INTERPOLATORS)
- private val DEFAULT_DIALOG_TO_APP_ANIMATOR =
- TransitionAnimator(DIALOG_TIMINGS, INTERPOLATORS)
-
/** Durations & interpolators for the navigation bar fading in & out. */
private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
@@ -121,6 +123,14 @@
* cancelled by WM.
*/
private const val LONG_TRANSITION_TIMEOUT = 5_000L
+
+ private fun defaultTransitionAnimator(mainExecutor: Executor): TransitionAnimator {
+ return TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS)
+ }
+
+ private fun defaultDialogToAppAnimator(mainExecutor: Executor): TransitionAnimator {
+ return TransitionAnimator(mainExecutor, DIALOG_TIMINGS, INTERPOLATORS)
+ }
}
/**
@@ -257,9 +267,7 @@
private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
if (Looper.myLooper() != Looper.getMainLooper()) {
- this.transitionContainer.context.mainExecutor.execute {
- callOnIntentStartedOnMainThread(willAnimate)
- }
+ mainExecutor.execute { callOnIntentStartedOnMainThread(willAnimate) }
} else {
if (DEBUG_TRANSITION_ANIMATION) {
Log.d(
@@ -479,12 +487,10 @@
controller: Controller,
callback: Callback,
/** The animator to use to animate the window transition. */
- transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
+ transitionAnimator: TransitionAnimator,
/** Listener for animation lifecycle events. */
listener: Listener? = null
) : IRemoteAnimationRunner.Stub() {
- private val context = controller.transitionContainer.context
-
// This is being passed across IPC boundaries and cycles (through PendingIntentRecords,
// etc.) are possible. So we need to make sure we drop any references that might
// transitively cause leaks when we're done with animation.
@@ -493,11 +499,12 @@
init {
delegate =
AnimationDelegate(
+ mainExecutor,
controller,
callback,
DelegatingAnimationCompletionListener(listener, this::dispose),
transitionAnimator,
- disableWmTimeout
+ disableWmTimeout,
)
}
@@ -510,7 +517,7 @@
finishedCallback: IRemoteAnimationFinishedCallback?
) {
val delegate = delegate
- context.mainExecutor.execute {
+ mainExecutor.execute {
if (delegate == null) {
Log.i(TAG, "onAnimationStart called after completion")
// Animation started too late and timed out already. We need to still
@@ -525,7 +532,7 @@
@BinderThread
override fun onAnimationCancelled() {
val delegate = delegate
- context.mainExecutor.execute {
+ mainExecutor.execute {
delegate ?: Log.wtf(TAG, "onAnimationCancelled called after completion")
delegate?.onAnimationCancelled()
}
@@ -535,19 +542,21 @@
fun dispose() {
// Drop references to animation controller once we're done with the animation
// to avoid leaking.
- context.mainExecutor.execute { delegate = null }
+ mainExecutor.execute { delegate = null }
}
}
class AnimationDelegate
@JvmOverloads
constructor(
+ private val mainExecutor: Executor,
private val controller: Controller,
private val callback: Callback,
/** Listener for animation lifecycle events. */
private val listener: Listener? = null,
/** The animator to use to animate the window transition. */
- private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
+ private val transitionAnimator: TransitionAnimator =
+ defaultTransitionAnimator(mainExecutor),
/**
* Whether we should disable the WindowManager timeout. This should be set to true in tests
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
index b89ebfc..f5d01d7 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
@@ -37,6 +37,7 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.util.maybeForceFullscreen
import com.android.systemui.util.registerAnimationOnBackInvoked
+import java.util.concurrent.Executor
import kotlin.math.roundToInt
private const val TAG = "DialogTransitionAnimator"
@@ -55,10 +56,16 @@
class DialogTransitionAnimator
@JvmOverloads
constructor(
+ private val mainExecutor: Executor,
private val callback: Callback,
private val interactionJankMonitor: InteractionJankMonitor,
private val featureFlags: AnimationFeatureFlags,
- private val transitionAnimator: TransitionAnimator = TransitionAnimator(TIMINGS, INTERPOLATORS),
+ private val transitionAnimator: TransitionAnimator =
+ TransitionAnimator(
+ mainExecutor,
+ TIMINGS,
+ INTERPOLATORS,
+ ),
private val isForTesting: Boolean = false,
) {
private companion object {
@@ -937,24 +944,9 @@
}
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
- // onLaunchAnimationEnd is called by an Animator at the end of the animation,
- // on a Choreographer animation tick. The following calls will move the animated
- // content from the dialog overlay back to its original position, and this
- // change must be reflected in the next frame given that we then sync the next
- // frame of both the content and dialog ViewRoots. However, in case that content
- // is rendered by Compose, whose compositions are also scheduled on a
- // Choreographer frame, any state change made *right now* won't be reflected in
- // the next frame given that a Choreographer frame can't schedule another and
- // have it happen in the same frame. So we post the forwarded calls to
- // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring
- // that the move of the content back to its original window will be reflected in
- // the next frame right after [onLaunchAnimationEnd] is called.
- dialog.context.mainExecutor.execute {
- startController.onTransitionAnimationEnd(isExpandingFullyAbove)
- endController.onTransitionAnimationEnd(isExpandingFullyAbove)
-
- onLaunchAnimationEnd()
- }
+ startController.onTransitionAnimationEnd(isExpandingFullyAbove)
+ endController.onTransitionAnimationEnd(isExpandingFullyAbove)
+ onLaunchAnimationEnd()
}
override fun onTransitionAnimationProgress(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 679c969..cc55df1 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -31,12 +31,17 @@
import androidx.annotation.VisibleForTesting
import com.android.app.animation.Interpolators.LINEAR
import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
+import java.util.concurrent.Executor
import kotlin.math.roundToInt
private const val TAG = "TransitionAnimator"
/** A base class to animate a window (activity or dialog) launch to or return from a view . */
-class TransitionAnimator(private val timings: Timings, private val interpolators: Interpolators) {
+class TransitionAnimator(
+ private val mainExecutor: Executor,
+ private val timings: Timings,
+ private val interpolators: Interpolators,
+) {
companion object {
internal const val DEBUG = false
private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
@@ -351,11 +356,27 @@
if (DEBUG) {
Log.d(TAG, "Animation ended")
}
- controller.onTransitionAnimationEnd(isExpandingFullyAbove)
- transitionContainerOverlay.remove(windowBackgroundLayer)
- if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
- openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+ // onAnimationEnd is called at the end of the animation, on a Choreographer
+ // animation tick. During dialog launches, the following calls will move the
+ // animated content from the dialog overlay back to its original position, and
+ // this change must be reflected in the next frame given that we then sync the
+ // next frame of both the content and dialog ViewRoots. During SysUI activity
+ // launches, we will instantly collapse the shade at the end of the transition.
+ // However, if those are rendered by Compose, whose compositions are also
+ // scheduled on a Choreographer frame, any state change made *right now* won't
+ // be reflected in the next frame given that a Choreographer frame can't
+ // schedule another and have it happen in the same frame. So we post the
+ // forwarded calls to [Controller.onLaunchAnimationEnd] in the main executor,
+ // leaving this Choreographer frame, ensuring that any state change applied by
+ // onTransitionAnimationEnd() will be reflected in the same frame.
+ mainExecutor.execute {
+ controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+ transitionContainerOverlay.remove(windowBackgroundLayer)
+
+ if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
+ openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index bca8fde..30b6c6c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -36,6 +36,7 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.displayCutoutPadding
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -68,6 +69,8 @@
import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
+import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
@@ -152,6 +155,8 @@
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
) {
+ val cutoutLocation = LocalDisplayCutout.current.location
+
val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
val contentAlpha by
animateFloatAsState(
@@ -183,6 +188,9 @@
// scene (and not the one under it) during a scene transition.
Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
}
+ .thenIf(cutoutLocation != CutoutLocation.CENTER) {
+ Modifier.displayCutoutPadding()
+ },
) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 36b60d6..10fe0cab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -31,6 +31,7 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.displayCutoutPadding
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -67,6 +68,8 @@
import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
+import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.composable.MediaCarousel
@@ -208,6 +211,8 @@
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
) {
+ val cutoutLocation = LocalDisplayCutout.current.location
+
val maxNotifScrimTop = remember { mutableStateOf(0f) }
val tileSquishiness by
animateSceneFloatAsState(
@@ -243,9 +248,15 @@
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier =
- Modifier.fillMaxWidth().thenIf(isClickable) {
- Modifier.clickable(onClick = { viewModel.onContentClicked() })
- }
+ Modifier.fillMaxWidth()
+ .thenIf(isClickable) {
+ Modifier.clickable(
+ onClick = { viewModel.onContentClicked() }
+ )
+ }
+ .thenIf(cutoutLocation != CutoutLocation.CENTER) {
+ Modifier.displayCutoutPadding()
+ },
) {
CollapsedShadeHeader(
viewModel = viewModel.shadeHeaderViewModel,
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index f539a23..bdeab79 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -28,16 +28,16 @@
import android.util.AttributeSet
import android.util.MathUtils.constrainedMap
import android.util.TypedValue
-import android.view.View.MeasureSpec.EXACTLY
import android.view.View
+import android.view.View.MeasureSpec.EXACTLY
import android.widget.TextView
import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.TextAnimator
import com.android.systemui.customization.R
-import com.android.systemui.log.core.LogcatOnlyMessageBuffer
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.core.MessageBuffer
import java.io.PrintWriter
@@ -47,11 +47,13 @@
import kotlin.math.min
/**
- * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
- * The time's text color is a gradient that changes its colors based on its controller.
+ * Displays the time with the hour positioned above the minutes (ie: 09 above 30 is 9:30). The
+ * time's text color is a gradient that changes its colors based on its controller.
*/
@SuppressLint("AppCompatCustomView")
-class AnimatableClockView @JvmOverloads constructor(
+class AnimatableClockView
+@JvmOverloads
+constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
@@ -63,7 +65,9 @@
get() = field ?: DEFAULT_LOGGER
var messageBuffer: MessageBuffer
get() = logger.buffer
- set(value) { logger = Logger(value, TAG) }
+ set(value) {
+ logger = Logger(value, TAG)
+ }
var hasCustomPositionUpdatedAnimation: Boolean = false
var migratedClocks: Boolean = false
@@ -77,16 +81,13 @@
private var format: CharSequence? = null
private var descFormat: CharSequence? = null
- @ColorInt
- private var dozingColor = 0
-
- @ColorInt
- private var lockScreenColor = 0
+ @ColorInt private var dozingColor = 0
+ @ColorInt private var lockScreenColor = 0
private var lineSpacingScale = 1f
private val chargeAnimationDelay: Int
private var textAnimator: TextAnimator? = null
- private var onTextAnimatorInitialized: Runnable? = null
+ private var onTextAnimatorInitialized: ((TextAnimator) -> Unit)? = null
private var translateForCenterAnimation = false
private val parentWidth: Int
@@ -94,9 +95,11 @@
// last text size which is not constrained by view height
private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE
- @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator =
- { layout, invalidateCb ->
- TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) }
+
+ @VisibleForTesting
+ var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb ->
+ TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb)
+ }
// Used by screenshot tests to provide stability
@VisibleForTesting var isAnimationEnabled: Boolean = true
@@ -109,40 +112,55 @@
get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
/**
- * The number of pixels below the baseline. For fonts that support languages such as
- * Burmese, this space can be significant and should be accounted for when computing layout.
+ * The number of pixels below the baseline. For fonts that support languages such as Burmese,
+ * this space can be significant and should be accounted for when computing layout.
*/
- val bottom get() = paint?.fontMetrics?.bottom ?: 0f
+ val bottom: Float
+ get() = paint?.fontMetrics?.bottom ?: 0f
init {
- val animatableClockViewAttributes = context.obtainStyledAttributes(
- attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes
- )
+ val animatableClockViewAttributes =
+ context.obtainStyledAttributes(
+ attrs,
+ R.styleable.AnimatableClockView,
+ defStyleAttr,
+ defStyleRes
+ )
try {
- dozingWeightInternal = animatableClockViewAttributes.getInt(
- R.styleable.AnimatableClockView_dozeWeight,
- /* default = */ 100
- )
- lockScreenWeightInternal = animatableClockViewAttributes.getInt(
- R.styleable.AnimatableClockView_lockScreenWeight,
- /* default = */ 300
- )
- chargeAnimationDelay = animatableClockViewAttributes.getInt(
- R.styleable.AnimatableClockView_chargeAnimationDelay, /* default = */ 200
- )
+ dozingWeightInternal =
+ animatableClockViewAttributes.getInt(
+ R.styleable.AnimatableClockView_dozeWeight,
+ /* default = */ 100
+ )
+ lockScreenWeightInternal =
+ animatableClockViewAttributes.getInt(
+ R.styleable.AnimatableClockView_lockScreenWeight,
+ /* default = */ 300
+ )
+ chargeAnimationDelay =
+ animatableClockViewAttributes.getInt(
+ R.styleable.AnimatableClockView_chargeAnimationDelay,
+ /* default = */ 200
+ )
} finally {
animatableClockViewAttributes.recycle()
}
- val textViewAttributes = context.obtainStyledAttributes(
- attrs, android.R.styleable.TextView,
- defStyleAttr, defStyleRes
- )
+ val textViewAttributes =
+ context.obtainStyledAttributes(
+ attrs,
+ android.R.styleable.TextView,
+ defStyleAttr,
+ defStyleRes
+ )
try {
- isSingleLineInternal = textViewAttributes.getBoolean(
- android.R.styleable.TextView_singleLine, /* default = */ false)
+ isSingleLineInternal =
+ textViewAttributes.getBoolean(
+ android.R.styleable.TextView_singleLine,
+ /* default = */ false
+ )
} finally {
textViewAttributes.recycle()
}
@@ -156,9 +174,7 @@
refreshFormat()
}
- /**
- * Whether to use a bolded version based on the user specified fontWeightAdjustment.
- */
+ /** Whether to use a bolded version based on the user specified fontWeightAdjustment. */
fun useBoldedVersion(): Boolean {
// "Bold text" fontWeightAdjustment is 300.
return resources.configuration.fontWeightAdjustment > 100
@@ -169,25 +185,30 @@
contentDescription = DateFormat.format(descFormat, time)
val formattedText = DateFormat.format(format, time)
logger.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
- // Setting text actually triggers a layout pass (because the text view is set to
- // wrap_content width and TextView always relayouts for this). Avoid needless
- // relayout if the text didn't actually change.
- if (!TextUtils.equals(text, formattedText)) {
- text = formattedText
- logger.d({ "refreshTime: done setting new time text to: $str1" }) {
- str1 = formattedText?.toString()
- }
- // Because the TextLayout may mutate under the hood as a result of the new text, we
- // notify the TextAnimator that it may have changed and request a measure/layout. A
- // crash will occur on the next invocation of setTextStyle if the layout is mutated
- // without being notified TextInterpolator being notified.
- if (layout != null) {
- textAnimator?.updateLayout(layout)
- logger.d("refreshTime: done updating textAnimator layout")
- }
- requestLayout()
- logger.d("refreshTime: after requestLayout")
+
+ // Setting text actually triggers a layout pass in TextView (because the text view is set to
+ // wrap_content width and TextView always relayouts for this). This avoids needless relayout
+ // if the text didn't actually change.
+ if (TextUtils.equals(text, formattedText)) {
+ return
}
+
+ text = formattedText
+ logger.d({ "refreshTime: done setting new time text to: $str1" }) {
+ str1 = formattedText?.toString()
+ }
+
+ // Because the TextLayout may mutate under the hood as a result of the new text, we notify
+ // the TextAnimator that it may have changed and request a measure/layout. A crash will
+ // occur on the next invocation of setTextStyle if the layout is mutated without being
+ // notified TextInterpolator being notified.
+ if (layout != null) {
+ textAnimator?.updateLayout(layout)
+ logger.d("refreshTime: done updating textAnimator layout")
+ }
+
+ requestLayout()
+ logger.d("refreshTime: after requestLayout")
}
fun onTimeZoneChanged(timeZone: TimeZone?) {
@@ -206,19 +227,27 @@
@SuppressLint("DrawAllocation")
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
logger.d("onMeasure")
- if (migratedClocks && !isSingleLineInternal &&
- MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) {
+
+ if (
+ migratedClocks &&
+ !isSingleLineInternal &&
+ MeasureSpec.getMode(heightMeasureSpec) == EXACTLY
+ ) {
// Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize
- super.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F))
+ super.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F)
+ )
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val animator = textAnimator
if (animator == null) {
- textAnimator = textAnimatorFactory(layout, ::invalidate)
- onTextAnimatorInitialized?.run()
- onTextAnimatorInitialized = null
+ textAnimator =
+ textAnimatorFactory(layout, ::invalidate)?.also {
+ onTextAnimatorInitialized?.invoke(it)
+ onTextAnimatorInitialized = null
+ }
} else {
animator.updateLayout(layout)
}
@@ -243,15 +272,13 @@
canvas.translate(parentWidth / 4f, 0f)
}
- logger.d({ "onDraw($str1)"}) { str1 = text.toString() }
+ logger.d({ "onDraw($str1)" }) { str1 = text.toString() }
// intentionally doesn't call super.onDraw here or else the text will be rendered twice
textAnimator?.draw(canvas)
canvas.restore()
}
override fun invalidate() {
- @Suppress("UNNECESSARY_SAFE_CALL")
- // logger won't be initialized when called by TextView's constructor
logger.d("invalidate")
super.invalidate()
}
@@ -325,6 +352,7 @@
if (textAnimator == null) {
return
}
+
logger.d("animateFoldAppear")
setTextStyle(
weight = lockScreenWeightInternal,
@@ -348,10 +376,11 @@
}
fun animateCharge(isDozing: () -> Boolean) {
+ // Skip charge animation if dozing animation is already playing.
if (textAnimator == null || textAnimator!!.isRunning()) {
- // Skip charge animation if dozing animation is already playing.
return
}
+
logger.d("animateCharge")
val startAnimPhase2 = Runnable {
setTextStyle(
@@ -409,10 +438,9 @@
/**
* Set text style with an optional animation.
- *
- * By passing -1 to weight, the view preserves its current weight.
- * By passing -1 to textSize, the view preserves its current text size.
- * By passing null to color, the view preserves its current color.
+ * - By passing -1 to weight, the view preserves its current weight.
+ * - By passing -1 to textSize, the view preserves its current text size.
+ * - By passing null to color, the view preserves its current color.
*
* @param weight text weight.
* @param textSize font size.
@@ -428,8 +456,8 @@
delay: Long,
onAnimationEnd: Runnable?
) {
- if (textAnimator != null) {
- textAnimator?.setTextStyle(
+ textAnimator?.let {
+ it.setTextStyle(
weight = weight,
textSize = textSize,
color = color,
@@ -439,23 +467,24 @@
delay = delay,
onAnimationEnd = onAnimationEnd
)
- textAnimator?.glyphFilter = glyphFilter
- } else {
- // when the text animator is set, update its start values
- onTextAnimatorInitialized = Runnable {
- textAnimator?.setTextStyle(
- weight = weight,
- textSize = textSize,
- color = color,
- animate = false,
- duration = duration,
- interpolator = interpolator,
- delay = delay,
- onAnimationEnd = onAnimationEnd
- )
- textAnimator?.glyphFilter = glyphFilter
- }
+ it.glyphFilter = glyphFilter
}
+ ?: run {
+ // when the text animator is set, update its start values
+ onTextAnimatorInitialized = { textAnimator ->
+ textAnimator.setTextStyle(
+ weight = weight,
+ textSize = textSize,
+ color = color,
+ animate = false,
+ duration = duration,
+ interpolator = interpolator,
+ delay = delay,
+ onAnimationEnd = onAnimationEnd
+ )
+ textAnimator.glyphFilter = glyphFilter
+ }
+ }
}
private fun setTextStyle(
@@ -483,12 +512,13 @@
fun refreshFormat(use24HourFormat: Boolean) {
Patterns.update(context)
- format = when {
- isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
- !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR
- isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
- else -> DOUBLE_LINE_FORMAT_12_HOUR
- }
+ format =
+ when {
+ isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
+ !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR
+ isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
+ else -> DOUBLE_LINE_FORMAT_12_HOUR
+ }
logger.d({ "refreshFormat($str1)" }) { str1 = format?.toString() }
descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
@@ -510,10 +540,10 @@
pw.println(" time=$time")
}
- private val moveToCenterDelays
+ private val moveToCenterDelays: List<Int>
get() = if (isLayoutRtl) MOVE_LEFT_DELAYS else MOVE_RIGHT_DELAYS
- private val moveToSideDelays
+ private val moveToSideDelays: List<Int>
get() = if (isLayoutRtl) MOVE_RIGHT_DELAYS else MOVE_LEFT_DELAYS
/**
@@ -531,7 +561,7 @@
fun offsetGlyphsForStepClockAnimation(
clockStartLeft: Int,
clockMoveDirection: Int,
- moveFraction: Float
+ moveFraction: Float,
) {
val isMovingToCenter = if (isLayoutRtl) clockMoveDirection < 0 else clockMoveDirection > 0
val currentMoveAmount = left - clockStartLeft
@@ -558,8 +588,8 @@
*
* @param distance is the total distance in pixels to offset the glyphs when animation
* completes. Negative distance means we are animating the position towards the center.
- * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1
- * means it finished moving.
+ * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
+ * it finished moving.
*/
fun offsetGlyphsForStepClockAnimation(
distance: Float,
@@ -568,13 +598,17 @@
for (i in 0 until NUM_DIGITS) {
val dir = if (isLayoutRtl) -1 else 1
val digitFraction =
- getDigitFraction(digit = i, isMovingToCenter = distance > 0, fraction = fraction)
+ getDigitFraction(
+ digit = i,
+ isMovingToCenter = distance > 0,
+ fraction = fraction,
+ )
val moveAmountForDigit = dir * distance * digitFraction
glyphOffsets[i] = moveAmountForDigit
if (distance > 0) {
- // If distance > 0 then we are moving from the left towards the center.
- // We need ensure that the glyphs are offset to the initial position.
+ // If distance > 0 then we are moving from the left towards the center. We need to
+ // ensure that the glyphs are offset to the initial position.
glyphOffsets[i] -= dir * distance
}
}
@@ -582,27 +616,25 @@
}
private fun getDigitFraction(digit: Int, isMovingToCenter: Boolean, fraction: Float): Float {
- // The delay for the digit, in terms of fraction (i.e. the digit should not move
- // during 0.0 - 0.1).
- val digitInitialDelay =
- if (isMovingToCenter) {
- moveToCenterDelays[digit] * MOVE_DIGIT_STEP
- } else {
- moveToSideDelays[digit] * MOVE_DIGIT_STEP
- }
+ // The delay for the digit, in terms of fraction.
+ // (i.e. the digit should not move during 0.0 - 0.1).
+ val delays = if (isMovingToCenter) moveToCenterDelays else moveToSideDelays
+ val digitInitialDelay = delays[digit] * MOVE_DIGIT_STEP
return MOVE_INTERPOLATOR.getInterpolation(
- constrainedMap(
- 0.0f,
- 1.0f,
- digitInitialDelay,
- digitInitialDelay + AVAILABLE_ANIMATION_TIME,
- fraction,
- )
+ constrainedMap(
+ /* rangeMin= */ 0.0f,
+ /* rangeMax= */ 1.0f,
+ /* valueMin= */ digitInitialDelay,
+ /* valueMax= */ digitInitialDelay + AVAILABLE_ANIMATION_TIME,
+ /* value= */ fraction,
)
+ )
}
- // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
- // This is an optimization to ensure we only recompute the patterns when the inputs change.
+ /**
+ * DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. This
+ * is a cache optimization to ensure we only recompute the patterns when the inputs change.
+ */
private object Patterns {
var sClockView12: String? = null
var sClockView24: String? = null
@@ -610,21 +642,22 @@
fun update(context: Context) {
val locale = Locale.getDefault()
- val res = context.resources
- val clockView12Skel = res.getString(R.string.clock_12hr_format)
- val clockView24Skel = res.getString(R.string.clock_24hr_format)
- val key = locale.toString() + clockView12Skel + clockView24Skel
- if (key == sCacheKey) return
-
- val clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel)
- sClockView12 = clockView12
-
- // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
- // format. The following code removes the AM/PM indicator if we didn't want it.
- if (!clockView12Skel.contains("a")) {
- sClockView12 = clockView12.replace("a".toRegex(), "").trim { it <= ' ' }
+ val clockView12Skel = context.resources.getString(R.string.clock_12hr_format)
+ val clockView24Skel = context.resources.getString(R.string.clock_24hr_format)
+ val key = "$locale$clockView12Skel$clockView24Skel"
+ if (key == sCacheKey) {
+ return
}
+ sClockView12 =
+ DateFormat.getBestDateTimePattern(locale, clockView12Skel).let {
+ // CLDR insists on adding an AM/PM indicator even though it wasn't in the format
+ // string. The following code removes the AM/PM indicator if we didn't want it.
+ if (!clockView12Skel.contains("a")) {
+ it.replace("a".toRegex(), "").trim { it <= ' ' }
+ } else it
+ }
+
sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel)
sCacheKey = key
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 3d8159e..9c9ee53 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -24,7 +24,6 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.util.settings.FakeSettings
-import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -66,7 +65,7 @@
runCurrent()
- Truth.assertThat(actualValue).isFalse()
+ assertThat(actualValue).isFalse()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt
new file mode 100644
index 0000000..ca824cb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.provider.Settings
+import android.testing.LeakCheck
+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.dagger.NightDisplayListenerModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.utils.leaks.FakeLocationController
+import com.google.common.truth.Truth.assertThat
+import java.time.LocalTime
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayRepositoryTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testUser = UserHandle.of(1)!!
+ private val testStartTime = LocalTime.MIDNIGHT
+ private val testEndTime = LocalTime.NOON
+ private val colorDisplayManager =
+ mock<ColorDisplayManager> {
+ whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED)
+ whenever(isNightDisplayActivated).thenReturn(false)
+ whenever(nightDisplayCustomStartTime).thenReturn(testStartTime)
+ whenever(nightDisplayCustomEndTime).thenReturn(testEndTime)
+ }
+ private val locationController = FakeLocationController(LeakCheck())
+ private val nightDisplayListener = mock<NightDisplayListener>()
+ private val listenerBuilder =
+ mock<NightDisplayListenerModule.Builder> {
+ whenever(setUser(ArgumentMatchers.anyInt())).thenReturn(this)
+ whenever(build()).thenReturn(nightDisplayListener)
+ }
+ private val globalSettings = kosmos.fakeGlobalSettings
+ private val secureSettings = kosmos.fakeSettings
+ private val testDispatcher = StandardTestDispatcher()
+ private val scope = TestScope(testDispatcher)
+ private val userScopedColorDisplayManager =
+ mock<UserScopedService<ColorDisplayManager>> {
+ whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager)
+ }
+
+ private val underTest =
+ NightDisplayRepository(
+ testDispatcher,
+ scope.backgroundScope,
+ globalSettings,
+ secureSettings,
+ listenerBuilder,
+ userScopedColorDisplayManager,
+ locationController,
+ )
+
+ @Before
+ fun setup() {
+ enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser)
+ }
+
+ @Test
+ fun nightDisplayState_matchesAutoMode() =
+ scope.runTest {
+ enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser)
+ val callbackCaptor = argumentCaptor<NightDisplayListener.Callback>()
+ val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+
+ runCurrent()
+
+ verify(nightDisplayListener).setCallback(callbackCaptor.capture())
+ val callback = callbackCaptor.value
+
+ assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_DISABLED)
+
+ callback.onAutoModeChanged(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+ assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+
+ callback.onCustomStartTimeChanged(testStartTime)
+ assertThat(lastState!!.startTime).isEqualTo(testStartTime)
+
+ callback.onCustomEndTimeChanged(testEndTime)
+ assertThat(lastState!!.endTime).isEqualTo(testEndTime)
+
+ callback.onAutoModeChanged(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+
+ assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+ }
+
+ @Test
+ fun nightDisplayState_matchesIsNightDisplayActivated() =
+ scope.runTest {
+ val callbackCaptor = argumentCaptor<NightDisplayListener.Callback>()
+
+ val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+ runCurrent()
+
+ verify(nightDisplayListener).setCallback(callbackCaptor.capture())
+ val callback = callbackCaptor.value
+ assertThat(lastState!!.isActivated)
+ .isEqualTo(colorDisplayManager.isNightDisplayActivated)
+
+ callback.onActivated(true)
+ assertThat(lastState!!.isActivated).isTrue()
+
+ callback.onActivated(false)
+ assertThat(lastState!!.isActivated).isFalse()
+ }
+
+ @Test
+ fun nightDisplayState_matchesController_initiallyCustomAutoMode() =
+ scope.runTest {
+ whenever(colorDisplayManager.nightDisplayAutoMode)
+ .thenReturn(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+
+ val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+ runCurrent()
+
+ assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME)
+ }
+
+ @Test
+ fun nightDisplayState_matchesController_initiallyTwilightAutoMode() =
+ scope.runTest {
+ whenever(colorDisplayManager.nightDisplayAutoMode)
+ .thenReturn(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+
+ val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+ runCurrent()
+
+ assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_TWILIGHT)
+ }
+
+ @Test
+ fun nightDisplayState_matchesForceAutoMode() =
+ scope.runTest {
+ enrollInForcedNightDisplayAutoMode(false, testUser)
+ val lastState by collectLastValue(underTest.nightDisplayState(testUser))
+ runCurrent()
+
+ assertThat(lastState!!.shouldForceAutoMode).isEqualTo(false)
+
+ enrollInForcedNightDisplayAutoMode(true, testUser)
+ assertThat(lastState!!.shouldForceAutoMode).isEqualTo(true)
+ }
+
+ private fun enrollInForcedNightDisplayAutoMode(enroll: Boolean, userHandle: UserHandle) {
+ globalSettings.putString(
+ Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
+ if (enroll) NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE
+ else NIGHT_DISPLAY_FORCED_AUTO_MODE_UNAVAILABLE
+ )
+ secureSettings.putIntForUser(
+ Settings.Secure.NIGHT_DISPLAY_AUTO_MODE,
+ if (enroll) NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET else NIGHT_DISPLAY_AUTO_MODE_RAW_SET,
+ userHandle.identifier
+ )
+ }
+
+ private companion object {
+ const val INITIALLY_FORCE_AUTO_MODE = false
+ const val NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE = "1"
+ const val NIGHT_DISPLAY_FORCED_AUTO_MODE_UNAVAILABLE = "0"
+ const val NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET = -1
+ const val NIGHT_DISPLAY_AUTO_MODE_RAW_SET = 0
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
index 02d927a..f9d5073 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt
@@ -86,4 +86,26 @@
)
assertThat(isUmoOnCommunal).isFalse()
}
+
+ @Test
+ fun testIsUmoOnCommunalDuringTransitionBetweenOccludedAndGlanceableHub() =
+ testScope.runTest {
+ val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal)
+ assertThat(isUmoOnCommunal).isNull()
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope
+ )
+ assertThat(isUmoOnCommunal).isTrue()
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+
+ assertThat(isUmoOnCommunal).isFalse()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 9d6a66d..1f8cb8a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -90,7 +90,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var user: UserInfo
@Mock private lateinit var providerInfo: AppWidgetProviderInfo
@@ -111,7 +111,7 @@
private lateinit var underTest: CommunalViewModel
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 2b3f40f..e3dd9ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -170,7 +170,7 @@
@Test
public void testBurnInProtectionStopsWhenContentViewDetached() {
mController.onViewDetached();
- verify(mHandler).removeCallbacks(any(Runnable.class));
+ verify(mHandler).removeCallbacksAndMessages(null);
}
@Test
@@ -281,4 +281,16 @@
verify(mAnimationsController).cancelAnimations();
}
+
+ @Test
+ public void onViewAttached_addsScrimExpansionCallback() {
+ mController.onViewAttached();
+ verify(mBouncerlessScrimController).addCallback(any());
+ }
+
+ @Test
+ public void onViewDetached_removesScrimExpansionCallback() {
+ mController.onViewDetached();
+ verify(mBouncerlessScrimController).removeCallback(any());
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index 3b6f6a2..f31eb7f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -58,7 +58,7 @@
@ExperimentalCoroutinesApi
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class UdfpsKeyguardInteractorTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class UdfpsKeyguardInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
val keyguardRepository = kosmos.fakeKeyguardRepository
@@ -88,7 +88,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
index f52c66e..cde703b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
@@ -43,7 +43,7 @@
@ExperimentalCoroutinesApi
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
val repository = kosmos.fakeKeyguardTransitionRepository
@@ -60,7 +60,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
index fee18dd..d632936 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -16,13 +16,15 @@
package com.android.systemui.keyguard.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -31,29 +33,25 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BouncerToGoneFlowsTest : SysuiTestCase() {
- @Mock private lateinit var shadeInteractor: ShadeInteractor
-
- private val shadeExpansionStateFlow = MutableStateFlow(0.1f)
+@RunWith(ParameterizedAndroidJunit4::class)
+class BouncerToGoneFlowsTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
@@ -61,16 +59,31 @@
}
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val shadeRepository = kosmos.shadeRepository
private val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
private val primaryBouncerInteractor = kosmos.mockPrimaryBouncerInteractor
- private val underTest = kosmos.bouncerToGoneFlows
+
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+ private lateinit var underTest: BouncerToGoneFlows
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(false)
+ underTest = kosmos.bouncerToGoneFlows
}
@Test
@@ -79,7 +92,7 @@
val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
runCurrent()
- shadeRepository.setLockscreenShadeExpansion(1f)
+ shadeTestUtil.setLockscreenShadeExpansion(1f)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
keyguardTransitionRepository.sendTransitionSteps(
@@ -99,12 +112,13 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun scrimAlpha_runDimissFromKeyguard_shadeNotExpanded() =
testScope.runTest {
val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
runCurrent()
- shadeRepository.setLockscreenShadeExpansion(0f)
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 2e1765a..838b2a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -60,7 +60,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardRootViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
@@ -75,7 +75,6 @@
private val viewState = ViewStateAccessor()
- // add to init block
companion object {
@JvmStatic
@Parameters(name = "{0}")
@@ -85,7 +84,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index ec2cb04..de4b999 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -47,7 +47,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenContentViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos: Kosmos = testKosmos()
@@ -62,7 +62,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
index e3ae3ba..bc381f2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
@@ -49,7 +49,7 @@
@ExperimentalCoroutinesApi
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) }
@@ -73,7 +73,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index adeb395..9337793 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -51,7 +51,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
@@ -73,7 +73,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index f8da74f..6ce7e88 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -54,7 +54,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
@@ -76,7 +76,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index d5df159..58c6817 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -46,7 +46,7 @@
@ExperimentalCoroutinesApi
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization?) :
+class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization) :
SysuiTestCase() {
private val kosmos =
testKosmos().apply {
@@ -67,7 +67,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt
new file mode 100644
index 0000000..a0aa2d4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.interactor
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.time.DateFormatUtil
+import com.android.systemui.utils.leaks.FakeLocationController
+import com.google.common.truth.Truth.assertThat
+import java.time.LocalTime
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayTileDataInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testUser = UserHandle.of(1)!!
+ private val testStartTime = LocalTime.MIDNIGHT
+ private val testEndTime = LocalTime.NOON
+ private val colorDisplayManager =
+ mock<ColorDisplayManager> {
+ whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED)
+ whenever(isNightDisplayActivated).thenReturn(false)
+ whenever(nightDisplayCustomStartTime).thenReturn(testStartTime)
+ whenever(nightDisplayCustomEndTime).thenReturn(testEndTime)
+ }
+ private val locationController = FakeLocationController(LeakCheck())
+ private val nightDisplayListener = mock<NightDisplayListener>()
+ private val listenerBuilder =
+ mock<NightDisplayListenerModule.Builder> {
+ whenever(setUser(anyInt())).thenReturn(this)
+ whenever(build()).thenReturn(nightDisplayListener)
+ }
+ private val globalSettings = kosmos.fakeGlobalSettings
+ private val secureSettings = kosmos.fakeSettings
+ private val dateFormatUtil = mock<DateFormatUtil> { whenever(is24HourFormat).thenReturn(false) }
+ private val testDispatcher = StandardTestDispatcher()
+ private val scope = TestScope(testDispatcher)
+ private val userScopedColorDisplayManager =
+ mock<UserScopedService<ColorDisplayManager>> {
+ whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager)
+ }
+ private val nightDisplayRepository =
+ NightDisplayRepository(
+ testDispatcher,
+ scope.backgroundScope,
+ globalSettings,
+ secureSettings,
+ listenerBuilder,
+ userScopedColorDisplayManager,
+ locationController,
+ )
+
+ private val underTest: NightDisplayTileDataInteractor =
+ NightDisplayTileDataInteractor(context, dateFormatUtil, nightDisplayRepository)
+
+ @Test
+ fun availability_matchesColorDisplayManager() = runTest {
+ val availability by collectLastValue(underTest.availability(testUser))
+
+ val expectedAvailability = ColorDisplayManager.isNightDisplayAvailable(context)
+ assertThat(availability).isEqualTo(expectedAvailability)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..adc8bcb
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.interactor
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.utils.leaks.FakeLocationController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayTileUserActionInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
+ private val testUser = UserHandle.of(1)
+ private val colorDisplayManager =
+ mock<ColorDisplayManager> {
+ whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED)
+ whenever(isNightDisplayActivated).thenReturn(false)
+ }
+ private val locationController = FakeLocationController(LeakCheck())
+ private val nightDisplayListener = mock<NightDisplayListener>()
+ private val listenerBuilder =
+ mock<NightDisplayListenerModule.Builder> {
+ whenever(setUser(ArgumentMatchers.anyInt())).thenReturn(this)
+ whenever(build()).thenReturn(nightDisplayListener)
+ }
+ private val globalSettings = kosmos.fakeGlobalSettings
+ private val secureSettings = kosmos.fakeSettings
+ private val testDispatcher = StandardTestDispatcher()
+ private val scope = TestScope(testDispatcher)
+ private val userScopedColorDisplayManager =
+ mock<UserScopedService<ColorDisplayManager>> {
+ whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager)
+ }
+ private val nightDisplayRepository =
+ NightDisplayRepository(
+ testDispatcher,
+ scope.backgroundScope,
+ globalSettings,
+ secureSettings,
+ listenerBuilder,
+ userScopedColorDisplayManager,
+ locationController,
+ )
+
+ private val underTest =
+ NightDisplayTileUserActionInteractor(
+ nightDisplayRepository,
+ qsTileIntentUserActionHandler,
+ kosmos.qsTileLogger
+ )
+
+ @Test
+ fun handleClick_inactive_activates() =
+ scope.runTest {
+ val startingModel = NightDisplayTileModel.AutoModeOff(false, false)
+
+ underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+ verify(colorDisplayManager).setNightDisplayActivated(true)
+ }
+
+ @Test
+ fun handleClick_active_disables() =
+ scope.runTest {
+ val startingModel = NightDisplayTileModel.AutoModeOff(true, false)
+
+ underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+ verify(colorDisplayManager).setNightDisplayActivated(false)
+ }
+
+ @Test
+ fun handleClick_whenAutoModeTwilight_flipsState() =
+ scope.runTest {
+ val originalState = true
+ val startingModel = NightDisplayTileModel.AutoModeTwilight(originalState, false, false)
+
+ underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+ verify(colorDisplayManager).setNightDisplayActivated(!originalState)
+ }
+
+ @Test
+ fun handleClick_whenAutoModeCustom_flipsState() =
+ scope.runTest {
+ val originalState = true
+ val startingModel =
+ NightDisplayTileModel.AutoModeCustom(originalState, false, null, null, false)
+
+ underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser))
+
+ verify(colorDisplayManager).setNightDisplayActivated(!originalState)
+ }
+
+ @Test
+ fun handleLongClickWhenEnabled() =
+ scope.runTest {
+ val enabledState = true
+
+ underTest.handleInput(
+ QSTileInputTestKtx.longClick(
+ NightDisplayTileModel.AutoModeOff(enabledState, false),
+ testUser
+ )
+ )
+
+ assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+
+ val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+ val actualIntentAction = intentInput.intent.action
+ val expectedIntentAction = Settings.ACTION_NIGHT_DISPLAY_SETTINGS
+ assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+ }
+
+ @Test
+ fun handleLongClickWhenDisabled() =
+ scope.runTest {
+ val enabledState = false
+
+ underTest.handleInput(
+ QSTileInputTestKtx.longClick(
+ NightDisplayTileModel.AutoModeOff(enabledState, false),
+ testUser
+ )
+ )
+
+ assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
+
+ val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
+ val actualIntentAction = intentInput.intent.action
+ val expectedIntentAction = Settings.ACTION_NIGHT_DISPLAY_SETTINGS
+ assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
new file mode 100644
index 0000000..5d2e701
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.impl.night.qsNightDisplayTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.mock
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NightDisplayTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val config = kosmos.qsNightDisplayTileConfig
+
+ private val testStartTime = LocalTime.MIDNIGHT
+ private val testEndTime = LocalTime.NOON
+
+ private lateinit var mapper: NightDisplayTileMapper
+
+ @Before
+ fun setup() {
+ mapper =
+ NightDisplayTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.qs_nightlight_icon_on, TestStubDrawable())
+ addOverride(R.drawable.qs_nightlight_icon_off, TestStubDrawable())
+ }
+ .resources,
+ context.theme,
+ mock<QSTileLogger>(),
+ )
+ }
+
+ @Test
+ fun disabledModel_whenAutoModeOff() {
+ val inputModel = NightDisplayTileModel.AutoModeOff(false, false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ /** Force enable does not change the mode by itself. */
+ @Test
+ fun disabledModel_whenAutoModeOff_whenForceEnable() {
+ val inputModel = NightDisplayTileModel.AutoModeOff(false, true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel_whenAutoModeOff() {
+ val inputModel = NightDisplayTileModel.AutoModeOff(true, false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel_forceAutoMode_whenAutoModeOff() {
+ val inputModel = NightDisplayTileModel.AutoModeOff(true, true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel_autoModeTwilight_locationOff() {
+ val inputModel = NightDisplayTileModel.AutoModeTwilight(true, false, false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState = createNightDisplayTileState(QSTileState.ActivationState.ACTIVE, null)
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel_autoModeTwilight_locationOn() {
+ val inputModel = NightDisplayTileModel.AutoModeTwilight(true, false, true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.quick_settings_night_secondary_label_until_sunrise)
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun disabledModel_autoModeTwilight_locationOn() {
+ val inputModel = NightDisplayTileModel.AutoModeTwilight(false, false, true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset)
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun disabledModel_autoModeTwilight_locationOff() {
+ val inputModel = NightDisplayTileModel.AutoModeTwilight(false, false, false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState = createNightDisplayTileState(QSTileState.ActivationState.INACTIVE, null)
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun disabledModel_autoModeCustom_24Hour() {
+ val inputModel =
+ NightDisplayTileModel.AutoModeCustom(false, false, testStartTime, null, true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(
+ R.string.quick_settings_night_secondary_label_on_at,
+ formatter24Hour.format(testStartTime)
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun disabledModel_autoModeCustom_12Hour() {
+ val inputModel =
+ NightDisplayTileModel.AutoModeCustom(false, false, testStartTime, null, false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(
+ R.string.quick_settings_night_secondary_label_on_at,
+ formatter12Hour.format(testStartTime)
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ /** Should have the same outcome as [disabledModel_autoModeCustom_12Hour] */
+ @Test
+ fun disabledModel_autoModeCustom_12Hour_isEnrolledForcedAutoMode() {
+ val inputModel =
+ NightDisplayTileModel.AutoModeCustom(false, true, testStartTime, null, false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(
+ R.string.quick_settings_night_secondary_label_on_at,
+ formatter12Hour.format(testStartTime)
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel_autoModeCustom_24Hour() {
+ val inputModel = NightDisplayTileModel.AutoModeCustom(true, false, null, testEndTime, true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(
+ R.string.quick_settings_secondary_label_until,
+ formatter24Hour.format(testEndTime)
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel_autoModeCustom_12Hour() {
+ val inputModel = NightDisplayTileModel.AutoModeCustom(true, false, null, testEndTime, false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(
+ R.string.quick_settings_secondary_label_until,
+ formatter12Hour.format(testEndTime)
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ /** Should have the same state as [enabledModel_autoModeCustom_24Hour] */
+ @Test
+ fun enabledModel_autoModeCustom_24Hour_forceEnabled() {
+ val inputModel = NightDisplayTileModel.AutoModeCustom(true, true, null, testEndTime, true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createNightDisplayTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(
+ R.string.quick_settings_secondary_label_until,
+ formatter24Hour.format(testEndTime)
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createNightDisplayTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String?
+ ): QSTileState {
+ val label = context.getString(R.string.quick_settings_night_display_label)
+
+ val contentDescription =
+ if (TextUtils.isEmpty(secondaryLabel)) label
+ else TextUtils.concat(label, ", ", secondaryLabel)
+ return QSTileState(
+ {
+ Icon.Loaded(
+ context.getDrawable(
+ if (activationState == QSTileState.ActivationState.ACTIVE)
+ R.drawable.qs_nightlight_icon_on
+ else R.drawable.qs_nightlight_icon_off
+ )!!,
+ null
+ )
+ },
+ label,
+ activationState,
+ secondaryLabel,
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ contentDescription,
+ null,
+ QSTileState.SideViewIcon.None,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+
+ private companion object {
+ val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
+ val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index aa0ca18..78c4def 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -60,7 +60,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class ShadeInteractorImplTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class ShadeInteractorImplTest(flags: FlagsParameterization) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
@@ -85,7 +85,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
index 44c9695..cecc70c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -60,7 +60,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class ShadeStartableTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class ShadeStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val shadeInteractor by lazy { kosmos.shadeInteractor }
@@ -80,7 +80,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index d353a62..f06e04b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -33,7 +33,10 @@
import com.android.systemui.flags.parameterizeSceneContainerFlag
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -65,11 +68,11 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@TestableLooper.RunWithLooper
-class StatusBarStateControllerImplTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
-
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val mockDarkAnimator = mock<ObjectAnimator>()
private lateinit var underTest: StatusBarStateControllerImpl
@@ -84,7 +87,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
@@ -98,6 +101,7 @@
uiEventLogger,
{ kosmos.interactionJankMonitor },
JavaAdapter(testScope.backgroundScope),
+ { kosmos.keyguardTransitionInteractor },
{ kosmos.shadeInteractor },
{ kosmos.deviceUnlockedInteractor },
{ kosmos.sceneInteractor },
@@ -330,4 +334,25 @@
assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
}
+
+ @Test
+ fun leaveOpenOnKeyguard_whenGone_isFalse() =
+ testScope.runTest {
+ underTest.start()
+ underTest.setLeaveOpenOnKeyguardHide(true)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
+ assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(true)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+ assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(false)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index cbbc4d8..9367a93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -59,7 +59,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterization?) :
+class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterization) :
SysuiTestCase() {
companion object {
@@ -71,7 +71,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 7ac549a..cc5df74 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -20,12 +20,13 @@
import android.app.NotificationManager.Policy
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -33,7 +34,7 @@
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.res.R
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
@@ -56,11 +57,13 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@EnableFlags(FooterViewRefactor.FLAG_NAME)
-class NotificationListViewModelTest : SysuiTestCase() {
+class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
@@ -72,16 +75,30 @@
private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
private val fakePowerRepository = kosmos.fakePowerRepository
private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository
- private val fakeShadeRepository = kosmos.fakeShadeRepository
private val fakeUserSetupRepository = kosmos.fakeUserSetupRepository
private val headsUpRepository = kosmos.headsUpNotificationRepository
private val zenModeRepository = kosmos.zenModeRepository
- val underTest = kosmos.notificationListViewModel
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+ private lateinit var underTest: NotificationListViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ underTest = kosmos.notificationListViewModel
}
@Test
@@ -163,7 +180,7 @@
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND quick settings are expanded
- fakeShadeRepository.legacyQsFullscreen.value = true
+ shadeTestUtil.setQsFullscreen(true)
runCurrent()
// THEN empty shade is not visible
@@ -178,9 +195,10 @@
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
// AND quick settings are expanded
- fakeShadeRepository.setQsExpansion(1f)
- // AND split shade is enabled
+ shadeTestUtil.setQsExpansion(1f)
+ // AND split shade is expanded
overrideResource(R.bool.config_use_split_notification_shade, true)
+ shadeTestUtil.setShadeExpansion(1f)
fakeConfigurationController.notifyConfigurationChanged()
runCurrent()
@@ -290,7 +308,7 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND shade is open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
runCurrent()
// THEN footer is visible
@@ -306,7 +324,7 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND shade is open on lockscreen
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
runCurrent()
// THEN footer is visible
@@ -337,7 +355,7 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND shade is open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
// AND user is not set up
fakeUserSetupRepository.setUserSetUp(false)
runCurrent()
@@ -355,7 +373,7 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND shade is open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
// AND device is starting to go to sleep
fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
runCurrent()
@@ -373,10 +391,10 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND shade is open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
// AND quick settings are expanded
- fakeShadeRepository.setQsExpansion(1f)
- fakeShadeRepository.legacyQsFullscreen.value = true
+ shadeTestUtil.setQsExpansion(1f)
+ shadeTestUtil.setQsFullscreen(true)
runCurrent()
// THEN footer is not visible
@@ -390,11 +408,11 @@
// WHEN has notifs
activeNotificationListRepository.setActiveNotifs(count = 2)
+ // AND quick settings are expanded
+ shadeTestUtil.setQsExpansion(1f)
// AND shade is open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
- // AND quick settings are expanded
- fakeShadeRepository.setQsExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
// AND split shade is enabled
overrideResource(R.bool.config_use_split_notification_shade, true)
fakeConfigurationController.notifyConfigurationChanged()
@@ -413,7 +431,7 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND shade is open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
// AND remote input is active
fakeRemoteInputRepository.isRemoteInputActive.value = true
runCurrent()
@@ -431,7 +449,7 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND shade is open and fully expanded
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
runCurrent()
// THEN footer visibility animates
@@ -447,7 +465,7 @@
activeNotificationListRepository.setActiveNotifs(count = 2)
// AND we are on the keyguard
fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
runCurrent()
// THEN footer visibility does not animate
@@ -461,7 +479,7 @@
// WHEN shade is closed
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(0f)
+ shadeTestUtil.setShadeExpansion(0f)
runCurrent()
// THEN footer is hidden
@@ -475,7 +493,7 @@
// WHEN shade is open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setShadeExpansion(1f)
runCurrent()
// THEN footer is hidden
@@ -489,8 +507,8 @@
// WHEN QS partially open
fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
- fakeShadeRepository.setQsExpansion(0.5f)
- fakeShadeRepository.setLegacyShadeExpansion(0.5f)
+ shadeTestUtil.setQsExpansion(0.5f)
+ shadeTestUtil.setShadeExpansion(0.5f)
runCurrent()
// THEN footer is hidden
@@ -588,7 +606,7 @@
testScope.runTest {
val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
- fakeShadeRepository.setQsExpansion(0.0f)
+ shadeTestUtil.setQsExpansion(0.0f)
fakeKeyguardRepository.setKeyguardShowing(false)
runCurrent()
@@ -601,7 +619,7 @@
testScope.runTest {
val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
- fakeShadeRepository.setQsExpansion(0.0f)
+ shadeTestUtil.setQsExpansion(0.0f)
fakeKeyguardRepository.setKeyguardShowing(true)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 2cd295c..f2ce745 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -75,7 +75,7 @@
@RunWith(ParameterizedAndroidJunit4::class)
// SharedNotificationContainerViewModel is only bound when FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT is on
@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
companion object {
@JvmStatic
@@ -89,7 +89,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 8ce5037..63f19fb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -249,22 +249,40 @@
@Test
- fun testDelete_showingEntryKeyBecomesPreviousHunKey() {
+ fun testDelete_deleteSecondToLastEntry_showingEntryKeyBecomesPreviousHunKey() {
mAvalancheController.previousHunKey = ""
// Entry is showing
+ val firstEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = firstEntry
+
+ // There's another entry waiting to show next
+ val secondEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.addToNext(secondEntry, runnableMock!!)
+
+ // Delete
+ mAvalancheController.delete(firstEntry, runnableMock, "testLabel")
+
+ // Next entry is shown
+ assertThat(mAvalancheController.previousHunKey).isEqualTo(firstEntry.mEntry!!.key)
+ }
+
+ @Test
+ fun testDelete_deleteLastEntry_previousHunKeyCleared() {
+ mAvalancheController.previousHunKey = "key"
+
+ // Nothing waiting to show
+ mAvalancheController.clearNext()
+
+ // One entry is showing
val showingEntry = createHeadsUpEntry(id = 0)
mAvalancheController.headsUpEntryShowing = showingEntry
- // There's another entry waiting to show next
- val nextEntry = createHeadsUpEntry(id = 1)
- mAvalancheController.addToNext(nextEntry, runnableMock!!)
-
// Delete
- mAvalancheController.delete(showingEntry, runnableMock, "testLabel")
+ mAvalancheController.delete(showingEntry, runnableMock!!, "testLabel")
// Next entry is shown
- assertThat(mAvalancheController.previousHunKey).isEqualTo(showingEntry.mEntry!!.key)
+ assertThat(mAvalancheController.previousHunKey).isEqualTo("");
}
@Test
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a1daebd..5857692 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -285,6 +285,9 @@
the amount by the view is positioned above the screen before the animation starts. -->
<dimen name="heads_up_appear_y_above_screen">32dp</dimen>
+ <!-- padding between the old and new heads up notifications for the hun cycling animation -->
+ <dimen name="heads_up_cycling_padding">8dp</dimen>
+
<!-- padding between the heads up and the statusbar -->
<dimen name="heads_up_status_bar_padding">8dp</dimen>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
index 422f02f..8979ef1 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
@@ -16,7 +16,6 @@
package com.android.systemui.biometrics
import android.Manifest
-import android.annotation.IntDef
import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX
@@ -39,14 +38,9 @@
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
import com.android.internal.widget.LockPatternUtils
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy
+import com.android.systemui.biometrics.shared.model.PromptKind
object Utils {
- const val CREDENTIAL_PIN = 1
- const val CREDENTIAL_PATTERN = 2
- const val CREDENTIAL_PASSWORD = 3
-
/** Base set of layout flags for fingerprint overlay widgets. */
const val FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS =
(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
@@ -91,17 +85,16 @@
(promptInfo.authenticators and Authenticators.BIOMETRIC_WEAK) != 0
@JvmStatic
- @CredentialType
- fun getCredentialType(utils: LockPatternUtils, userId: Int): Int =
+ fun getCredentialType(utils: LockPatternUtils, userId: Int): PromptKind =
when (utils.getKeyguardStoredPasswordQuality(userId)) {
- PASSWORD_QUALITY_SOMETHING -> CREDENTIAL_PATTERN
+ PASSWORD_QUALITY_SOMETHING -> PromptKind.Pattern
PASSWORD_QUALITY_NUMERIC,
- PASSWORD_QUALITY_NUMERIC_COMPLEX -> CREDENTIAL_PIN
+ PASSWORD_QUALITY_NUMERIC_COMPLEX -> PromptKind.Pin
PASSWORD_QUALITY_ALPHABETIC,
PASSWORD_QUALITY_ALPHANUMERIC,
PASSWORD_QUALITY_COMPLEX,
- PASSWORD_QUALITY_MANAGED -> CREDENTIAL_PASSWORD
- else -> CREDENTIAL_PASSWORD
+ PASSWORD_QUALITY_MANAGED -> PromptKind.Password
+ else -> PromptKind.Password
}
@JvmStatic
@@ -129,8 +122,4 @@
return windowMetrics?.windowInsets?.getInsets(WindowInsets.Type.navigationBars())
?: Insets.NONE
}
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD)
- annotation class CredentialType
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
similarity index 69%
rename from packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
rename to packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
index a97e2dc..8782962 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,16 +16,20 @@
package com.android.systemui.biometrics.shared.model
-import com.android.systemui.biometrics.Utils
-
-// TODO(b/251476085): this should eventually replace Utils.CredentialType
-/** Credential options for biometric prompt. Shadows [Utils.CredentialType]. */
sealed interface PromptKind {
+ object None : PromptKind
+
data class Biometric(
val activeModalities: BiometricModalities = BiometricModalities(),
+ // TODO(b/330908557): Use this value to decide whether to show two pane layout, instead of
+ // simply depending on rotations.
+ val showTwoPane: Boolean = false
) : PromptKind
object Pin : PromptKind
object Pattern : PromptKind
object Password : PromptKind
+
+ fun isBiometric() = this is Biometric
+ fun isCredential() = (this is Pin) or (this is Pattern) or (this is Password)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 460779c..f33acf2 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -43,6 +43,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -432,6 +433,7 @@
listenForDozeAmountTransition(this)
listenForAnyStateToAodTransition(this)
listenForAnyStateToLockscreenTransition(this)
+ listenForAnyStateToDozingTransition(this)
} else {
listenForDozeAmount(this)
}
@@ -578,6 +580,21 @@
}
}
+ /**
+ * When keyguard is displayed due to pulsing notifications when AOD is off,
+ * we should make sure clock is in dozing state instead of LS state
+ */
+ @VisibleForTesting
+ internal fun listenForAnyStateToDozingTransition(scope: CoroutineScope): Job {
+ return scope.launch {
+ keyguardTransitionInteractor
+ .transitionStepsToState(DOZING)
+ .filter { it.transitionState == TransitionState.FINISHED }
+ .collect { handleDoze(1f) }
+ }
+ }
+
+
@VisibleForTesting
internal fun listenForDozing(scope: CoroutineScope): Job {
return scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt
new file mode 100644
index 0000000..8f071e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.model
+
+import java.time.LocalTime
+
+sealed interface NightDisplayChangeEvent {
+ data class OnAutoModeChanged(val autoMode: Int) : NightDisplayChangeEvent
+ data class OnActivatedChanged(val isActivated: Boolean) : NightDisplayChangeEvent
+ data class OnCustomStartTimeChanged(val startTime: LocalTime?) : NightDisplayChangeEvent
+ data class OnCustomEndTimeChanged(val endTime: LocalTime?) : NightDisplayChangeEvent
+ data class OnForceAutoModeChanged(val shouldForceAutoMode: Boolean) : NightDisplayChangeEvent
+ data class OnLocationEnabledChanged(val locationEnabled: Boolean) : NightDisplayChangeEvent
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt
new file mode 100644
index 0000000..196876e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.model
+
+import java.time.LocalTime
+
+/** models the state of NightDisplayRepository */
+data class NightDisplayState(
+ val autoMode: Int = 0,
+ val isActivated: Boolean = true,
+ val startTime: LocalTime? = null,
+ val endTime: LocalTime? = null,
+ val shouldForceAutoMode: Boolean = false,
+ val locationEnabled: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
index ae9f57f..6032f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt
@@ -18,7 +18,7 @@
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.channels.awaitClose
@@ -29,6 +29,8 @@
interface AccessibilityRepository {
/** @see [AccessibilityManager.isTouchExplorationEnabled] */
val isTouchExplorationEnabled: Flow<Boolean>
+ /** @see [AccessibilityManager.isEnabled] */
+ val isEnabled: Flow<Boolean>
companion object {
operator fun invoke(a11yManager: AccessibilityManager): AccessibilityRepository =
@@ -47,6 +49,15 @@
awaitClose { manager.removeTouchExplorationStateChangeListener(listener) }
}
.distinctUntilChanged()
+
+ override val isEnabled: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val listener = AccessibilityManager.AccessibilityStateChangeListener(::trySend)
+ manager.addAccessibilityStateChangeListener(listener)
+ trySend(manager.isEnabled)
+ awaitClose { manager.removeAccessibilityStateChangeListener(listener) }
+ }
+ .distinctUntilChanged()
}
@Module
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt
new file mode 100644
index 0000000..bf44fab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.systemui.accessibility.data.model.NightDisplayChangeEvent
+import com.android.systemui.accessibility.data.model.NightDisplayState
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.util.kotlin.isLocationEnabledFlow
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import java.time.LocalTime
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.scan
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+class NightDisplayRepository
+@Inject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ @Application private val scope: CoroutineScope,
+ private val globalSettings: GlobalSettings,
+ private val secureSettings: SecureSettings,
+ private val nightDisplayListenerBuilder: NightDisplayListenerModule.Builder,
+ private val colorDisplayManagerUserScopedService: UserScopedService<ColorDisplayManager>,
+ private val locationController: LocationController,
+) {
+ private val stateFlowUserMap = mutableMapOf<Int, Flow<NightDisplayState>>()
+
+ fun nightDisplayState(user: UserHandle): Flow<NightDisplayState> =
+ stateFlowUserMap.getOrPut(user.identifier) {
+ return merge(
+ colorDisplayManagerChangeEventFlow(user),
+ shouldForceAutoMode(user).map {
+ NightDisplayChangeEvent.OnForceAutoModeChanged(it)
+ },
+ locationController.isLocationEnabledFlow().map {
+ NightDisplayChangeEvent.OnLocationEnabledChanged(it)
+ }
+ )
+ .scan(initialState(user)) { state, event ->
+ when (event) {
+ is NightDisplayChangeEvent.OnActivatedChanged ->
+ state.copy(isActivated = event.isActivated)
+ is NightDisplayChangeEvent.OnAutoModeChanged ->
+ state.copy(autoMode = event.autoMode)
+ is NightDisplayChangeEvent.OnCustomStartTimeChanged ->
+ state.copy(startTime = event.startTime)
+ is NightDisplayChangeEvent.OnCustomEndTimeChanged ->
+ state.copy(endTime = event.endTime)
+ is NightDisplayChangeEvent.OnForceAutoModeChanged ->
+ state.copy(shouldForceAutoMode = event.shouldForceAutoMode)
+ is NightDisplayChangeEvent.OnLocationEnabledChanged ->
+ state.copy(locationEnabled = event.locationEnabled)
+ }
+ }
+ .conflate()
+ .onStart { emit(initialState(user)) }
+ .flowOn(bgCoroutineContext)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), NightDisplayState())
+ }
+
+ /** Track changes in night display enabled state and its auto mode */
+ private fun colorDisplayManagerChangeEventFlow(user: UserHandle) = callbackFlow {
+ val nightDisplayListener = nightDisplayListenerBuilder.setUser(user.identifier).build()
+ val nightDisplayCallback =
+ object : NightDisplayListener.Callback {
+ override fun onActivated(activated: Boolean) {
+ trySend(NightDisplayChangeEvent.OnActivatedChanged(activated))
+ }
+
+ override fun onAutoModeChanged(autoMode: Int) {
+ trySend(NightDisplayChangeEvent.OnAutoModeChanged(autoMode))
+ }
+
+ override fun onCustomStartTimeChanged(startTime: LocalTime?) {
+ trySend(NightDisplayChangeEvent.OnCustomStartTimeChanged(startTime))
+ }
+
+ override fun onCustomEndTimeChanged(endTime: LocalTime?) {
+ trySend(NightDisplayChangeEvent.OnCustomEndTimeChanged(endTime))
+ }
+ }
+ nightDisplayListener.setCallback(nightDisplayCallback)
+ awaitClose { nightDisplayListener.setCallback(null) }
+ }
+
+ /** @return true when the option to force auto mode is available and a value has not been set */
+ private fun shouldForceAutoMode(userHandle: UserHandle): Flow<Boolean> =
+ combine(isForceAutoModeAvailable, isDisplayAutoModeRawNotSet(userHandle)) {
+ isForceAutoModeAvailable,
+ isDisplayAutoModeRawNotSet,
+ ->
+ isForceAutoModeAvailable && isDisplayAutoModeRawNotSet
+ }
+
+ private val isForceAutoModeAvailable: Flow<Boolean> =
+ globalSettings
+ .observerFlow(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME)
+ .onStart { emit(Unit) }
+ .map {
+ globalSettings.getString(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) ==
+ NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE
+ }
+ .distinctUntilChanged()
+
+ /** Inspired by [ColorDisplayService.getNightDisplayAutoModeRawInternal] */
+ private fun isDisplayAutoModeRawNotSet(userHandle: UserHandle): Flow<Boolean> =
+ if (userHandle.identifier == UserHandle.USER_NULL) {
+ flowOf(IS_AUTO_MODE_RAW_NOT_SET_DEFAULT)
+ } else {
+ secureSettings
+ .observerFlow(userHandle.identifier, DISPLAY_AUTO_MODE_RAW_SETTING_NAME)
+ .onStart { emit(Unit) }
+ .map {
+ secureSettings.getIntForUser(
+ DISPLAY_AUTO_MODE_RAW_SETTING_NAME,
+ userHandle.identifier
+ ) == NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET
+ }
+ }
+ .distinctUntilChanged()
+
+ suspend fun setNightDisplayAutoMode(autoMode: Int, user: UserHandle) {
+ withContext(bgCoroutineContext) {
+ colorDisplayManagerUserScopedService.forUser(user).nightDisplayAutoMode = autoMode
+ }
+ }
+
+ suspend fun setNightDisplayActivated(activated: Boolean, user: UserHandle) {
+ withContext(bgCoroutineContext) {
+ colorDisplayManagerUserScopedService.forUser(user).isNightDisplayActivated = activated
+ }
+ }
+
+ private fun initialState(user: UserHandle): NightDisplayState {
+ val colorDisplayManager = colorDisplayManagerUserScopedService.forUser(user)
+ return NightDisplayState(
+ colorDisplayManager.nightDisplayAutoMode,
+ colorDisplayManager.isNightDisplayActivated,
+ colorDisplayManager.nightDisplayCustomStartTime,
+ colorDisplayManager.nightDisplayCustomEndTime,
+ globalSettings.getString(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) ==
+ NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE &&
+ secureSettings.getIntForUser(DISPLAY_AUTO_MODE_RAW_SETTING_NAME, user.identifier) ==
+ NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET,
+ locationController.isLocationEnabled,
+ )
+ }
+
+ private companion object {
+ const val NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET = -1
+ const val NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE = "1"
+ const val IS_AUTO_MODE_RAW_NOT_SET_DEFAULT = true
+ const val IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME =
+ Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE
+ const val DISPLAY_AUTO_MODE_RAW_SETTING_NAME = Settings.Secure.NIGHT_DISPLAY_AUTO_MODE
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt
index 968ce0d..93b624a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt
@@ -28,6 +28,8 @@
private val a11yRepo: AccessibilityRepository,
) {
/** @see [android.view.accessibility.AccessibilityManager.isTouchExplorationEnabled] */
- val isTouchExplorationEnabled: Flow<Boolean>
- get() = a11yRepo.isTouchExplorationEnabled
+ val isTouchExplorationEnabled: Flow<Boolean> = a11yRepo.isTouchExplorationEnabled
+
+ /** @see [android.view.accessibility.AccessibilityManager.isEnabled] */
+ val isEnabled: Flow<Boolean> = a11yRepo.isEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 54dd6d0..ed9597d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -41,6 +41,10 @@
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import com.android.systemui.qs.tiles.impl.night.domain.interactor.NightDisplayTileDataInteractor
+import com.android.systemui.qs.tiles.impl.night.domain.interactor.NightDisplayTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.impl.night.ui.NightDisplayTileMapper
import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileDataInteractor
import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel
@@ -117,6 +121,7 @@
const val FONT_SCALING_TILE_SPEC = "font_scaling"
const val REDUCE_BRIGHTNESS_TILE_SPEC = "reduce_brightness"
const val ONE_HANDED_TILE_SPEC = "onehanded"
+ const val NIGHT_DISPLAY_TILE_SPEC = "night"
@Provides
@IntoMap
@@ -279,5 +284,41 @@
mapper,
)
else StubQSTileViewModel
+
+ @Provides
+ @IntoMap
+ @StringKey(NIGHT_DISPLAY_TILE_SPEC)
+ fun provideNightDisplayTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(NIGHT_DISPLAY_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_nightlight_icon_off,
+ labelRes = R.string.quick_settings_night_display_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /**
+ * Inject NightDisplay Tile into tileViewModelMap in QSModule. The tile is hidden behind a
+ * flag.
+ */
+ @Provides
+ @IntoMap
+ @StringKey(NIGHT_DISPLAY_TILE_SPEC)
+ fun provideNightDisplayTileViewModel(
+ factory: QSTileViewModelFactory.Static<NightDisplayTileModel>,
+ mapper: NightDisplayTileMapper,
+ stateInteractor: NightDisplayTileDataInteractor,
+ userActionInteractor: NightDisplayTileUserActionInteractor
+ ): QSTileViewModel =
+ if (Flags.qsNewTilesFuture())
+ factory.create(
+ TileSpec.create(NIGHT_DISPLAY_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ else StubQSTileViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 5ba0b2d..9ba41ef 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -76,6 +76,7 @@
import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
import com.android.systemui.biometrics.shared.model.BiometricModalities;
+import com.android.systemui.biometrics.shared.model.PromptKind;
import com.android.systemui.biometrics.ui.BiometricPromptLayout;
import com.android.systemui.biometrics.ui.CredentialView;
import com.android.systemui.biometrics.ui.binder.BiometricViewBinder;
@@ -500,24 +501,18 @@
private void addCredentialView(boolean animatePanel, boolean animateContents) {
final LayoutInflater factory = LayoutInflater.from(mContext);
- @Utils.CredentialType final int credentialType = Utils.getCredentialType(
- mLockPatternUtils, mEffectiveUserId);
-
- switch (credentialType) {
- case Utils.CREDENTIAL_PATTERN:
- mCredentialView = factory.inflate(
- R.layout.auth_credential_pattern_view, null, false);
- break;
- case Utils.CREDENTIAL_PIN:
- mCredentialView = factory.inflate(R.layout.auth_credential_pin_view, null, false);
- break;
- case Utils.CREDENTIAL_PASSWORD:
- mCredentialView = factory.inflate(
- R.layout.auth_credential_password_view, null, false);
- break;
- default:
- throw new IllegalStateException("Unknown credential type: " + credentialType);
+ PromptKind credentialType = Utils.getCredentialType(mLockPatternUtils, mEffectiveUserId);
+ final int layoutResourceId;
+ if (credentialType instanceof PromptKind.Pattern) {
+ layoutResourceId = R.layout.auth_credential_pattern_view;
+ } else if (credentialType instanceof PromptKind.Pin) {
+ layoutResourceId = R.layout.auth_credential_pin_view;
+ } else if (credentialType instanceof PromptKind.Password) {
+ layoutResourceId = R.layout.auth_credential_password_view;
+ } else {
+ throw new IllegalStateException("Unknown credential type: " + credentialType);
}
+ mCredentialView = factory.inflate(layoutResourceId, null, false);
// The background is used for detecting taps / cancelling authentication. Since the
// credential view is full-screen and should not be canceled from background taps,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index 9ad3f43..f659ff0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -125,7 +125,7 @@
private val _userId: MutableStateFlow<Int?> = MutableStateFlow(null)
override val userId = _userId.asStateFlow()
- private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
+ private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.None)
override val kind = _kind.asStateFlow()
private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
@@ -149,7 +149,7 @@
override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow()
override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) {
- val hasCredentialViewShown = kind.value !is PromptKind.Biometric
+ val hasCredentialViewShown = kind.value.isCredential()
val showBpForCredential =
Flags.customBiometricPrompt() &&
constraintBp() &&
@@ -178,7 +178,7 @@
_promptInfo.value = null
_userId.value = null
_challenge.value = null
- _kind.value = PromptKind.Biometric()
+ _kind.value = PromptKind.None
_opPackageName.value = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
index b7c0fa8..f8fb7bb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -16,10 +16,8 @@
package com.android.systemui.biometrics.domain.interactor
-import android.hardware.biometrics.PromptInfo
import com.android.internal.widget.LockPatternView
import com.android.internal.widget.LockscreenCredential
-import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.data.repository.PromptRepository
import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
@@ -42,12 +40,6 @@
* Business logic for BiometricPrompt's CredentialViews, which primarily includes checking a users
* PIN, pattern, or password credential instead of a biometric.
*
- * This is used to cache the calling app's options that were given to the underlying authenticate
- * APIs and should be set before any UI is shown to the user.
- *
- * There can be at most one request active at a given time. Use [resetPrompt] when no request is
- * active to clear the cache.
- *
* Views that use any biometric should use [PromptSelectorInteractor] instead.
*/
class PromptCredentialInteractor
@@ -137,28 +129,6 @@
private val _verificationError = MutableStateFlow<CredentialStatus.Fail?>(null)
val verificationError: Flow<CredentialStatus.Fail?> = _verificationError.asStateFlow()
- /** Update the current request to use credential-based authentication instead of biometrics. */
- fun useCredentialsForAuthentication(
- promptInfo: PromptInfo,
- @Utils.CredentialType kind: Int,
- userId: Int,
- challenge: Long,
- opPackageName: String,
- ) {
- biometricPromptRepository.setPrompt(
- promptInfo,
- userId,
- challenge,
- kind.asBiometricPromptCredential(),
- opPackageName,
- )
- }
-
- /** Unset the current authentication request. */
- fun resetPrompt() {
- biometricPromptRepository.unsetPrompt()
- }
-
/**
* Check a credential and return the attestation token (HAT) if successful.
*
@@ -231,13 +201,3 @@
_verificationError.value = null
}
}
-
-// TODO(b/251476085): remove along with Utils.CredentialType
-/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */
-private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind =
- when (this) {
- Utils.CREDENTIAL_PIN -> PromptKind.Pin
- Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
- Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
- else -> PromptKind.Biometric()
- }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index 45816c1..deb47d3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -18,7 +18,6 @@
import android.hardware.biometrics.PromptInfo
import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.Utils.getCredentialType
import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
@@ -95,7 +94,7 @@
/** Use credential-based authentication instead of biometrics. */
fun useCredentialsForAuthentication(
promptInfo: PromptInfo,
- @Utils.CredentialType kind: Int,
+ kind: PromptKind,
userId: Int,
challenge: Long,
opPackageName: String,
@@ -152,14 +151,7 @@
override val credentialKind: Flow<PromptKind> =
combine(prompt, isCredentialAllowed) { prompt, isAllowed ->
if (prompt != null && isAllowed) {
- when (
- getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId)
- ) {
- Utils.CREDENTIAL_PIN -> PromptKind.Pin
- Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
- Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
- else -> PromptKind.Biometric()
- }
+ getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId)
} else {
PromptKind.Biometric()
}
@@ -191,7 +183,7 @@
override fun useCredentialsForAuthentication(
promptInfo: PromptInfo,
- @Utils.CredentialType kind: Int,
+ kind: PromptKind,
userId: Int,
challenge: Long,
opPackageName: String,
@@ -200,7 +192,7 @@
promptInfo = promptInfo,
userId = userId,
gatekeeperChallenge = challenge,
- kind = kind.asBiometricPromptCredential(),
+ kind = kind,
opPackageName = opPackageName,
)
}
@@ -209,13 +201,3 @@
promptRepository.unsetPrompt()
}
}
-
-// TODO(b/251476085): remove along with Utils.CredentialType
-/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */
-private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind =
- when (this) {
- Utils.CREDENTIAL_PIN -> PromptKind.Pin
- Utils.CREDENTIAL_PASSWORD -> PromptKind.Password
- Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern
- else -> PromptKind.Biometric()
- }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index 337d873..9114aab 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -33,6 +33,7 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
/** View model for transitions related to the communal hub. */
@@ -49,6 +50,27 @@
communalInteractor: CommunalInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
+ // Show UMO on glanceable hub immediately on transition into glanceable hub
+ private val showUmoFromOccludedToGlanceableHub: Flow<Boolean> =
+ keyguardTransitionInteractor
+ .transitionStepsFromState(KeyguardState.OCCLUDED)
+ .filter {
+ it.to == KeyguardState.GLANCEABLE_HUB &&
+ (it.transitionState == TransitionState.STARTED ||
+ it.transitionState == TransitionState.CANCELED)
+ }
+ .map { it.transitionState == TransitionState.STARTED }
+
+ private val showUmoFromGlanceableHubToOccluded: Flow<Boolean> =
+ keyguardTransitionInteractor
+ .transitionStepsFromState(KeyguardState.GLANCEABLE_HUB)
+ .filter {
+ it.to == KeyguardState.OCCLUDED &&
+ (it.transitionState == TransitionState.FINISHED ||
+ it.transitionState == TransitionState.CANCELED)
+ }
+ .map { it.transitionState != TransitionState.FINISHED }
+
/**
* Whether UMO location should be on communal. This flow is responsive to transitions so that a
* new value is emitted at the right step of a transition to/from communal hub that the location
@@ -60,6 +82,8 @@
glanceableHubToLockscreenTransitionViewModel.showUmo,
dreamToGlanceableHubTransitionViewModel.showUmo,
glanceableHubToDreamTransitionViewModel.showUmo,
+ showUmoFromOccludedToGlanceableHub,
+ showUmoFromGlanceableHubToOccluded,
)
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index e00137e..11e6f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -225,6 +225,13 @@
@Provides
@Singleton
+ static UserScopedService<ColorDisplayManager> provideScopedColorDisplayManager(
+ Context context) {
+ return new UserScopedServiceImpl<>(context, ColorDisplayManager.class);
+ }
+
+ @Provides
+ @Singleton
static CrossWindowBlurListeners provideCrossWindowBlurListeners() {
return CrossWindowBlurListeners.getInstance();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 8c0a73c..6e04339 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -198,7 +198,6 @@
mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
mBouncerlessScrimController = bouncerlessScrimController;
- mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
@@ -234,6 +233,7 @@
mJitterStartTimeMillis = System.currentTimeMillis();
mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
+ mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
final Region emptyRegion = Region.obtain();
mView.getRootSurfaceControl().setTouchableRegion(emptyRegion);
emptyRegion.recycle();
@@ -255,8 +255,9 @@
@Override
protected void onViewDetached() {
- mHandler.removeCallbacks(this::updateBurnInOffsets);
+ mHandler.removeCallbacksAndMessages(null);
mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
+ mBouncerlessScrimController.removeCallback(mBouncerlessExpansionCallback);
mDreamOverlayAnimationsController.cancelAnimations();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index e32bfcf..7f3274c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -134,7 +134,7 @@
TransitionInfo(
ownerName = "",
from = KeyguardState.OFF,
- to = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OFF,
animator = null
)
)
@@ -266,6 +266,14 @@
}
override suspend fun emitInitialStepsFromOff(to: KeyguardState) {
+ _currentTransitionInfo.value =
+ TransitionInfo(
+ ownerName = "KeyguardTransitionRepository(boot)",
+ from = KeyguardState.OFF,
+ to = to,
+ animator = null
+ )
+
emitTransition(
TransitionStep(
KeyguardState.OFF,
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 4f00495..e2b66c5 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
@@ -76,7 +76,7 @@
view,
HapticFeedbackConstants.CONFIRM,
)
- applicationScope.launch { viewModel.onLongPress() }
+ applicationScope.launch { viewModel.onUserInteraction() }
}
}
@@ -116,6 +116,17 @@
launch("$TAG#viewModel.accessibilityDelegateHint") {
viewModel.accessibilityDelegateHint.collect { hint ->
view.accessibilityHintType = hint
+ if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) {
+ view.setOnClickListener {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.CONFIRM,
+ )
+ applicationScope.launch { viewModel.onUserInteraction() }
+ }
+ } else {
+ view.setOnClickListener(null)
+ }
}
}
launch("$TAG#viewModel.useBackgroundProtection") {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
index 35b2598..200d30c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -65,12 +65,12 @@
object : AccessibilityDelegate() {
private val accessibilityAuthenticateHint =
AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfoCompat.ACTION_LONG_CLICK,
+ AccessibilityNodeInfoCompat.ACTION_CLICK,
resources.getString(R.string.accessibility_authenticate_hint)
)
private val accessibilityEnterHint =
AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfoCompat.ACTION_LONG_CLICK,
+ AccessibilityNodeInfoCompat.ACTION_CLICK,
resources.getString(R.string.accessibility_enter_hint)
)
override fun onInitializeAccessibilityNodeInfo(
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 da2fcc4..53b2697 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
@@ -19,6 +19,7 @@
import android.animation.FloatEvaluator
import android.animation.IntEvaluator
import com.android.keyguard.KeyguardViewController
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -68,6 +69,7 @@
private val keyguardViewController: Lazy<KeyguardViewController>,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
+ private val accessibilityInteractor: AccessibilityInteractor,
@Application private val scope: CoroutineScope,
) {
val isUdfpsSupported: StateFlow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
@@ -232,7 +234,8 @@
}
}
val isVisible: Flow<Boolean> = deviceEntryViewAlpha.map { it > 0f }.distinctUntilChanged()
- val isLongPressEnabled: Flow<Boolean> =
+
+ private val isInteractive: Flow<Boolean> =
combine(
iconType,
isUdfpsSupported,
@@ -244,17 +247,24 @@
DeviceEntryIconView.IconType.NONE -> false
}
}
-
val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
- combine(iconType, isLongPressEnabled) { deviceEntryStatus, longPressEnabled ->
- if (longPressEnabled) {
- deviceEntryStatus.toAccessibilityHintType()
+ accessibilityInteractor.isEnabled.flatMapLatest { touchExplorationEnabled ->
+ if (touchExplorationEnabled) {
+ combine(iconType, isInteractive) { iconType, isInteractive ->
+ if (isInteractive) {
+ iconType.toAccessibilityHintType()
+ } else {
+ DeviceEntryIconView.AccessibilityHintType.NONE
+ }
+ }
} else {
- DeviceEntryIconView.AccessibilityHintType.NONE
+ flowOf(DeviceEntryIconView.AccessibilityHintType.NONE)
}
}
- suspend fun onLongPress() {
+ val isLongPressEnabled: Flow<Boolean> = isInteractive
+
+ suspend fun onUserInteraction() {
if (SceneContainerFlag.isEnabled) {
deviceEntryInteractor.attemptDeviceEntry()
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 933065b..295b293 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -442,7 +442,7 @@
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.GET_SHARED_LIBRARY_FILES));
int resId = resources.getIdentifier(
- "gesture_blocking_activities", "array", recentsPackageName);
+ "back_gesture_blocking_activities", "array", recentsPackageName);
if (resId == 0) {
Log.e(TAG, "No resource found for gesture-blocking activities");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index 6fb5174..5720f76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -125,7 +125,10 @@
public void setNumPages(int numPages) {
setVisibility(numPages > 1 ? View.VISIBLE : View.GONE);
- if (numPages == getChildCount()) {
+ int childCount = getChildCount();
+ // We're checking if the width needs to be updated as it's possible that the number of pages
+ // was changed while the page indicator was not visible, automatically skipping onMeasure.
+ if (numPages == childCount && calculateWidth(childCount) == getMeasuredWidth()) {
return;
}
if (mAnimating) {
@@ -295,6 +298,10 @@
}
}
+ private int calculateWidth(int numPages) {
+ return (mPageIndicatorWidth - mPageDotWidth) * (numPages - 1) + mPageDotWidth;
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int N = getChildCount();
@@ -309,7 +316,7 @@
for (int i = 0; i < N; i++) {
getChildAt(i).measure(widthChildSpec, heightChildSpec);
}
- int width = (mPageIndicatorWidth - mPageDotWidth) * (N - 1) + mPageDotWidth;
+ int width = calculateWidth(N);
setMeasuredDimension(width, mPageIndicatorHeight);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index b515ce0..278352c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -28,6 +28,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
import com.android.systemui.log.core.LogLevel.VERBOSE
import com.android.systemui.log.dagger.QSConfigLog
import com.android.systemui.log.dagger.QSLog
@@ -56,6 +57,9 @@
fun d(@CompileTimeConstant msg: String, arg: Any) {
buffer.log(TAG, DEBUG, { str1 = arg.toString() }, { "$msg: $str1" })
}
+ fun i(@CompileTimeConstant msg: String, arg: Any) {
+ buffer.log(TAG, INFO, { str1 = arg.toString() }, { "$msg: $str1" })
+ }
fun logTileAdded(tileSpec: String) {
buffer.log(TAG, DEBUG, { str1 = tileSpec }, { "[$str1] Tile added" })
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index 6539cf3..86cc6f5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -23,10 +23,12 @@
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Box
@@ -71,7 +73,7 @@
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.dp
-import com.android.compose.modifiers.background
+import com.android.compose.animation.Expandable
import com.android.compose.theme.colorAttr
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
@@ -134,7 +136,7 @@
}
}
- @OptIn(ExperimentalCoroutinesApi::class)
+ @OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
@Composable
private fun Tile(
tile: TileViewModel,
@@ -147,28 +149,39 @@
.collectAsState(initial = tile.currentState.toUiState())
val context = LocalContext.current
- Row(
- modifier = modifier.clickable { tile.onClick(null) }.tileModifier(state.colors),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = tileHorizontalArrangement(iconOnly)
+ Expandable(
+ color = colorAttr(state.colors.background),
+ shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)),
) {
- val icon =
- remember(state.icon) {
- state.icon.get().let {
- if (it is QSTileImpl.ResourceIcon) {
- Icon.Resource(it.resId, null)
- } else {
- Icon.Loaded(it.getDrawable(context), null)
+ Row(
+ modifier =
+ modifier
+ .combinedClickable(
+ onClick = { tile.onClick(it) },
+ onLongClick = { tile.onLongClick(it) }
+ )
+ .tileModifier(state.colors),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(iconOnly),
+ ) {
+ val icon =
+ remember(state.icon) {
+ state.icon.get().let {
+ if (it is QSTileImpl.ResourceIcon) {
+ Icon.Resource(it.resId, null)
+ } else {
+ Icon.Loaded(it.getDrawable(context), null)
+ }
}
}
- }
- TileContent(
- label = state.label.toString(),
- secondaryLabel = state.secondaryLabel.toString(),
- icon = icon,
- colors = state.colors,
- iconOnly = iconOnly
- )
+ TileContent(
+ label = state.label.toString(),
+ secondaryLabel = state.secondaryLabel?.toString(),
+ icon = icon,
+ colors = state.colors,
+ iconOnly = iconOnly
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
index 065e89f..f0d7206 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -175,6 +175,26 @@
)
}
+ /** Log with level [LogLevel.WARNING] */
+ fun logWarning(
+ tileSpec: TileSpec,
+ message: String,
+ ) {
+ tileSpec
+ .getLogBuffer()
+ .log(tileSpec.getLogTag(), LogLevel.WARNING, { str1 = message }, { str1!! })
+ }
+
+ /** Log with level [LogLevel.INFO] */
+ fun logInfo(
+ tileSpec: TileSpec,
+ message: String,
+ ) {
+ tileSpec
+ .getLogBuffer()
+ .log(tileSpec.getLogTag(), LogLevel.INFO, { str1 = message }, { str1!! })
+ }
+
fun logCustomTileUserActionDelivered(tileSpec: TileSpec) {
tileSpec
.getLogBuffer()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 60469c0..b057476 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs.tiles.dialog;
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
+
import static com.android.settingslib.mobile.MobileMappings.getIconKey;
import static com.android.settingslib.mobile.MobileMappings.mapIconSets;
import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource;
@@ -190,7 +192,7 @@
private DialogTransitionAnimator mDialogTransitionAnimator;
private boolean mHasWifiEntries;
private WifiStateWorker mWifiStateWorker;
- private boolean mHasActiveSubId;
+ private boolean mHasActiveSubIdOnDds;
@VisibleForTesting
static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -298,7 +300,7 @@
mExecutor);
// Listen the subscription changes
mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener();
- refreshHasActiveSubId();
+ refreshHasActiveSubIdOnDds();
mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
mOnSubscriptionsChangedListener);
mDefaultDataSubId = getDefaultDataSubscriptionId();
@@ -428,7 +430,7 @@
}
boolean isActiveOnNonDds = getActiveAutoSwitchNonDdsSubId() != SubscriptionManager
.INVALID_SUBSCRIPTION_ID;
- if (!hasActiveSubId() || (!isVoiceStateInService(mDefaultDataSubId)
+ if (!hasActiveSubIdOnDds() || (!isVoiceStateInService(mDefaultDataSubId)
&& !isDataStateInService(mDefaultDataSubId) && !isActiveOnNonDds)) {
if (DEBUG) {
Log.d(TAG, "No carrier or service is out of service.");
@@ -901,23 +903,42 @@
/**
* @return whether there is the carrier item in the slice.
*/
- boolean hasActiveSubId() {
+ boolean hasActiveSubIdOnDds() {
if (isAirplaneModeEnabled() || mTelephonyManager == null) {
return false;
}
- return mHasActiveSubId;
+ return mHasActiveSubIdOnDds;
}
- private void refreshHasActiveSubId() {
+ private static boolean isEmbeddedSubscriptionVisible(@NonNull SubscriptionInfo subInfo) {
+ if (subInfo.isEmbedded() && subInfo.getProfileClass() == PROFILE_CLASS_PROVISIONING) {
+ return false;
+ }
+ return true;
+ }
+
+ private void refreshHasActiveSubIdOnDds() {
if (mSubscriptionManager == null) {
- mHasActiveSubId = false;
+ mHasActiveSubIdOnDds = false;
Log.e(TAG, "SubscriptionManager is null, set mHasActiveSubId = false");
return;
}
+ int dds = getDefaultDataSubscriptionId();
+ if (dds == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ mHasActiveSubIdOnDds = false;
+ Log.d(TAG, "DDS is INVALID_SUBSCRIPTION_ID");
+ return;
+ }
+ SubscriptionInfo ddsSubInfo = mSubscriptionManager.getActiveSubscriptionInfo(dds);
+ if (ddsSubInfo == null) {
+ mHasActiveSubIdOnDds = false;
+ Log.e(TAG, "Can't get DDS subscriptionInfo");
+ return;
+ }
- mHasActiveSubId = mSubscriptionManager.getActiveSubscriptionIdList().length > 0;
- Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubId);
+ mHasActiveSubIdOnDds = isEmbeddedSubscriptionVisible(ddsSubInfo);
+ Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubIdOnDds);
}
/**
@@ -1209,7 +1230,7 @@
@Override
public void onSubscriptionsChanged() {
- refreshHasActiveSubId();
+ refreshHasActiveSubIdOnDds();
updateListener();
}
}
@@ -1306,6 +1327,7 @@
Log.d(TAG, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED");
}
mConfig = MobileMappings.Config.readConfig(context);
+ refreshHasActiveSubIdOnDds();
updateListener();
} else if (WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION.equals(action)) {
updateListener();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index 1a881b6..c9c4443 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -429,7 +429,7 @@
}
boolean isWifiEnabled = mInternetDialogController.isWifiEnabled();
- if (!mInternetDialogController.hasActiveSubId()
+ if (!mInternetDialogController.hasActiveSubIdOnDds()
&& (!isWifiEnabled || !isCarrierNetworkActive)) {
mMobileNetworkLayout.setVisibility(View.GONE);
if (mSecondaryMobileNetworkLayout != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt
index d1c8030..bd2f2c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt
@@ -17,15 +17,15 @@
package com.android.systemui.qs.tiles.impl.location.domain.interactor
import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.util.kotlin.isLocationEnabledFlow
import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
/** Observes location state changes providing the [LocationTileModel]. */
class LocationTileDataInteractor
@@ -38,19 +38,7 @@
user: UserHandle,
triggers: Flow<DataUpdateTrigger>
): Flow<LocationTileModel> =
- ConflatedCallbackFlow.conflatedCallbackFlow {
- val initialValue = locationController.isLocationEnabled
- trySend(LocationTileModel(initialValue))
-
- val callback =
- object : LocationController.LocationChangeCallback {
- override fun onLocationSettingsChanged(locationEnabled: Boolean) {
- trySend(LocationTileModel(locationEnabled))
- }
- }
- locationController.addCallback(callback)
- awaitClose { locationController.removeCallback(callback) }
- }
+ locationController.isLocationEnabledFlow().map { LocationTileModel(it) }
override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt
new file mode 100644
index 0000000..88bd224
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.interactor
+
+import android.content.Context
+import android.hardware.display.ColorDisplayManager
+import android.os.UserHandle
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.util.time.DateFormatUtil
+import java.time.LocalTime
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Observes screen record state changes providing the [NightDisplayTileModel]. */
+class NightDisplayTileDataInteractor
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val dateFormatUtil: DateFormatUtil,
+ private val nightDisplayRepository: NightDisplayRepository,
+) : QSTileDataInteractor<NightDisplayTileModel> {
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<NightDisplayTileModel> =
+ nightDisplayRepository.nightDisplayState(user).map {
+ generateModel(
+ it.autoMode,
+ it.isActivated,
+ it.startTime,
+ it.endTime,
+ it.shouldForceAutoMode,
+ it.locationEnabled
+ )
+ }
+
+ /** This checks resources and there fore does not make a binder call. */
+ override fun availability(user: UserHandle): Flow<Boolean> =
+ flowOf(ColorDisplayManager.isNightDisplayAvailable(context))
+
+ private fun generateModel(
+ autoMode: Int,
+ isNightDisplayActivated: Boolean,
+ customStartTime: LocalTime?,
+ customEndTime: LocalTime?,
+ shouldForceAutoMode: Boolean,
+ locationEnabled: Boolean,
+ ): NightDisplayTileModel {
+ if (autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) {
+ return NightDisplayTileModel.AutoModeTwilight(
+ isNightDisplayActivated,
+ shouldForceAutoMode,
+ locationEnabled,
+ )
+ } else if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) {
+ return NightDisplayTileModel.AutoModeCustom(
+ isNightDisplayActivated,
+ shouldForceAutoMode,
+ customStartTime,
+ customEndTime,
+ dateFormatUtil.is24HourFormat,
+ )
+ } else { // auto mode off
+ return NightDisplayTileModel.AutoModeOff(isNightDisplayActivated, shouldForceAutoMode)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt
new file mode 100644
index 0000000..5cee8c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.interactor
+
+import android.content.Intent
+import android.hardware.display.ColorDisplayManager.AUTO_MODE_CUSTOM_TIME
+import android.provider.Settings
+import com.android.systemui.accessibility.data.repository.NightDisplayRepository
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles night display tile clicks. */
+class NightDisplayTileUserActionInteractor
+@Inject
+constructor(
+ private val nightDisplayRepository: NightDisplayRepository,
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val qsLogger: QSTileLogger,
+) : QSTileUserActionInteractor<NightDisplayTileModel> {
+ override suspend fun handleInput(input: QSTileInput<NightDisplayTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ // Enroll in forced auto mode if eligible.
+ if (data.isEnrolledInForcedNightDisplayAutoMode) {
+ nightDisplayRepository.setNightDisplayAutoMode(AUTO_MODE_CUSTOM_TIME, user)
+ qsLogger.logInfo(spec, "Enrolled in forced night display auto mode")
+ }
+ nightDisplayRepository.setNightDisplayActivated(!data.isActivated, user)
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.expandable,
+ Intent(Settings.ACTION_NIGHT_DISPLAY_SETTINGS)
+ )
+ }
+ }
+ }
+
+ companion object {
+ val spec = TileSpec.create(QSAccessibilityModule.NIGHT_DISPLAY_TILE_SPEC)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt
new file mode 100644
index 0000000..6b1bd5b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.domain.model
+
+import java.time.LocalTime
+
+/** Data model for night display tile */
+sealed interface NightDisplayTileModel {
+ val isActivated: Boolean
+ val isEnrolledInForcedNightDisplayAutoMode: Boolean
+ data class AutoModeTwilight(
+ override val isActivated: Boolean,
+ override val isEnrolledInForcedNightDisplayAutoMode: Boolean,
+ val isLocationEnabled: Boolean
+ ) : NightDisplayTileModel
+ data class AutoModeCustom(
+ override val isActivated: Boolean,
+ override val isEnrolledInForcedNightDisplayAutoMode: Boolean,
+ val startTime: LocalTime?,
+ val endTime: LocalTime?,
+ val is24HourFormat: Boolean
+ ) : NightDisplayTileModel
+ data class AutoModeOff(
+ override val isActivated: Boolean,
+ override val isEnrolledInForcedNightDisplayAutoMode: Boolean
+ ) : NightDisplayTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
new file mode 100644
index 0000000..5c2dcfc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night.ui
+
+import android.content.res.Resources
+import android.service.quicksettings.Tile
+import android.text.TextUtils
+import androidx.annotation.StringRes
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import java.time.DateTimeException
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+import javax.inject.Inject
+
+/** Maps [NightDisplayTileModel] to [QSTileState]. */
+class NightDisplayTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+ private val logger: QSTileLogger,
+) : QSTileDataToStateMapper<NightDisplayTileModel> {
+ override fun map(config: QSTileConfig, data: NightDisplayTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(R.string.quick_settings_night_display_label)
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ sideViewIcon = QSTileState.SideViewIcon.None
+
+ if (data.isActivated) {
+ activationState = QSTileState.ActivationState.ACTIVE
+ val loadedIcon =
+ Icon.Loaded(
+ resources.getDrawable(R.drawable.qs_nightlight_icon_on, theme),
+ contentDescription = null
+ )
+ icon = { loadedIcon }
+ } else {
+ activationState = QSTileState.ActivationState.INACTIVE
+ val loadedIcon =
+ Icon.Loaded(
+ resources.getDrawable(R.drawable.qs_nightlight_icon_off, theme),
+ contentDescription = null
+ )
+ icon = { loadedIcon }
+ }
+
+ secondaryLabel = getSecondaryLabel(data, resources)
+
+ contentDescription =
+ if (TextUtils.isEmpty(secondaryLabel)) label
+ else TextUtils.concat(label, ", ", secondaryLabel)
+ }
+
+ private fun getSecondaryLabel(
+ data: NightDisplayTileModel,
+ resources: Resources
+ ): CharSequence? {
+ when (data) {
+ is NightDisplayTileModel.AutoModeTwilight -> {
+ if (!data.isLocationEnabled) {
+ return null
+ } else {
+ return resources.getString(
+ if (data.isActivated)
+ R.string.quick_settings_night_secondary_label_until_sunrise
+ else R.string.quick_settings_night_secondary_label_on_at_sunset
+ )
+ }
+ }
+ is NightDisplayTileModel.AutoModeOff -> {
+ val subtitleArray = resources.getStringArray(R.array.tile_states_night)
+ return subtitleArray[
+ if (data.isActivated) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE]
+ }
+ is NightDisplayTileModel.AutoModeCustom -> {
+ // User-specified time, approximated to the nearest hour.
+ @StringRes val toggleTimeStringRes: Int
+ val toggleTime: LocalTime
+ if (data.isActivated) {
+ toggleTime = data.endTime ?: return null
+ toggleTimeStringRes = R.string.quick_settings_secondary_label_until
+ } else {
+ toggleTime = data.startTime ?: return null
+ toggleTimeStringRes = R.string.quick_settings_night_secondary_label_on_at
+ }
+
+ try {
+ val formatter = if (data.is24HourFormat) formatter24Hour else formatter12Hour
+ val formatArg = formatter.format(toggleTime)
+ return resources.getString(toggleTimeStringRes, formatArg)
+ } catch (exception: DateTimeException) {
+ logger.logWarning(spec, exception.message.toString())
+ return null
+ }
+ }
+ }
+ }
+
+ private companion object {
+ val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
+ val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
+ val spec = TileSpec.create(QSAccessibilityModule.NIGHT_DISPLAY_TILE_SPEC)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 259a8bf..b971781 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -56,9 +56,8 @@
}
// TODO(b/298525212): remove once Compose exposes window inset bounds.
- override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
- val insets = super.onApplyWindowInsets(windowInsets)
- this.windowInsets.value = insets
- return insets
+ override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets {
+ this.windowInsets.value = windowInsets
+ return windowInsets
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 70632d5..79218ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -18,6 +18,7 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE;
import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
import android.animation.Animator;
@@ -49,6 +50,7 @@
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus;
import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
@@ -108,6 +110,7 @@
private final UiEventLogger mUiEventLogger;
private final Lazy<InteractionJankMonitor> mInteractionJankMonitorLazy;
private final JavaAdapter mJavaAdapter;
+ private final Lazy<KeyguardTransitionInteractor> mKeyguardTransitionInteractorLazy;
private final Lazy<ShadeInteractor> mShadeInteractorLazy;
private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
private final Lazy<SceneInteractor> mSceneInteractorLazy;
@@ -175,6 +178,7 @@
UiEventLogger uiEventLogger,
Lazy<InteractionJankMonitor> interactionJankMonitorLazy,
JavaAdapter javaAdapter,
+ Lazy<KeyguardTransitionInteractor> keyguardTransitionInteractor,
Lazy<ShadeInteractor> shadeInteractorLazy,
Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy,
Lazy<SceneInteractor> sceneInteractorLazy,
@@ -182,6 +186,7 @@
mUiEventLogger = uiEventLogger;
mInteractionJankMonitorLazy = interactionJankMonitorLazy;
mJavaAdapter = javaAdapter;
+ mKeyguardTransitionInteractorLazy = keyguardTransitionInteractor;
mShadeInteractorLazy = shadeInteractorLazy;
mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy;
mSceneInteractorLazy = sceneInteractorLazy;
@@ -193,6 +198,14 @@
@Override
public void start() {
+ mJavaAdapter.alwaysCollectFlow(
+ mKeyguardTransitionInteractorLazy.get().isFinishedInState(GONE),
+ (Boolean isFinishedInState) -> {
+ if (isFinishedInState) {
+ setLeaveOpenOnKeyguardHide(false);
+ }
+ });
+
mJavaAdapter.alwaysCollectFlow(mShadeInteractorLazy.get().isAnyExpanded(),
this::onShadeOrQsExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index c17da4b..0524589 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -32,6 +32,7 @@
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
@@ -56,10 +57,10 @@
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
-import com.android.systemui.statusbar.phone.ui.StatusBarIconList;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
import com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl;
+import com.android.systemui.statusbar.phone.ui.StatusBarIconList;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import dagger.Binds;
@@ -209,14 +210,16 @@
/** */
@Provides
@SysUISingleton
- static ActivityTransitionAnimator provideActivityTransitionAnimator() {
- return new ActivityTransitionAnimator();
+ static ActivityTransitionAnimator provideActivityTransitionAnimator(
+ @Main Executor mainExecutor) {
+ return new ActivityTransitionAnimator(mainExecutor);
}
/** */
@Provides
@SysUISingleton
- static DialogTransitionAnimator provideDialogTransitionAnimator(IDreamManager dreamManager,
+ static DialogTransitionAnimator provideDialogTransitionAnimator(@Main Executor mainExecutor,
+ IDreamManager dreamManager,
KeyguardStateController keyguardStateController,
Lazy<AlternateBouncerInteractor> alternateBouncerInteractor,
InteractionJankMonitor interactionJankMonitor,
@@ -243,7 +246,7 @@
}
};
return new DialogTransitionAnimator(
- callback, interactionJankMonitor, animationFeatureFlags);
+ mainExecutor, callback, interactionJankMonitor, animationFeatureFlags);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 6a38f8d..d2d0aaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.row;
import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.BOTTOM;
+import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.TOP;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -43,6 +45,7 @@
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -354,12 +357,13 @@
@Override
public long performRemoveAnimation(long duration, long delay, float translationDirection,
boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener) {
+ AnimatorListenerAdapter animationListener, ClipSide clipSide) {
enableAppearDrawing(true);
mIsHeadsUpAnimation = isHeadsUpAnimation;
if (mDrawingAppearAnimation) {
startAppearAnimation(false /* isAppearing */, translationDirection,
- delay, duration, onStartedRunnable, onFinishedRunnable, animationListener);
+ delay, duration, onStartedRunnable, onFinishedRunnable, animationListener,
+ clipSide);
} else {
if (onStartedRunnable != null) {
onStartedRunnable.run();
@@ -378,13 +382,13 @@
mIsHeadsUpAnimation = isHeadsUpAppear;
if (mDrawingAppearAnimation) {
startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay,
- duration, null, null, null);
+ duration, null, null, null, ClipSide.BOTTOM);
}
}
private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
long duration, final Runnable onStartedRunnable, final Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener) {
+ AnimatorListenerAdapter animationListener, ClipSide clipSide) {
mAnimationTranslationY = translationDirection * getActualHeight();
cancelAppearAnimation();
if (mAppearAnimationFraction == -1.0f) {
@@ -406,9 +410,16 @@
mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE;
targetValue = 0.0f;
}
+
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ // TODO(b/316404716): add avalanche filtering
+ mCurrentAppearInterpolator = Interpolators.LINEAR;
+ }
+
mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
targetValue);
- if (NotificationsImprovedHunAnimation.isEnabled()) {
+ if (NotificationsImprovedHunAnimation.isEnabled()
+ || NotificationHeadsUpCycling.isEnabled()) {
mAppearAnimator.setInterpolator(mCurrentAppearInterpolator);
} else {
mAppearAnimator.setInterpolator(Interpolators.LINEAR);
@@ -418,7 +429,12 @@
mAppearAnimator.addUpdateListener(animation -> {
mAppearAnimationFraction = (float) animation.getAnimatedValue();
updateAppearAnimationAlpha();
- updateAppearRect();
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ // For cycling out, we want the HUN to be clipped from the top.
+ updateAppearRect(clipSide);
+ } else {
+ updateAppearRect();
+ }
invalidate();
});
if (animationListener != null) {
@@ -426,7 +442,11 @@
}
// we need to apply the initial state already to avoid drawn frames in the wrong state
updateAppearAnimationAlpha();
- updateAppearRect();
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ updateAppearRect(clipSide);
+ } else {
+ updateAppearRect();
+ }
mAppearAnimator.addListener(new AnimatorListenerAdapter() {
private boolean mRunWithoutInterruptions;
@@ -508,14 +528,18 @@
enableAppearDrawing(false);
}
- private void updateAppearRect() {
+ /**
+ * Update the View's Rect clipping to fit the appear animation
+ * @param clipSide Which side if view we want to clip from
+ */
+ private void updateAppearRect(ClipSide clipSide) {
float interpolatedFraction =
- NotificationsImprovedHunAnimation.isEnabled() ? mAppearAnimationFraction
+ NotificationsImprovedHunAnimation.isEnabled()
+ || NotificationHeadsUpCycling.isEnabled() ? mAppearAnimationFraction
: mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY;
- final int actualHeight = getActualHeight();
- float bottom = actualHeight * interpolatedFraction;
-
+ final int fullHeight = getActualHeight();
+ float height = fullHeight * interpolatedFraction;
if (mTargetPoint != null) {
int width = getWidth();
float fraction = 1 - mAppearAnimationFraction;
@@ -524,13 +548,26 @@
mAnimationTranslationY
+ (mAnimationTranslationY - mTargetPoint.y) * fraction,
width - (width - mTargetPoint.x) * fraction,
- actualHeight - (actualHeight - mTargetPoint.y) * fraction);
+ fullHeight - (fullHeight - mTargetPoint.y) * fraction);
} else {
- setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
- bottom + mAppearAnimationTranslation);
+ if (clipSide == TOP) {
+ setOutlineRect(
+ 0,
+ /* top= */ fullHeight - height,
+ getWidth(),
+ /* bottom= */ fullHeight
+ );
+ } else if (clipSide == BOTTOM) {
+ setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
+ height + mAppearAnimationTranslation);
+ }
}
}
+ private void updateAppearRect() {
+ updateAppearRect(ClipSide.BOTTOM);
+ }
+
private float getInterpolatedAppearAnimationFraction() {
if (mAppearAnimationFraction >= 0) {
@@ -540,11 +577,36 @@
}
private void updateAppearAnimationAlpha() {
- float contentAlphaProgress = MathUtils.constrain(mAppearAnimationFraction,
- ALPHA_APPEAR_START_FRACTION, ALPHA_APPEAR_END_FRACTION);
- float range = ALPHA_APPEAR_END_FRACTION - ALPHA_APPEAR_START_FRACTION;
- float alpha = (contentAlphaProgress - ALPHA_APPEAR_START_FRACTION) / range;
- setContentAlpha(Interpolators.ALPHA_IN.getInterpolation(alpha));
+ updateAppearAnimationContentAlpha(
+ mAppearAnimationFraction,
+ ALPHA_APPEAR_START_FRACTION,
+ ALPHA_APPEAR_END_FRACTION,
+ Interpolators.ALPHA_IN
+ );
+ }
+
+ /**
+ * Update the alpha value of the content view during the appear animation. We suppose that the
+ * content alpha changes from 0 to 1 during some part of the appear animation.
+ * @param appearFraction the current appearFraction, should be in the range of [0, 1], where
+ * 1 represents fully appeared
+ * @param startFraction the appear fraction when the content view should be
+ * * fully transparent
+ * @param endFraction the appear fraction when the content view should be
+ * fully in-transparent, should be greater or equals to startFraction
+ * @param interpolator the interpolator to update the alpha
+ */
+ private void updateAppearAnimationContentAlpha(
+ float appearFraction,
+ float startFraction,
+ float endFraction,
+ Interpolator interpolator
+ ) {
+ float contentAlphaProgress = MathUtils.constrain(appearFraction, startFraction,
+ endFraction);
+ float range = endFraction - startFraction;
+ float alpha = (contentAlphaProgress - startFraction) / range;
+ setContentAlpha(interpolator.getInterpolation(alpha));
}
private void setContentAlpha(float contentAlpha) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 23c0a0d..747cb3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -3076,7 +3076,7 @@
boolean isHeadsUpAnimation,
Runnable onStartedRunnable,
Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener) {
+ AnimatorListenerAdapter animationListener, ClipSide clipSide) {
if (mMenuRow != null && mMenuRow.isMenuVisible()) {
Animator anim = getTranslateViewAnimator(0f, null /* listener */);
if (anim != null) {
@@ -3092,7 +3092,7 @@
public void onAnimationEnd(Animator animation) {
ExpandableNotificationRow.super.performRemoveAnimation(
duration, delay, translationDirection, isHeadsUpAnimation,
- null, onFinishedRunnable, animationListener);
+ null, onFinishedRunnable, animationListener, ClipSide.BOTTOM);
}
});
anim.start();
@@ -3100,7 +3100,8 @@
}
}
return super.performRemoveAnimation(duration, delay, translationDirection,
- isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener);
+ isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener,
+ clipSide);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 05e8717..2af119f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -362,17 +362,17 @@
/**
* Perform a remove animation on this view.
- * @param duration The duration of the remove animation.
- * @param delay The delay of the animation
+ *
+ * @param duration The duration of the remove animation.
+ * @param delay The delay of the animation
* @param translationDirection The direction value from [-1 ... 1] indicating in which the
* animation should be performed. A value of -1 means that The
* remove animation should be performed upwards,
* such that the child appears to be going away to the top. 1
* Should mean the opposite.
- * @param isHeadsUpAnimation Is this a headsUp animation.
- * @param onFinishedRunnable A runnable which should be run when the animation is finished.
- * @param animationListener An animation listener to add to the animation.
- *
+ * @param isHeadsUpAnimation Is this a headsUp animation.
+ * @param onFinishedRunnable A runnable which should be run when the animation is finished.
+ * @param animationListener An animation listener to add to the animation.
* @return The additional delay, in milliseconds, that this view needs to add before the
* animation starts.
*/
@@ -380,7 +380,12 @@
long delay, float translationDirection, boolean isHeadsUpAnimation,
Runnable onStartedRunnable,
Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener);
+ AnimatorListenerAdapter animationListener, ClipSide clipSide);
+
+ public enum ClipSide {
+ TOP,
+ BOTTOM
+ }
public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
performAddAnimation(delay, duration, isHeadsUpAppear, null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
index 816e5c1..db3cf5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.row
import android.app.Flags
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import javax.inject.Inject
/**
@@ -27,11 +28,14 @@
fun shouldApplyCompactStyle(): Boolean
}
-class HeadsUpStyleProviderImpl @Inject constructor() : HeadsUpStyleProvider {
+class HeadsUpStyleProviderImpl
+@Inject
+constructor(private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore) :
+ HeadsUpStyleProvider {
- /**
- * TODO(b/270709257) This feature is under development. This method returns Compact when the
- * flag is enabled for fish fooding purpose.
- */
- override fun shouldApplyCompactStyle(): Boolean = Flags.compactHeadsUpNotification()
+ override fun shouldApplyCompactStyle(): Boolean {
+ // Use compact HUN for immersive mode.
+ return Flags.compactHeadsUpNotification() &&
+ statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 162e8af..291dc13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -252,7 +252,7 @@
float translationDirection, boolean isHeadsUpAnimation,
Runnable onStartedRunnable,
Runnable onFinishedRunnable,
- AnimatorListenerAdapter animationListener) {
+ AnimatorListenerAdapter animationListener, ClipSide clipSide) {
// TODO: Use duration
if (onStartedRunnable != null) {
onStartedRunnable.run();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
index 0344b32..d4f8ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
@@ -33,7 +33,12 @@
/** Is the heads-up cycling animation enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.notificationContentAlphaOptimization()
+ get() = Flags.notificationHeadsUpCycling()
+
+ /** Whether to animate the bottom line when transiting from a tall HUN to a short HUN */
+ @JvmStatic
+ inline val animateTallToShort
+ get() = false
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index e520957..5f4e832 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -293,6 +293,8 @@
}
String getAvalancheShowingHunKey() {
+ // If we don't have a previous showing hun, we don't consider the showing hun as avalanche
+ if (isNullAvalancheKey(getAvalanchePreviousHunKey())) return "";
return mAvalancheController.getShowingHunKey();
}
@@ -300,6 +302,11 @@
return mAvalancheController.getPreviousHunKey();
}
+ boolean isNullAvalancheKey(String key) {
+ if (key == null || key.isEmpty()) return true;
+ return key.equals("HeadsUpEntry null") || key.equals("HeadsUpEntry.mEntry null");
+ }
+
void setOverExpansion(float overExpansion) {
mOverExpansion = overExpansion;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index 5551ab4..bd7bd59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -70,13 +70,14 @@
}
override fun performRemoveAnimation(
- duration: Long,
- delay: Long,
- translationDirection: Float,
- isHeadsUpAnimation: Boolean,
- onStartedRunnable: Runnable?,
- onFinishedRunnable: Runnable?,
- animationListener: AnimatorListenerAdapter?
+ duration: Long,
+ delay: Long,
+ translationDirection: Float,
+ isHeadsUpAnimation: Boolean,
+ onStartedRunnable: Runnable?,
+ onFinishedRunnable: Runnable?,
+ animationListener: AnimatorListenerAdapter?,
+ clipSide: ClipSide
): Long {
return 0
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 232b4e9..bfc7425 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -112,6 +112,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -151,7 +152,6 @@
public class NotificationStackScrollLayout
extends ViewGroup
implements Dumpable, NotificationScrollView {
-
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
private static final String TAG = "StackScroller";
private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
@@ -3144,6 +3144,11 @@
type = row.wasJustClicked()
? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
: AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ if (mStackScrollAlgorithm.isCyclingOut(row, mAmbientState)) {
+ type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT;
+ }
+ }
if (row.isChildInGroup()) {
// We can otherwise get stuck in there if it was just isolated
row.setHeadsUpAnimatingAway(false);
@@ -3164,6 +3169,11 @@
if (pinnedAndClosed || shouldHunAppearFromTheBottom) {
// Our custom add animation
type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ if (mStackScrollAlgorithm.isCyclingIn(row, mAmbientState)) {
+ type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN;
+ }
+ }
} else {
// Normal add animation
type = AnimationEvent.ANIMATION_TYPE_ADD;
@@ -6135,6 +6145,22 @@
.animateTopInset()
.animateY()
.animateZ(),
+
+ // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT
+ new AnimationFilter()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ .hasDelays(),
+
+ // ANIMATION_TYPE_HEADS_UP_CYCLING_IN
+ new AnimationFilter()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ .hasDelays(),
};
static int[] LENGTHS = new int[]{
@@ -6186,6 +6212,12 @@
// ANIMATION_TYPE_EVERYTHING
StackStateAnimator.ANIMATION_DURATION_STANDARD,
+
+ // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT
+ StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING,
+
+ // ANIMATION_TYPE_HEADS_UP_CYCLING_IN
+ StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING,
};
static final int ANIMATION_TYPE_ADD = 0;
@@ -6204,6 +6236,8 @@
static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 13;
static final int ANIMATION_TYPE_HEADS_UP_OTHER = 14;
static final int ANIMATION_TYPE_EVERYTHING = 15;
+ static final int ANIMATION_TYPE_HEADS_UP_CYCLING_OUT = 16;
+ static final int ANIMATION_TYPE_HEADS_UP_CYCLING_IN = 17;
final long eventStartTime;
final ExpandableView mChangingView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index d0cebae..0fcfc4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -38,6 +38,7 @@
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import java.util.ArrayList;
@@ -75,6 +76,7 @@
private float mSmallCornerRadius;
private float mLargeCornerRadius;
private int mHeadsUpAppearHeightBottom;
+ private int mHeadsUpCyclingPadding;
public StackScrollAlgorithm(
Context context,
@@ -99,6 +101,8 @@
R.dimen.heads_up_status_bar_padding);
mHeadsUpAppearStartAboveScreen = res.getDimensionPixelSize(
R.dimen.heads_up_appear_y_above_screen);
+ mHeadsUpCyclingPadding = context.getResources()
+ .getDimensionPixelSize(R.dimen.heads_up_cycling_padding);
mPinnedZTranslationExtra = res.getDimensionPixelSize(
R.dimen.heads_up_pinned_elevation);
mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
@@ -348,7 +352,8 @@
&& !firstHeadsUp
&& (isHeadsUp || child.isHeadsUpAnimatingAway())
&& newNotificationEnd > firstHeadsUpEnd
- && !ambientState.isShadeExpanded()) {
+ && !ambientState.isShadeExpanded()
+ && !skipClipBottomForCycling(child, ambientState)) {
// The bottom of this view is peeking out from under the previous view.
// Clip the part that is peeking out.
float overlapAmount = newNotificationEnd - firstHeadsUpEnd;
@@ -370,6 +375,44 @@
}
}
+ /**
+ * @return Should we skip clipping the bottom clipping when new hun has lower bottom line for
+ * the hun cycling animation.
+ */
+ private boolean skipClipBottomForCycling(ExpandableView view, AmbientState ambientState) {
+ if (!NotificationHeadsUpCycling.isEnabled()) return false;
+ if (!isCyclingOut(view, ambientState)) return false;
+ // skip bottom clipping if we animate the bottom line
+ return NotificationHeadsUpCycling.getAnimateTallToShort();
+ }
+
+ /**
+ * Whether the view is the hun that is cycling out by the notification avalanche.
+ */
+ public boolean isCyclingOut(ExpandableView view, AmbientState ambientState) {
+ if (!NotificationHeadsUpCycling.isEnabled()) return false;
+ if (!(view instanceof ExpandableNotificationRow)) return false;
+ return isCyclingOut((ExpandableNotificationRow) view, ambientState);
+ }
+
+ /**
+ * Whether the row is the hun that is cycling out by the notification avalanche.
+ */
+ public boolean isCyclingOut(ExpandableNotificationRow row, AmbientState ambientState) {
+ if (!NotificationHeadsUpCycling.isEnabled()) return false;
+ String cyclingOutKey = ambientState.getAvalanchePreviousHunKey();
+ return row.getEntry().getKey().equals(cyclingOutKey);
+ }
+
+ /**
+ * Whether the row is the hun that is cycling in by the notification avalanche.
+ */
+ public boolean isCyclingIn(ExpandableNotificationRow row, AmbientState ambientState) {
+ if (!NotificationHeadsUpCycling.isEnabled()) return false;
+ String cyclingInKey = ambientState.getAvalancheShowingHunKey();
+ return row.getEntry().getKey().equals(cyclingInKey);
+ }
+
/** Updates the dimmed and hiding sensitive states of the children. */
private void updateDimmedAndHideSensitive(AmbientState ambientState,
StackScrollAlgorithmState algorithmState) {
@@ -799,6 +842,7 @@
}
ExpandableNotificationRow topHeadsUpEntry = null;
+ int cyclingInHunHeight = -1;
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
if (!(child instanceof ExpandableNotificationRow row)) {
@@ -839,6 +883,13 @@
childState.setYTranslation(
Math.max(childState.getYTranslation(), headsUpTranslation));
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
+ if (NotificationHeadsUpCycling.isEnabled()) {
+ if (isCyclingIn(row, ambientState)) {
+ if (cyclingInHunHeight == -1) {
+ cyclingInHunHeight = childState.height;
+ }
+ }
+ }
childState.hidden = false;
ExpandableViewState topState =
topHeadsUpEntry == null ? null : topHeadsUpEntry.getViewState();
@@ -860,6 +911,26 @@
}
}
if (row.isHeadsUpAnimatingAway()) {
+ if (NotificationHeadsUpCycling.isEnabled() && isCyclingOut(row, ambientState)) {
+ // If the two HUNs in the cycling animation have different heights, we need
+ // an extra y translation to align the animation.
+ int extraTranslation;
+ if (NotificationHeadsUpCycling.getAnimateTallToShort()) {
+ if (cyclingInHunHeight > 0) {
+ extraTranslation = cyclingInHunHeight - childState.height;
+ } else {
+ extraTranslation = 0;
+ }
+ } else {
+ extraTranslation = cyclingInHunHeight >= childState.height
+ ? cyclingInHunHeight - childState.height : 0;
+ }
+ extraTranslation += mHeadsUpCyclingPadding;
+ float inSpaceTranslation = Math.max(childState.getYTranslation(),
+ headsUpTranslation);
+ childState.setYTranslation(inSpaceTranslation + extraTranslation);
+ cyclingInHunHeight = -1;
+ } else
if (NotificationsImprovedHunAnimation.isEnabled() && !ambientState.isDozing()) {
if (shouldHunAppearFromBottom(ambientState, childState)) {
// move to the bottom of the screen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 5963d35..5dc5449 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.stack;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK;
@@ -57,6 +59,7 @@
public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
+ public static final int ANIMATION_DURATION_HEADS_UP_CYCLING = 400;
public static final int ANIMATION_DURATION_FOLD_TO_AOD =
AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD;
public static final int ANIMATION_DURATION_PRIORITY_CHANGE = 500;
@@ -68,6 +71,8 @@
@VisibleForTesting int mGoToFullShadeAppearingTranslation;
@VisibleForTesting float mHeadsUpAppearStartAboveScreen;
+ // Padding between the old and new heads up notifications for the hun cycling animation
+ private float mHeadsUpCyclingPadding;
private final ExpandableViewState mTmpState = new ExpandableViewState();
private final AnimationProperties mAnimationProperties;
public NotificationStackScrollLayout mHostLayout;
@@ -125,6 +130,8 @@
R.dimen.go_to_full_shade_appearing_translation);
mHeadsUpAppearStartAboveScreen = context.getResources()
.getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
+ mHeadsUpCyclingPadding = context.getResources()
+ .getDimensionPixelSize(R.dimen.heads_up_cycling_padding);
}
protected void setLogger(StackStateLogger logger) {
@@ -449,7 +456,8 @@
}
changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
- startAnimation, postAnimation, getGlobalAnimationFinishedListener());
+ startAnimation, postAnimation, getGlobalAnimationFinishedListener(),
+ ExpandableView.ClipSide.BOTTOM);
needsCustomAnimation = true;
} else if (event.animationType ==
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
@@ -464,6 +472,27 @@
.AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
ExpandableNotificationRow row = (ExpandableNotificationRow) event.mChangingView;
row.prepareExpansionChanged();
+ } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_IN) {
+ mHeadsUpAppearChildren.add(changingView);
+
+ mTmpState.copyFrom(changingView.getViewState());
+ mTmpState.setYTranslation(changingView.getViewState().getYTranslation()
+ + getHeadsUpCyclingInYTranslationStart(event.headsUpFromBottom));
+ mTmpState.applyToView(changingView);
+
+ // TODO(b/339519404): use a different interpolator
+ Runnable onAnimationEnd = null;
+ if (loggable) {
+ // This only captures HEADS_UP_APPEAR animations, but HUNs can appear with
+ // normal ADD animations, which would not be logged here.
+ String finalKey = key;
+ mLogger.logHUNViewAppearing(key);
+ onAnimationEnd = () -> {
+ mLogger.appearAnimationEnded(finalKey);
+ };
+ }
+ changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_CYCLING,
+ /* isHeadsUpAppear= */ true, onAnimationEnd);
} else if (NotificationsImprovedHunAnimation.isEnabled()
&& (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR)) {
mHeadsUpAppearChildren.add(changingView);
@@ -486,6 +515,87 @@
}
changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
/* isHeadsUpAppear= */ true, onAnimationEnd);
+ } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_OUT) {
+ mHeadsUpDisappearChildren.add(changingView);
+ Runnable endRunnable = null;
+ mTmpState.copyFrom(changingView.getViewState());
+
+ if (changingView.getParent() == null) {
+ // This notification was actually removed, so we need to add it
+ // transiently
+ mHostLayout.addTransientView(changingView, 0);
+ changingView.setTransientContainer(mHostLayout);
+ // TODO(b/316404716): remove the hard-coded height
+ // StackScrollAlgorithm cannot find this view because it has been removed
+ // from the NSSL. To correctly translate the view to the top or bottom of
+ // the screen (where it animated from), we need to update its translation.
+ mTmpState.setYTranslation(
+ mTmpState.getYTranslation() + 10
+ );
+ endRunnable = changingView::removeFromTransientContainer;
+ }
+
+ boolean needsAnimation = true;
+ if (changingView instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row =
+ (ExpandableNotificationRow) changingView;
+ if (row.isDismissed()) {
+ needsAnimation = false;
+ }
+ }
+ if (needsAnimation) {
+ // We need to add the global animation listener, since once no animations are
+ // running anymore, the panel will instantly hide itself. We need to wait until
+ // the animation is fully finished for this though.
+ final Runnable tmpEndRunnable = endRunnable;
+ Runnable postAnimation;
+ Runnable startAnimation;
+ if (loggable) {
+ String finalKey1 = key;
+ final boolean finalIsHeadsUp = isHeadsUp;
+ final String type = "ANIMATION_TYPE_HEADS_UP_CYCLING_OUT";
+ startAnimation = () -> {
+ mLogger.animationStart(finalKey1, type, finalIsHeadsUp);
+ changingView.setInRemovalAnimation(true);
+ };
+ postAnimation = () -> {
+ mLogger.animationEnd(finalKey1, type, finalIsHeadsUp);
+ changingView.setInRemovalAnimation(false);
+ if (tmpEndRunnable != null) {
+ tmpEndRunnable.run();
+ }
+
+ };
+ } else {
+ postAnimation = () -> {
+ changingView.setInRemovalAnimation(false);
+ if (tmpEndRunnable != null) {
+ tmpEndRunnable.run();
+ }
+ };
+ startAnimation = () -> {
+ changingView.setInRemovalAnimation(true);
+ };
+ }
+ long removeAnimationDelay = changingView.performRemoveAnimation(
+ ANIMATION_DURATION_HEADS_UP_CYCLING,
+ /* delay= */ 0,
+ // It's a shame that translationDirection isn't where we do the y
+ // translation, the actual translation is in StackScrollAlgorithm.
+ /* translationDirection= */ 0.0f,
+ /* isHeadsUpAnimation= */ true,
+ startAnimation, postAnimation,
+ getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.TOP);
+ mAnimationProperties.delay += removeAnimationDelay;
+ mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_CYCLING;
+ mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+ Interpolators.LINEAR);
+ mAnimationProperties.getAnimationFilter().animateY = true;
+ mTmpState.animateTo(changingView, mAnimationProperties);
+ } else if (endRunnable != null) {
+ endRunnable.run();
+ }
+ needsCustomAnimation |= needsAnimation;
} else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) {
NotificationsImprovedHunAnimation.assertInLegacyMode();
// This item is added, initialize its properties.
@@ -565,21 +675,21 @@
}
};
} else {
+ startAnimation = () -> {
+ changingView.setInRemovalAnimation(true);
+ };
postAnimation = () -> {
changingView.setInRemovalAnimation(false);
if (tmpEndRunnable != null) {
tmpEndRunnable.run();
}
};
- startAnimation = () -> {
- changingView.setInRemovalAnimation(true);
- };
}
long removeAnimationDelay = changingView.performRemoveAnimation(
ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
0, 0.0f, true /* isHeadsUpAppear */,
startAnimation, postAnimation,
- getGlobalAnimationFinishedListener());
+ getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.BOTTOM);
mAnimationProperties.delay += removeAnimationDelay;
if (NotificationsImprovedHunAnimation.isEnabled()) {
mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
@@ -607,6 +717,38 @@
return -mStackTopMargin - mHeadsUpAppearStartAboveScreen;
}
+ /**
+ * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen
+ * @return The start y translation of the HUN cycling in animation
+ */
+ private float getHeadsUpCyclingInYTranslationStart(boolean headsUpFromBottom) {
+ if (headsUpFromBottom) {
+ // start from the bottom of the screen
+ return mHeadsUpAppearHeightBottom + mHeadsUpCyclingPadding;
+ }
+ // start from the top of the screen
+ return -mHeadsUpCyclingPadding;
+ }
+
+ /**
+ * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen
+ * @param oldHunHeight Height of the old HUN
+ * @param newHunHeight Height of the new HUN
+ * @return The y translation target value of the HUN cycling out animation
+ */
+ private float getHeadsUpCyclingOutYTranslation(
+ boolean headsUpFromBottom,
+ int oldHunHeight,
+ int newHunHeight
+ ) {
+ final float translationDistance = mHeadsUpCyclingPadding + newHunHeight - oldHunHeight;
+ if (headsUpFromBottom) {
+ // start from the bottom of the screen
+ return mHeadsUpAppearHeightBottom - translationDistance;
+ }
+ return translationDistance;
+ }
+
public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
final boolean isRubberbanded) {
final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index be6bef7..23674b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2184,7 +2184,9 @@
}
if (mStatusBarStateController.leaveOpenOnKeyguardHide()) {
if (!mStatusBarStateController.isKeyguardRequested()) {
- mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
+ if (!MigrateClocksToBlueprint.isEnabled()) {
+ mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
+ }
}
long delay = mKeyguardStateController.calculateGoingToFullShadeDelay();
mLockscreenShadeTransitionController.onHideKeyguard(delay, previousState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
index d9d909a..fc54f14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
@@ -33,6 +33,18 @@
*/
val isOpportunistic: Boolean = false,
+ /**
+ * True if this subscription **only** supports non-terrestrial networks (NTN) and false
+ * otherwise. (non-terrestrial == satellite)
+ *
+ * Note that we intend to filter these subscriptions out, because these connections are actually
+ * supported by
+ * [com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository]. See
+ * [com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor] for
+ * the filtering.
+ */
+ val isExclusivelyNonTerrestrial: Boolean = false,
+
/** Subscriptions in the same group may be filtered or treated as a single subscription */
val groupUuid: ParcelUuid? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 2278597..425c58b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -23,6 +23,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.StateFlow
@@ -76,7 +77,17 @@
*/
val isInService: StateFlow<Boolean>
- /** Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork] */
+ /**
+ * True if this subscription is actively connected to a non-terrestrial network and false
+ * otherwise. Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork].
+ *
+ * Notably: This value reflects that this subscription is **currently** using a non-terrestrial
+ * network, because some subscriptions can switch between terrestrial and non-terrestrial
+ * networks. [SubscriptionModel.isExclusivelyNonTerrestrial] reflects whether a subscription is
+ * configured to exclusively connect to non-terrestrial networks. [isNonTerrestrial] can change
+ * during the lifetime of a subscription but [SubscriptionModel.isExclusivelyNonTerrestrial]
+ * will stay constant.
+ */
val isNonTerrestrial: StateFlow<Boolean>
/** True if [android.telephony.SignalStrength] told us that this connection is using GSM */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 5d91ef3..0073e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -424,6 +424,7 @@
SubscriptionModel(
subscriptionId = subscriptionId,
isOpportunistic = isOpportunistic,
+ isExclusivelyNonTerrestrial = isOnlyNonTerrestrialNetwork,
groupUuid = groupUuid,
carrierName = carrierName.toString(),
profileClass = profileClass,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index d555c47..91d7ca6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -172,21 +172,33 @@
private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> =
mobileConnectionsRepo.subscriptions
- /**
- * Any filtering that we can do based purely on the info of each subscription. Currently this
- * only applies the ProfileClass-based filter, but if we need other they can go here
- */
+ /** Any filtering that we can do based purely on the info of each subscription individually. */
private val subscriptionsBasedFilteredSubs =
- unfilteredSubscriptions.map { subs -> applyProvisioningFilter(subs) }.distinctUntilChanged()
+ unfilteredSubscriptions
+ .map { it.filterBasedOnProvisioning().filterBasedOnNtn() }
+ .distinctUntilChanged()
- private fun applyProvisioningFilter(subs: List<SubscriptionModel>): List<SubscriptionModel> =
+ private fun List<SubscriptionModel>.filterBasedOnProvisioning(): List<SubscriptionModel> =
if (!featureFlagsClassic.isEnabled(FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS)) {
- subs
+ this
} else {
- subs.filter { it.profileClass != PROFILE_CLASS_PROVISIONING }
+ this.filter { it.profileClass != PROFILE_CLASS_PROVISIONING }
}
/**
+ * Subscriptions that exclusively support non-terrestrial networks should **never** directly
+ * show any iconography in the status bar. These subscriptions only exist to provide a backing
+ * for the device-based satellite connections, and the iconography for those connections are
+ * already being handled in
+ * [com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository]. We
+ * need to filter out those subscriptions here so we guarantee the subscription never turns into
+ * an icon. See b/336881301.
+ */
+ private fun List<SubscriptionModel>.filterBasedOnNtn(): List<SubscriptionModel> {
+ return this.filter { !it.isExclusivelyNonTerrestrial }
+ }
+
+ /**
* Generally, SystemUI wants to show iconography for each subscription that is listed by
* [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only
* show a single representation of the pair of subscriptions. The docs define opportunistic as:
@@ -204,12 +216,8 @@
subscriptionsBasedFilteredSubs,
mobileConnectionsRepo.activeMobileDataSubscriptionId,
connectivityRepository.vcnSubId,
- ) { unfilteredSubs, activeId, vcnSubId ->
- filterSubsBasedOnOpportunistic(
- unfilteredSubs,
- activeId,
- vcnSubId,
- )
+ ) { preFilteredSubs, activeId, vcnSubId ->
+ filterSubsBasedOnOpportunistic(preFilteredSubs, activeId, vcnSubId)
}
.distinctUntilChanged()
.logDiffsForTable(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 2670a95..fa8a7d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -253,6 +253,7 @@
if (nextList.isEmpty()) {
log { "NO MORE TO SHOW" }
+ previousHunKey = ""
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt
new file mode 100644
index 0000000..ee1b565
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import com.android.systemui.statusbar.policy.LocationController
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
+
+fun LocationController.isLocationEnabledFlow(): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val locationCallback =
+ object : LocationController.LocationChangeCallback {
+ override fun onLocationSettingsChanged(locationEnabled: Boolean) {
+ trySend(locationEnabled)
+ }
+ }
+ addCallback(locationCallback)
+ awaitClose { removeCallback(locationCallback) }
+ }
+ .onStart { emit(isLocationEnabled) }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index e72027a..6f550ba 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -371,7 +371,7 @@
}
@Test
- fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToOne() =
+ fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToZero() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN))
@@ -434,6 +434,27 @@
}
@Test
+ fun listenForAnyStateToDozingTransition_UpdatesClockDozeAmountToOne() =
+ runBlocking(IMMEDIATE) {
+ val transitionStep = MutableStateFlow(TransitionStep())
+ whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.DOZING))
+ .thenReturn(transitionStep)
+
+ val job = underTest.listenForAnyStateToDozingTransition(this)
+ transitionStep.value =
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ transitionState = TransitionState.STARTED,
+ )
+ yield()
+
+ verify(animations, times(2)).doze(1f)
+
+ job.cancel()
+ }
+
+ @Test
fun unregisterListeners_validate() =
runBlocking(IMMEDIATE) {
underTest.unregisterListeners()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 41974f4..8e4c155 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -46,7 +46,8 @@
@RunWithLooper
class ActivityTransitionAnimatorTest : SysuiTestCase() {
private val transitionContainer = LinearLayout(mContext)
- private val testTransitionAnimator = fakeTransitionAnimator()
+ private val mainExecutor = context.mainExecutor
+ private val testTransitionAnimator = fakeTransitionAnimator(mainExecutor)
@Mock lateinit var callback: ActivityTransitionAnimator.Callback
@Mock lateinit var listener: ActivityTransitionAnimator.Listener
@Spy private val controller = TestTransitionAnimatorController(transitionContainer)
@@ -59,9 +60,10 @@
fun setup() {
activityTransitionAnimator =
ActivityTransitionAnimator(
+ mainExecutor,
testTransitionAnimator,
testTransitionAnimator,
- disableWmTimeout = true
+ disableWmTimeout = true,
)
activityTransitionAnimator.callback = callback
activityTransitionAnimator.addListener(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
index d84a578..e14762cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt
@@ -156,6 +156,7 @@
fun testActivityLaunchWhenLockedWithoutAlternateAuth() {
val dialogTransitionAnimator =
fakeDialogTransitionAnimator(
+ mainExecutor = mContext.mainExecutor,
isUnlocked = false,
isShowingAlternateAuthOnUnlock = false,
interactionJankMonitor = kosmos.interactionJankMonitor)
@@ -166,6 +167,7 @@
@Test
fun testActivityLaunchWhenLockedWithAlternateAuth() {
val dialogTransitionAnimator = fakeDialogTransitionAnimator(
+ mainExecutor = mContext.mainExecutor,
isUnlocked = false,
isShowingAlternateAuthOnUnlock = true,
interactionJankMonitor = kosmos.interactionJankMonitor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index e64df90..259ece9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -25,6 +25,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.activity.EmptyTestActivity
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -58,9 +60,11 @@
)
}
+ private val kosmos = Kosmos()
private val pathManager = GoldenPathManager(context, GOLDENS_PATH, pathConfig = PathConfig())
private val transitionAnimator =
TransitionAnimator(
+ kosmos.fakeExecutor,
ActivityTransitionAnimator.TIMINGS,
ActivityTransitionAnimator.INTERPOLATORS
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
index 2172bc5..8695c01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -5,12 +5,12 @@
import android.hardware.biometrics.PromptVerticalListContentView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.data.repository.FakePromptRepository
import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
import com.android.systemui.biometrics.promptInfo
import com.android.systemui.biometrics.shared.model.BiometricUserInfo
+import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -110,7 +110,7 @@
it.description = description
it.subtitle = subtitle
},
- kind = Utils.CREDENTIAL_PIN,
+ kind = PromptKind.Pin,
userId = USER_ID,
challenge = OPERATION_ID,
opPackageName = OP_PACKAGE_NAME
@@ -135,7 +135,7 @@
it.subtitle = subtitle
it.contentView = contentView
},
- kind = Utils.CREDENTIAL_PIN,
+ kind = PromptKind.Pin,
userId = USER_ID,
challenge = OPERATION_ID,
opPackageName = OP_PACKAGE_NAME
@@ -163,7 +163,7 @@
it.subtitle = subtitle
it.contentView = contentView
},
- kind = Utils.CREDENTIAL_PIN,
+ kind = PromptKind.Pin,
userId = USER_ID,
challenge = OPERATION_ID,
opPackageName = OP_PACKAGE_NAME
@@ -171,13 +171,13 @@
assertThat(showTitleOnly).isFalse()
}
- @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PIN)
+ @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(PromptKind.Pin)
- @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PASSWORD)
+ @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(PromptKind.Password)
- @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PATTERN)
+ @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(PromptKind.Pattern)
- private fun useCredentialForPrompt(kind: Int) =
+ private fun useCredentialForPrompt(kind: PromptKind) =
testScope.runTest {
val isStealth = false
credentialInteractor.stealthMode = isStealth
@@ -211,11 +211,10 @@
assertThat(prompt)
.isInstanceOf(
when (kind) {
- Utils.CREDENTIAL_PIN -> BiometricPromptRequest.Credential.Pin::class.java
- Utils.CREDENTIAL_PASSWORD ->
+ PromptKind.Pin -> BiometricPromptRequest.Credential.Pin::class.java
+ PromptKind.Password ->
BiometricPromptRequest.Credential.Password::class.java
- Utils.CREDENTIAL_PATTERN ->
- BiometricPromptRequest.Credential.Pattern::class.java
+ PromptKind.Pattern -> BiometricPromptRequest.Credential.Pattern::class.java
else -> throw Exception("wrong kind")
}
)
@@ -341,6 +340,28 @@
job.cancel()
}
+
+ /** Update the current request to use credential-based authentication instead of biometrics. */
+ private fun PromptCredentialInteractor.useCredentialsForAuthentication(
+ promptInfo: PromptInfo,
+ kind: PromptKind,
+ userId: Int,
+ challenge: Long,
+ opPackageName: String,
+ ) {
+ biometricPromptRepository.setPrompt(
+ promptInfo,
+ userId,
+ challenge,
+ kind,
+ opPackageName,
+ )
+ }
+
+ /** Unset the current authentication request. */
+ private fun PromptCredentialInteractor.resetPrompt() {
+ biometricPromptRepository.unsetPrompt()
+ }
}
private fun pinRequest(): BiometricPromptRequest.Credential.Pin =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index 2817780..c308507 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -22,7 +22,6 @@
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakePromptRepository
import com.android.systemui.biometrics.faceSensorPropertiesInternal
@@ -143,21 +142,20 @@
}
@Test
- fun usePinCredentialAndReset() =
- testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PIN) }
+ fun usePinCredentialAndReset() = testScope.runTest { useCredentialAndReset(PromptKind.Pin) }
@Test
fun usePatternCredentialAndReset() =
- testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PATTERN) }
+ testScope.runTest { useCredentialAndReset(PromptKind.Pattern) }
@Test
fun usePasswordCredentialAndReset() =
- testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PASSWORD) }
+ testScope.runTest { useCredentialAndReset(PromptKind.Password) }
- private fun TestScope.useCredentialAndReset(@Utils.CredentialType kind: Int) {
+ private fun TestScope.useCredentialAndReset(kind: PromptKind) {
setUserCredentialType(
- isPin = kind == Utils.CREDENTIAL_PIN,
- isPassword = kind == Utils.CREDENTIAL_PASSWORD,
+ isPin = kind == PromptKind.Pin,
+ isPassword = kind == PromptKind.Password,
)
val info =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
index 5caa146..0d01472 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
@@ -16,97 +16,95 @@
package com.android.systemui.biometrics.ui.viewmodel
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.TestMocksModule
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.flags.parameterizeSceneContainerFlag
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.user.domain.UserDomainLayerModule
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.statusbar.phone.systemUIDialogManager
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
-class DefaultUdfpsTouchOverlayViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class DefaultUdfpsTouchOverlayViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
+ }
+ private val testScope = kosmos.testScope
+
@Captor
private lateinit var sysuiDialogListenerCaptor: ArgumentCaptor<SystemUIDialogManager.Listener>
- private var systemUIDialogManager: SystemUIDialogManager = mock()
+ private var systemUIDialogManager = kosmos.systemUIDialogManager
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+ private lateinit var underTest: DefaultUdfpsTouchOverlayViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return parameterizeSceneContainerFlag()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ underTest =
+ DefaultUdfpsTouchOverlayViewModel(
+ kosmos.shadeInteractor,
+ systemUIDialogManager,
+ )
}
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- UserDomainLayerModule::class,
- BiometricsDomainLayerModule::class,
- ]
- )
- interface TestComponent : SysUITestComponent<DefaultUdfpsTouchOverlayViewModel> {
- val keyguardRepository: FakeKeyguardRepository
- val shadeRepository: FakeShadeRepository
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- featureFlags: FakeFeatureFlagsClassicModule,
- mocks: TestMocksModule,
- ): TestComponent
- }
- }
-
- private fun TestComponent.shadeExpanded(expanded: Boolean) {
+ private fun shadeExpanded(expanded: Boolean) {
if (expanded) {
- shadeRepository.setLegacyShadeExpansion(1f)
- shadeRepository.setLegacyShadeTracking(false)
- shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(true)
+ shadeTestUtil.setShadeExpansion(1f)
+ shadeTestUtil.setTracking(false)
+ shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(true)
} else {
keyguardRepository.setStatusBarState(StatusBarState.SHADE)
- shadeRepository.setLegacyShadeExpansion(0f)
- shadeRepository.setLegacyShadeTracking(false)
- shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(false)
+ shadeTestUtil.setShadeExpansion(0f)
+ shadeTestUtil.setTracking(false)
+ shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(false)
}
}
- private val testComponent: TestComponent =
- DaggerDefaultUdfpsTouchOverlayViewModelTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
- mocks = TestMocksModule(systemUIDialogManager = systemUIDialogManager),
- )
-
@Test
+ @BrokenWithSceneContainer(339465026)
fun shadeNotExpanded_noDialogShowing_shouldHandleTouchesTrue() =
- testComponent.runTest {
+ testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
runCurrent()
@@ -120,7 +118,7 @@
@Test
fun shadeNotExpanded_dialogShowing_shouldHandleTouchesFalse() =
- testComponent.runTest {
+ testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
runCurrent()
@@ -134,7 +132,7 @@
@Test
fun shadeExpanded_noDialogShowing_shouldHandleTouchesFalse() =
- testComponent.runTest {
+ testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index 28cbcb4..4bcd9a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothDevice
-import android.content.pm.PackageInfo
+import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.media.AudioManager
import android.platform.test.annotations.DisableFlags
@@ -25,7 +25,6 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
-import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.flags.Flags
import com.android.systemui.SysuiTestCase
@@ -120,11 +119,10 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testSavedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
- val exclusiveManagerName =
- BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
`when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(exclusiveManagerName.toByteArray())
- `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo())
+ .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+ `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+ .thenReturn(ApplicationInfo())
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(cachedDevice.isConnected).thenReturn(false)
@@ -144,11 +142,11 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testSavedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() {
+ fun testSavedFactory_isFilterMatched_exclusiveManagerNotEnabled_returnsTrue() {
`when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray())
- `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0))
- .thenReturn(PackageInfo())
+ .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+ `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+ .thenReturn(ApplicationInfo().also { it.enabled = false })
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(cachedDevice.isConnected).thenReturn(false)
@@ -158,12 +156,10 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testSavedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() {
- val exclusiveManagerName =
- BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+ fun testSavedFactory_isFilterMatched_exclusiveManagerNotInstalled_returnsTrue() {
`when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(exclusiveManagerName.toByteArray())
- `when`(packageManager.getPackageInfo(exclusiveManagerName, 0))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+ `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
.thenThrow(PackageManager.NameNotFoundException("Test!"))
`when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(cachedDevice.isConnected).thenReturn(false)
@@ -228,11 +224,10 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
fun testConnectedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
- val exclusiveManagerName =
- BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
`when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(exclusiveManagerName.toByteArray())
- `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo())
+ .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+ `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+ .thenReturn(ApplicationInfo())
`when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(bluetoothDevice.isConnected).thenReturn(true)
audioManager.setMode(AudioManager.MODE_NORMAL)
@@ -254,11 +249,11 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testConnectedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() {
+ fun testConnectedFactory_isFilterMatched_exclusiveManagerNotEnabled_returnsTrue() {
`when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray())
- `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0))
- .thenReturn(PackageInfo())
+ .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+ `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
+ .thenReturn(ApplicationInfo().also { it.enabled = false })
`when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(bluetoothDevice.isConnected).thenReturn(true)
audioManager.setMode(AudioManager.MODE_NORMAL)
@@ -269,12 +264,10 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
- fun testConnectedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() {
- val exclusiveManagerName =
- BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+ fun testConnectedFactory_isFilterMatched_exclusiveManagerNotInstalled_returnsTrue() {
`when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
- .thenReturn(exclusiveManagerName.toByteArray())
- `when`(packageManager.getPackageInfo(exclusiveManagerName, 0))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray())
+ `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0))
.thenThrow(PackageManager.NameNotFoundException("Test!"))
`when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
`when`(bluetoothDevice.isConnected).thenReturn(true)
@@ -317,7 +310,7 @@
companion object {
const val DEVICE_NAME = "DeviceName"
const val CONNECTION_SUMMARY = "ConnectionSummary"
- private const val FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name"
+ private const val TEST_EXCLUSIVE_MANAGER = "com.test.manager"
private const val DEVICE_ADDRESS = "04:52:C7:0B:D8:3C"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
index bed05ee..cde7a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
@@ -30,7 +30,6 @@
import java.nio.charset.Charset
import org.junit.After
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -102,12 +101,6 @@
assertThat(dataRead).isEqualTo(newDataToWrite)
}
- @Ignore("Ignored until we figure out why it is flaky b/336561027")
- @Test(expected = FileNotFoundException::class)
- fun read_fileNotFoundException() {
- underTest.readBytesFromDisk()
- }
-
@Test(expected = FileNotFoundException::class)
fun clear_returnsTrueWhenFileDeleted() {
// Write bytes to disk
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
index 6a0462b..c39c3fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.deviceentry.domain.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository
@@ -24,7 +24,9 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.data.ui.viewmodel.deviceEntryUdfpsAccessibilityOverlayViewModel
+import com.android.systemui.deviceentry.ui.viewmodel.DeviceEntryUdfpsAccessibilityOverlayViewModel
import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -34,19 +36,22 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition
import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@ExperimentalCoroutinesApi
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class UdfpsAccessibilityOverlayViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) }
@@ -59,8 +64,27 @@
private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
private val deviceEntryFingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
- private val shadeRepository = kosmos.fakeShadeRepository
- private val underTest = kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel
+
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
+ private lateinit var underTest: DeviceEntryUdfpsAccessibilityOverlayViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ @Before
+ fun setup() {
+ underTest = kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel
+ }
@Test
fun visible() =
@@ -142,7 +166,7 @@
)
// Shade not expanded
- shadeRepository.qsExpansion.value = 0f
- shadeRepository.lockscreenShadeExpansion.value = 0f
+ shadeTestUtil.setQsExpansion(0f)
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
}
}
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 1dc58d1..687e91a 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
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.StatusBarManager
+import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.keyguard.KeyguardSecurityModel
@@ -29,7 +30,9 @@
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -46,7 +49,8 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.commandQueue
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
@@ -61,13 +65,14 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
/**
* Class for testing user journeys through the interactors. They will all be activated during setup,
@@ -75,8 +80,8 @@
*/
@ExperimentalCoroutinesApi
@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardTransitionScenariosTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
@@ -87,7 +92,7 @@
private val keyguardRepository = kosmos.fakeKeyguardRepository
private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
private var commandQueue = kosmos.fakeCommandQueue
- private val shadeRepository = kosmos.fakeShadeRepository
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
private lateinit var featureFlags: FakeFeatureFlags
@@ -112,6 +117,18 @@
private val communalInteractor = kosmos.communalInteractor
private val dockManager = kosmos.fakeDockManager
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -119,9 +136,11 @@
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
- mSetFlagsRule.disableFlags(
- Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
- )
+ if (!SceneContainerFlag.isEnabled) {
+ mSetFlagsRule.disableFlags(
+ Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ )
+ }
featureFlags = FakeFeatureFlags()
fromLockscreenTransitionInteractor.start()
@@ -210,6 +229,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun lockscreenToDreaming() =
testScope.runTest {
// GIVEN a device that is not dreaming or dozing
@@ -238,6 +258,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun lockscreenToDreamingLockscreenHosted() =
testScope.runTest {
// GIVEN a device that is not dreaming or dozing
@@ -527,6 +548,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun dozingToGoneWithUnlock() =
testScope.runTest {
// GIVEN a prior transition has run to DOZING
@@ -706,6 +728,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun goneToLockscreen() =
testScope.runTest {
// GIVEN a prior transition has run to GONE
@@ -755,6 +778,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun goneToGlanceableHub() =
testScope.runTest {
// GIVEN a prior transition has run to GONE
@@ -897,6 +921,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun alternateBouncerToGone() =
testScope.runTest {
// GIVEN a prior transition has run to ALTERNATE_BOUNCER
@@ -1135,6 +1160,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun occludedToGone() =
testScope.runTest {
// GIVEN a device on lockscreen
@@ -1165,6 +1191,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun occludedToLockscreen() =
testScope.runTest {
// GIVEN a device on lockscreen
@@ -1193,6 +1220,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun occludedToGlanceableHub() =
testScope.runTest {
// GIVEN a device on lockscreen
@@ -1229,6 +1257,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun occludedToGlanceableHubWhenInitiallyOnHub() =
testScope.runTest {
// GIVEN a device on lockscreen and communal is available
@@ -1314,6 +1343,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun primaryBouncerToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1339,6 +1369,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun dozingToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to DOZING
@@ -1364,6 +1395,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun dreamingToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to DREAMING
@@ -1484,6 +1516,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun lockscreenToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to LOCKSCREEN
@@ -1507,6 +1540,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun aodToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to AOD
@@ -1553,6 +1587,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun lockscreenToOccluded_fromCameraGesture() =
testScope.runTest {
// GIVEN a prior transition has run to LOCKSCREEN
@@ -1586,6 +1621,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun lockscreenToPrimaryBouncerDragging() =
testScope.runTest {
// GIVEN a prior transition has run to LOCKSCREEN
@@ -1595,8 +1631,8 @@
// GIVEN the keyguard is showing locked
keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
runCurrent()
- shadeRepository.setLegacyShadeTracking(true)
- shadeRepository.setLegacyShadeExpansion(.9f)
+ shadeTestUtil.setTracking(true)
+ shadeTestUtil.setShadeExpansion(.9f)
runCurrent()
// THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
@@ -1613,8 +1649,8 @@
// WHEN the user stops dragging and shade is back to expanded
clearInvocations(transitionRepository)
runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
- shadeRepository.setLegacyShadeTracking(false)
- shadeRepository.setLegacyShadeExpansion(1f)
+ shadeTestUtil.setTracking(false)
+ shadeTestUtil.setShadeExpansion(1f)
runCurrent()
// THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
@@ -1803,6 +1839,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun glanceableHubToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to GLANCEABLE_HUB
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
index 0bca367..f61ddeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
@@ -19,6 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.coroutines.collectLastValue
@@ -150,6 +151,51 @@
assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.NONE)
}
+ fun accessibilityDelegateHint_accessibilityNotEnabled() =
+ testScope.runTest {
+ val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint)
+ kosmos.fakeAccessibilityRepository.isEnabled.value = false
+ assertThat(accessibilityDelegateHint)
+ .isEqualTo(DeviceEntryIconView.AccessibilityHintType.NONE)
+ }
+
+ @Test
+ fun accessibilityDelegateHint_accessibilityEnabled_locked() =
+ testScope.runTest {
+ val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint)
+ kosmos.fakeAccessibilityRepository.isEnabled.value = true
+
+ // interactive lock icon
+ keyguardRepository.setKeyguardDismissible(false)
+ fingerprintPropertyRepository.supportsUdfps()
+
+ assertThat(accessibilityDelegateHint)
+ .isEqualTo(DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE)
+
+ // non-interactive lock icon
+ keyguardRepository.setKeyguardDismissible(false)
+ fingerprintPropertyRepository.supportsRearFps()
+
+ assertThat(accessibilityDelegateHint)
+ .isEqualTo(DeviceEntryIconView.AccessibilityHintType.NONE)
+ }
+
+ @Test
+ fun accessibilityDelegateHint_accessibilityEnabled_unlocked() =
+ testScope.runTest {
+ val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint)
+ kosmos.fakeAccessibilityRepository.isEnabled.value = true
+
+ // interactive unlock icon
+ keyguardRepository.setKeyguardDismissible(true)
+ fingerprintPropertyRepository.supportsUdfps()
+ advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
+ runCurrent()
+
+ assertThat(accessibilityDelegateHint)
+ .isEqualTo(DeviceEntryIconView.AccessibilityHintType.ENTER)
+ }
+
private fun deviceEntryIconTransitionAlpha(alpha: Float) {
deviceEntryIconTransition.setDeviceEntryParentViewAlpha(alpha)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 1881a9e..16421a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -88,7 +88,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var expandable: Expandable
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
@@ -115,7 +115,7 @@
private val kosmos = testKosmos()
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 0c98cff..768d446 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -53,7 +53,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
val underTest = kosmos.keyguardClockViewModel
@@ -67,7 +67,7 @@
var faceConfig = ClockFaceConfig()
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index 2536a93..9798562 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -5,6 +5,7 @@
import static android.telephony.SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GREAT;
import static android.telephony.SignalStrength.SIGNAL_STRENGTH_POOR;
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource;
@@ -217,6 +218,8 @@
when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(SUB_ID);
+ SubscriptionInfo info = mock(SubscriptionInfo.class);
+ when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info);
when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt()))
.thenReturn(mSystemUIToast);
when(mSystemUIToast.getView()).thenReturn(mToastView);
@@ -1083,19 +1086,34 @@
}
@Test
- public void hasActiveSubId_activeSubIdListIsEmpty_returnFalse() {
- when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{});
+ public void hasActiveSubIdOnDds_noDds_returnFalse() {
+ when(SubscriptionManager.getDefaultDataSubscriptionId())
+ .thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
- assertThat(mInternetDialogController.hasActiveSubId()).isFalse();
+ assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse();
}
@Test
- public void hasActiveSubId_activeSubIdListNotEmpty_returnTrue() {
- when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+ public void hasActiveSubIdOnDds_activeDds_returnTrue() {
mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
- assertThat(mInternetDialogController.hasActiveSubId()).isTrue();
+ assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isTrue();
+ }
+
+ @Test
+ public void hasActiveSubIdOnDds_activeDdsAndHasProvisioning_returnFalse() {
+ when(SubscriptionManager.getDefaultDataSubscriptionId())
+ .thenReturn(SUB_ID);
+ SubscriptionInfo info = mock(SubscriptionInfo.class);
+ when(info.isEmbedded()).thenReturn(true);
+ when(info.getProfileClass()).thenReturn(PROFILE_CLASS_PROVISIONING);
+ when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info);
+
+ mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+ assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse();
}
private String getResourcesString(String name) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
index 6f88891..aefcc87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
@@ -251,7 +251,7 @@
// Mobile network should be gone if the list of active subscriptionId is null.
when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
- when(mInternetDialogController.hasActiveSubId()).thenReturn(false);
+ when(mInternetDialogController.hasActiveSubIdOnDds()).thenReturn(false);
mInternetDialogDelegate.updateDialog(true);
@@ -336,7 +336,7 @@
@Test
public void updateDialog_mobileDataIsEnabled_checkMobileDataSwitch() {
- doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
when(mInternetDialogController.isMobileDataEnabled()).thenReturn(true);
mMobileToggleSwitch.setChecked(false);
@@ -348,7 +348,7 @@
@Test
public void updateDialog_mobileDataIsNotChanged_checkMobileDataSwitch() {
- doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
when(mInternetDialogController.isMobileDataEnabled()).thenReturn(false);
mMobileToggleSwitch.setChecked(false);
@@ -361,7 +361,7 @@
@Test
public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
mInternetDialogDelegate.dismissDialog();
- doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
createInternetDialog();
// The preconditions WiFi ON and Internet WiFi are already in setUp()
doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
@@ -522,7 +522,7 @@
public void updateDialog_showSecondaryDataSub() {
mInternetDialogDelegate.dismissDialog();
doReturn(1).when(mInternetDialogController).getActiveAutoSwitchNonDdsSubId();
- doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
doReturn(false).when(mInternetDialogController).isAirplaneModeEnabled();
createInternetDialog();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 3793970..5b47c94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -446,6 +446,7 @@
mUiEventLogger,
() -> mKosmos.getInteractionJankMonitor(),
mJavaAdapter,
+ () -> mKeyguardTransitionInteractor,
() -> mShadeInteractor,
() -> mKosmos.getDeviceUnlockedInteractor(),
() -> mKosmos.getSceneInteractor(),
@@ -600,6 +601,7 @@
new UiEventLoggerFake(),
() -> mKosmos.getInteractionJankMonitor(),
mJavaAdapter,
+ () -> mKeyguardTransitionInteractor,
() -> mShadeInteractor,
() -> mKosmos.getDeviceUnlockedInteractor(),
() -> mKosmos.getSceneInteractor(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index a867b0f..45d0102 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -102,7 +102,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@RunWithLooper(setAsMainLooper = true)
-class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var view: NotificationShadeWindowView
@Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
@@ -160,7 +160,7 @@
private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 347620a..83ad18b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -55,7 +55,7 @@
@RunWith(ParameterizedAndroidJunit4::class)
@SmallTest
@EnableFlags(FooterViewRefactor.FLAG_NAME)
-class FooterViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
@@ -79,7 +79,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt
new file mode 100644
index 0000000..5e50af3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.app.Flags.FLAG_COMPACT_HEADS_UP_NOTIFICATION
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class HeadsUpStyleProviderImplTest : SysuiTestCase() {
+
+ @Rule @JvmField val setFlagsRule = SetFlagsRule()
+
+ private lateinit var statusBarModeRepositoryStore: FakeStatusBarModeRepository
+ private lateinit var headsUpStyleProvider: HeadsUpStyleProviderImpl
+
+ @Before
+ fun setUp() {
+ statusBarModeRepositoryStore = FakeStatusBarModeRepository()
+ statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = true
+
+ headsUpStyleProvider = HeadsUpStyleProviderImpl(statusBarModeRepositoryStore)
+ }
+
+ @Test
+ @DisableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION)
+ fun shouldApplyCompactStyle_returnsFalse_whenCompactFlagDisabled() {
+ assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION)
+ fun shouldApplyCompactStyle_returnsTrue_whenImmersiveModeEnabled() {
+ // GIVEN
+ statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = true
+
+ // THEN
+ assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isTrue()
+ }
+
+ @Test
+ @EnableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION)
+ fun shouldApplyCompactStyle_returnsFalse_whenImmersiveModeDisabled() {
+ // GIVEN
+ statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = false
+
+ // THEN
+ assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
index 4f0f91a..926c35f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -134,7 +134,8 @@
/* isHeadsUpAnimation= */ eq(true),
/* onStartedRunnable= */ any(),
/* onFinishedRunnable= */ runnableCaptor.capture(),
- /* animationListener= */ any()
+ /* animationListener= */ any(),
+ /* clipSide= */ eq(ExpandableView.ClipSide.BOTTOM),
)
animatorTestRule.advanceTimeBy(disappearDuration) // move to the end of SSA animations
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 9b4f931..cb40f72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -66,7 +66,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@TestableLooper.RunWithLooper
-class KeyguardBypassControllerTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardBypassControllerTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val featureFlags = FakeFeatureFlags()
@@ -92,7 +92,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Captor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index b5525b1..36df61d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -295,6 +295,50 @@
}
@Test
+ fun subscriptions_subIsOnlyNtn_modelHasExclusivelyNtnTrue() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.subscriptions)
+
+ val onlyNtnSub =
+ mock<SubscriptionInfo>().also {
+ whenever(it.isOnlyNonTerrestrialNetwork).thenReturn(true)
+ whenever(it.subscriptionId).thenReturn(45)
+ whenever(it.groupUuid).thenReturn(GROUP_1)
+ whenever(it.carrierName).thenReturn("NTN only")
+ whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
+ }
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(onlyNtnSub))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isExclusivelyNonTerrestrial).isTrue()
+ }
+
+ @Test
+ fun subscriptions_subIsNotOnlyNtn_modelHasExclusivelyNtnFalse() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.subscriptions)
+
+ val notOnlyNtnSub =
+ mock<SubscriptionInfo>().also {
+ whenever(it.isOnlyNonTerrestrialNetwork).thenReturn(false)
+ whenever(it.subscriptionId).thenReturn(45)
+ whenever(it.groupUuid).thenReturn(GROUP_1)
+ whenever(it.carrierName).thenReturn("NTN only")
+ whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
+ }
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(notOnlyNtnSub))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isExclusivelyNonTerrestrial).isFalse()
+ }
+
+ @Test
fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() =
testScope.runTest {
val latest by collectLastValue(underTest.subscriptions)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 0b14be1..0f9cbfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -42,14 +42,11 @@
import com.google.common.truth.Truth.assertThat
import java.util.UUID
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.yield
-import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
@@ -68,7 +65,7 @@
set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
}
- private val testDispatcher = UnconfinedTestDispatcher()
+ private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
private val tableLogBuffer =
@@ -113,17 +110,12 @@
)
}
- @After fun tearDown() {}
-
@Test
fun filteredSubscriptions_default() =
testScope.runTest {
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf<SubscriptionModel>())
-
- job.cancel()
}
// Based on the logic from the old pipeline, we'll never filter subs when there are more than 2
@@ -133,12 +125,9 @@
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
-
- job.cancel()
}
@Test
@@ -146,12 +135,9 @@
testScope.runTest {
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
-
- job.cancel()
}
@Test
@@ -160,12 +146,9 @@
connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP))
-
- job.cancel()
}
@Test
@@ -180,12 +163,9 @@
connectionsRepository.setSubscriptions(listOf(sub1, sub2))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf(sub1, sub2))
-
- job.cancel()
}
@Test
@@ -202,13 +182,10 @@
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
// Filtered subscriptions should show the active one when the config is false
assertThat(latest).isEqualTo(listOf(sub3))
-
- job.cancel()
}
@Test
@@ -225,13 +202,10 @@
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
// Filtered subscriptions should show the active one when the config is false
assertThat(latest).isEqualTo(listOf(sub4))
-
- job.cancel()
}
@Test
@@ -248,14 +222,11 @@
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
// Filtered subscriptions should show the primary (non-opportunistic) if the config is
// true
assertThat(latest).isEqualTo(listOf(sub1))
-
- job.cancel()
}
@Test
@@ -272,14 +243,11 @@
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
// Filtered subscriptions should show the primary (non-opportunistic) if the config is
// true
assertThat(latest).isEqualTo(listOf(sub1))
-
- job.cancel()
}
@Test
@@ -297,12 +265,9 @@
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf(sub3))
-
- job.cancel()
}
@Test
@@ -320,12 +285,9 @@
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
- var latest: List<SubscriptionModel>? = null
- val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.filteredSubscriptions)
assertThat(latest).isEqualTo(listOf(sub1))
-
- job.cancel()
}
@Test
@@ -446,313 +408,345 @@
}
@Test
+ fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() =
+ testScope.runTest {
+ val notExclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = false,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ connectionsRepository.setSubscriptions(listOf(notExclusivelyNonTerrestrialSub))
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(notExclusivelyNonTerrestrialSub))
+ }
+
+ @Test
+ fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() =
+ testScope.runTest {
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ connectionsRepository.setSubscriptions(listOf(exclusivelyNonTerrestrialSub))
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ fun filteredSubscription_mixOfExclusivelyNonTerrestrialAndOther_hasOtherSubsOnly() =
+ testScope.runTest {
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ val otherSub1 =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = false,
+ subscriptionId = 1,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ val otherSub2 =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = false,
+ subscriptionId = 2,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ connectionsRepository.setSubscriptions(
+ listOf(otherSub1, exclusivelyNonTerrestrialSub, otherSub2)
+ )
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(otherSub1, otherSub2))
+ }
+
+ @Test
+ fun filteredSubscriptions_exclusivelyNonTerrestrialSub_andOpportunistic_bothFiltersHappen() =
+ testScope.runTest {
+ // Exclusively non-terrestrial sub
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ // Opportunistic subs
+ val (sub3, sub4) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+
+ // WHEN both an exclusively non-terrestrial sub and opportunistic sub pair is included
+ connectionsRepository.setSubscriptions(listOf(sub3, sub4, exclusivelyNonTerrestrialSub))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ // THEN both the only-non-terrestrial sub and the non-active sub are filtered out,
+ // leaving only sub3.
+ assertThat(latest).isEqualTo(listOf(sub3))
+ }
+
+ @Test
fun activeDataConnection_turnedOn() =
testScope.runTest {
CONNECTION_1.setDataEnabled(true)
- var latest: Boolean? = null
- val job =
- underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+ val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun activeDataConnection_turnedOff() =
testScope.runTest {
CONNECTION_1.setDataEnabled(true)
- var latest: Boolean? = null
- val job =
- underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
CONNECTION_1.setDataEnabled(false)
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun activeDataConnection_invalidSubId() =
testScope.runTest {
- var latest: Boolean? = null
- val job =
- underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID)
- yield()
// An invalid active subId should tell us that data is off
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun failedConnection_default_validated_notFailed() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun failedConnection_notDefault_notValidated_notFailed() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = false
connectionsRepository.defaultConnectionIsValidated.value = false
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun failedConnection_default_notValidated_failed() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = false
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun failedConnection_carrierMergedDefault_notValidated_failed() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.hasCarrierMergedConnection.value = true
connectionsRepository.defaultConnectionIsValidated.value = false
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
/** Regression test for b/275076959. */
@Test
fun failedConnection_dataSwitchInSameGroup_notFailed() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
// WHEN there's a data change in the same subscription group
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
// THEN the default connection is *not* marked as failed because of forced validation
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun failedConnection_dataSwitchNotInSameGroup_isFailed() =
testScope.runTest {
- var latestConnectionFailed: Boolean? = null
- val job =
- underTest.isDefaultConnectionFailed
- .onEach { latestConnectionFailed = it }
- .launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
// WHEN the connection is invalidated without a activeSubChangedInGroupEvent
connectionsRepository.defaultConnectionIsValidated.value = false
// THEN the connection is immediately marked as failed
- assertThat(latestConnectionFailed).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun alwaysShowDataRatIcon_configHasTrue() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
val config = MobileMappings.Config()
config.alwaysShowDataRatIcon = true
connectionsRepository.defaultDataSubRatConfig.value = config
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun alwaysShowDataRatIcon_configHasFalse() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
val config = MobileMappings.Config()
config.alwaysShowDataRatIcon = false
connectionsRepository.defaultDataSubRatConfig.value = config
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun alwaysUseCdmaLevel_configHasTrue() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
val config = MobileMappings.Config()
config.alwaysShowCdmaRssi = true
connectionsRepository.defaultDataSubRatConfig.value = config
- yield()
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun alwaysUseCdmaLevel_configHasFalse() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
val config = MobileMappings.Config()
config.alwaysShowCdmaRssi = false
connectionsRepository.defaultDataSubRatConfig.value = config
- yield()
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun isSingleCarrier_zeroSubscriptions_false() =
testScope.runTest {
- var latest: Boolean? = true
- val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isSingleCarrier)
connectionsRepository.setSubscriptions(emptyList())
- assertThat(latest).isFalse()
- job.cancel()
+ assertThat(latest).isFalse()
}
@Test
fun isSingleCarrier_oneSubscription_true() =
testScope.runTest {
- var latest: Boolean? = false
- val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isSingleCarrier)
connectionsRepository.setSubscriptions(listOf(SUB_1))
- assertThat(latest).isTrue()
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun isSingleCarrier_twoSubscriptions_false() =
testScope.runTest {
- var latest: Boolean? = true
- val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isSingleCarrier)
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
- assertThat(latest).isFalse()
- job.cancel()
+ assertThat(latest).isFalse()
}
@Test
fun isSingleCarrier_updates() =
testScope.runTest {
- var latest: Boolean? = false
- val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isSingleCarrier)
connectionsRepository.setSubscriptions(listOf(SUB_1))
assertThat(latest).isTrue()
connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.mobileIsDefault)
connectionsRepository.mobileIsDefault.value = false
connectionsRepository.hasCarrierMergedConnection.value = false
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.mobileIsDefault)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.hasCarrierMergedConnection.value = false
assertThat(latest).isTrue()
-
- job.cancel()
}
/** Regression test for b/272586234. */
@Test
fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.mobileIsDefault)
connectionsRepository.mobileIsDefault.value = false
connectionsRepository.hasCarrierMergedConnection.value = true
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun mobileIsDefault_updatesWhenRepoUpdates() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.mobileIsDefault)
connectionsRepository.mobileIsDefault.value = true
assertThat(latest).isTrue()
@@ -762,8 +756,6 @@
connectionsRepository.hasCarrierMergedConnection.value = true
assertThat(latest).isTrue()
-
- job.cancel()
}
// The data switch tests are mostly testing the [forcingCellularValidation] flow, but that flow
@@ -772,95 +764,79 @@
@Test
fun dataSwitch_inSameGroup_validatedMatchesPreviousValue_expiresAfter2s() =
testScope.runTest {
- var latestConnectionFailed: Boolean? = null
- val job =
- underTest.isDefaultConnectionFailed
- .onEach { latestConnectionFailed = it }
- .launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
// Trigger a data change in the same subscription group that's not yet validated
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
// After 1s, the force validation bit is still present, so the connection is not marked
// as failed
advanceTimeBy(1000)
- assertThat(latestConnectionFailed).isFalse()
+ assertThat(latest).isFalse()
// After 2s, the force validation expires so the connection updates to failed
advanceTimeBy(1001)
- assertThat(latestConnectionFailed).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() =
testScope.runTest {
- var latestConnectionFailed: Boolean? = null
- val job =
- underTest.isDefaultConnectionFailed
- .onEach { latestConnectionFailed = it }
- .launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
- assertThat(latestConnectionFailed).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() =
testScope.runTest {
- var latestConnectionFailed: Boolean? = null
- val job =
- underTest.isDefaultConnectionFailed
- .onEach { latestConnectionFailed = it }
- .launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
// GIVEN the network starts validated
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
// WHEN a data change happens in the same group
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
// WHEN the validation bit is lost
connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
// WHEN another data change happens in the same group
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
// THEN the forced validation bit is still used...
- assertThat(latestConnectionFailed).isFalse()
+ assertThat(latest).isFalse()
advanceTimeBy(1000)
- assertThat(latestConnectionFailed).isFalse()
+ assertThat(latest).isFalse()
// ... but expires after 2s
advanceTimeBy(1001)
- assertThat(latestConnectionFailed).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun dataSwitch_whileAlreadyForcingValidation_resetsClock() =
testScope.runTest {
- var latestConnectionFailed: Boolean? = null
- val job =
- underTest.isDefaultConnectionFailed
- .onEach { latestConnectionFailed = it }
- .launchIn(this)
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
connectionsRepository.mobileIsDefault.value = true
connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
@@ -869,44 +845,37 @@
// WHEN another change in same group event happens
connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
// THEN the forced validation remains for exactly 2 more seconds from now
// 1.500s from second event
advanceTimeBy(1500)
- assertThat(latestConnectionFailed).isFalse()
+ assertThat(latest).isFalse()
// 2.001s from the second event
advanceTimeBy(501)
- assertThat(latestConnectionFailed).isTrue()
-
- job.cancel()
+ assertThat(latest).isTrue()
}
@Test
fun isForceHidden_repoHasMobileHidden_true() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isForceHidden)
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
assertThat(latest).isTrue()
-
- job.cancel()
}
@Test
fun isForceHidden_repoDoesNotHaveMobileHidden_false() =
testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.isForceHidden)
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
assertThat(latest).isFalse()
-
- job.cancel()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index cfa734a1..ab10bc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -47,7 +47,7 @@
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardStatusBarViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() {
+class KeyguardStatusBarViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
@@ -66,7 +66,7 @@
}
init {
- mSetFlagsRule.setFlagsParameterization(flags!!)
+ mSetFlagsRule.setFlagsParameterization(flags)
}
@Before
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
index 4085b1b..923b636 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt
@@ -25,8 +25,9 @@
@SysUISingleton
class FakeAccessibilityRepository(
override val isTouchExplorationEnabled: MutableStateFlow<Boolean>,
+ override val isEnabled: MutableStateFlow<Boolean>,
) : AccessibilityRepository {
- @Inject constructor() : this(MutableStateFlow(false))
+ @Inject constructor() : this(MutableStateFlow(false), MutableStateFlow(false))
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
index 66c9afb..b23767e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
@@ -17,5 +17,13 @@
package com.android.systemui.animation
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
-val Kosmos.activityTransitionAnimator by Kosmos.Fixture { ActivityTransitionAnimator() }
+val Kosmos.activityTransitionAnimator by
+ Kosmos.Fixture {
+ ActivityTransitionAnimator(
+ // The main thread is checked in a bunch of places inside the different transitions
+ // animators, so we have to pass the real main executor here.
+ mainExecutor = testCase.context.mainExecutor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
index 77cb167..5a092f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
@@ -19,7 +19,13 @@
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testCase
val Kosmos.dialogTransitionAnimator by Fixture {
- fakeDialogTransitionAnimator(interactionJankMonitor = interactionJankMonitor)
+ fakeDialogTransitionAnimator(
+ // The main thread is checked in a bunch of places inside the different transitions
+ // animators, so we have to pass the real main executor here.
+ mainExecutor = testCase.context.mainExecutor,
+ interactionJankMonitor = interactionJankMonitor,
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
index 48b72d0..1709329 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
@@ -15,17 +15,20 @@
package com.android.systemui.animation
import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
/** A [DialogTransitionAnimator] to be used in tests. */
@JvmOverloads
fun fakeDialogTransitionAnimator(
+ @Main mainExecutor: Executor,
isUnlocked: Boolean = true,
isShowingAlternateAuthOnUnlock: Boolean = false,
isPredictiveBackQsDialogAnim: Boolean = false,
interactionJankMonitor: InteractionJankMonitor,
): DialogTransitionAnimator {
return DialogTransitionAnimator(
+ mainExecutor = mainExecutor,
callback =
FakeCallback(
isUnlocked = isUnlocked,
@@ -36,7 +39,7 @@
object : AnimationFeatureFlags {
override val isPredictiveBackQsDialogAnim = isPredictiveBackQsDialogAnim
},
- transitionAnimator = fakeTransitionAnimator(),
+ transitionAnimator = fakeTransitionAnimator(mainExecutor),
isForTesting = true,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
index bc7ec3f..d07875f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
@@ -15,10 +15,12 @@
package com.android.systemui.animation
import com.android.app.animation.Interpolators
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
/** A [TransitionAnimator] to be used in tests. */
-fun fakeTransitionAnimator(): TransitionAnimator {
- return TransitionAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
+fun fakeTransitionAnimator(@Main mainExecutor: Executor): TransitionAnimator {
+ return TransitionAnimator(mainExecutor, TEST_TIMINGS, TEST_INTERPOLATORS)
}
/**
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 58b0ff8..67fa857 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
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntrySourceInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
@@ -49,6 +50,7 @@
keyguardViewController = { statusBarKeyguardViewManager },
deviceEntryInteractor = deviceEntryInteractor,
deviceEntrySourceInteractor = deviceEntrySourceInteractor,
+ accessibilityInteractor = accessibilityInteractor,
scope = testScope.backgroundScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 3762497..ec56327 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -33,6 +34,7 @@
uiEventLogger,
{ interactionJankMonitor },
mock(),
+ { keyguardTransitionInteractor },
{ shadeInteractor },
{ deviceUnlockedInteractor },
{ sceneInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt
new file mode 100644
index 0000000..5c21ab6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.night
+
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsNightDisplayTileConfig by
+ Kosmos.Fixture { QSAccessibilityModule.provideNightDisplayTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
index 38ede44..ea02d0c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
@@ -76,6 +76,16 @@
delegate.assertFlagValid()
delegate.programmaticCollapseShade()
}
+
+ fun setQsFullscreen(qsFullscreen: Boolean) {
+ delegate.assertFlagValid()
+ delegate.setQsFullscreen(qsFullscreen)
+ }
+
+ fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean) {
+ delegate.assertFlagValid()
+ delegate.setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer)
+ }
}
/** Sets up shade state for tests for a specific value of the scene container flag. */
@@ -103,6 +113,10 @@
/** Sets the shade to half collapsed with no touch input. */
fun programmaticCollapseShade()
+
+ fun setQsFullscreen(qsFullscreen: Boolean)
+
+ fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean)
}
/** Sets up shade state for tests when the scene container flag is disabled. */
@@ -146,6 +160,14 @@
shadeRepository.setLegacyShadeExpansion(.5f)
testScope.runCurrent()
}
+
+ override fun setQsFullscreen(qsFullscreen: Boolean) {
+ shadeRepository.legacyQsFullscreen.value = true
+ }
+
+ override fun setLegacyExpandedOrAwaitingInputTransfer(expanded: Boolean) {
+ shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(expanded)
+ }
}
/** Sets up shade state for tests when the scene container flag is enabled. */
@@ -183,6 +205,16 @@
setTransitionProgress(Scenes.Shade, Scenes.Lockscreen, .5f, false)
}
+ override fun setQsFullscreen(qsFullscreen: Boolean) {
+ setQsExpansion(1f)
+ }
+
+ override fun setLegacyExpandedOrAwaitingInputTransfer(
+ legacyExpandedOrAwaitingInputTransfer: Boolean
+ ) {
+ setShadeExpansion(.1f)
+ }
+
override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) {
if (lockscreenShadeExpansion == 0f) {
setIdleScene(Scenes.Lockscreen)
diff --git a/ravenwood/scripts/convert-androidtest.py b/ravenwood/scripts/convert-androidtest.py
new file mode 100755
index 0000000..61ec54b
--- /dev/null
+++ b/ravenwood/scripts/convert-androidtest.py
@@ -0,0 +1,184 @@
+#!/usr/bin/python3
+# 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.
+
+# This script converts a legacy test class (using AndroidTestCase, TestCase or
+# InstrumentationTestCase to a modern style test class, in a best-effort manner.
+#
+# Usage:
+# convert-androidtest.py TARGET-FILE [TARGET-FILE ...]
+#
+# Caveats:
+# - It adds all the extra imports, even if they're not needed.
+# - It won't sort imports.
+# - It also always adds getContext() and getTestContext().
+#
+
+import sys
+import fileinput
+import re
+import subprocess
+
+# Print message on console
+def log(msg):
+ print(msg, file=sys.stderr)
+
+
+# Matches `extends AndroidTestCase` (or another similar base class)
+re_extends = re.compile(
+ r''' \b extends \s+ (AndroidTestCase|TestCase|InstrumentationTestCase) \s* ''',
+ re.S + re.X)
+
+
+# Look into given files and return the files that have `re_extends`.
+def find_target_files(files):
+ ret = []
+
+ for file in files:
+ try:
+ with open(file, 'r') as f:
+ data = f.read()
+
+ if re_extends.search(data):
+ ret.append(file)
+
+ except FileNotFoundError as e:
+ log(f'Failed to open file {file}: {e}')
+
+ return ret
+
+
+def main(args):
+ files = args
+
+ # Find the files that should be processed.
+ files = find_target_files(files)
+
+ if len(files) == 0:
+ log("No target files found.")
+ return 0
+
+ # Process the files.
+ with fileinput.input(files=(files), inplace = True, backup = '.bak') as f:
+ import_seen = False
+ carry_over = ''
+ class_body_started = False
+ class_seen = False
+
+ def on_file_start():
+ nonlocal import_seen, carry_over, class_body_started, class_seen
+ import_seen = False
+ carry_over = ''
+ class_body_started = False
+ class_seen = False
+
+ for line in f:
+ if (fileinput.filelineno() == 1):
+ log(f"Processing: {fileinput.filename()}")
+ on_file_start()
+
+ line = line.rstrip('\n')
+
+ # Carry over a certain line to the next line.
+ if re.search(r'''@Override\b''', line):
+ carry_over = carry_over + line + '\n'
+ continue
+
+ if carry_over:
+ line = carry_over + line
+ carry_over = ''
+
+
+ # Remove the base class from the class definition.
+ line = re_extends.sub('', line)
+
+ # Add a @RunWith.
+ if not class_seen and re.search(r'''\b class \b''', line, re.X):
+ class_seen = True
+ print("@RunWith(AndroidJUnit4.class)")
+
+
+ # Inject extra imports.
+ if not import_seen and re.search(r'''^import\b''', line):
+ import_seen = True
+ print("""\
+import android.content.Context;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertSame;
+import static junit.framework.TestCase.assertNotSame;
+import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.fail;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+""")
+
+ # Add @Test to the test methods.
+ if re.search(r'''^ \s* public \s* void \s* test''', line, re.X):
+ print(" @Test")
+
+ # Convert setUp/tearDown to @Before/@After.
+ if re.search(r''' ^\s+ ( \@Override \s+ ) ? (public|protected) \s+ void \s+ (setUp|tearDown) ''',
+ line, re.X):
+ if re.search('setUp', line):
+ print(' @Before')
+ else:
+ print(' @After')
+
+ line = re.sub(r''' \s* \@Override \s* \n ''', '', line, 0, re.X)
+ line = re.sub(r'''protected''', 'public', line, 0, re.X)
+
+ # Remove the super setUp / tearDown call.
+ if re.search(r''' \b super \. (setUp|tearDown) \b ''', line, re.X):
+ continue
+
+ # Convert mContext to getContext().
+ line = re.sub(r'''\b mContext \b ''', 'getContext()', line, 0, re.X)
+
+ # Print the processed line.
+ print(line)
+
+ # Add getContext() / getTestContext() at the beginning of the class.
+ if not class_body_started and re.search(r'''\{''', line):
+ class_body_started = True
+ print("""\
+ private Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getTargetContext();
+ }
+
+ private Context getTestContext() {
+ return InstrumentationRegistry.getInstrumentation().getContext();
+ }
+""")
+
+
+ # Run diff
+ for file in files:
+ subprocess.call(["diff", "-u", "--color=auto", f"{file}.bak", file])
+
+ log(f'{len(files)} file(s) converted.')
+
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index e830523..249b3cb 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -83,6 +83,7 @@
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
@@ -172,6 +173,7 @@
private static final String TAG = "AppWidgetServiceImpl";
private static final boolean DEBUG = false;
+ private static final boolean DEBUG_NULL_PROVIDER_INFO = Build.IS_DEBUGGABLE;
private static final String OLD_KEYGUARD_HOST_PACKAGE = "android";
private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
@@ -736,7 +738,10 @@
}
RemoteViews views = new RemoteViews(mContext.getPackageName(),
R.layout.work_widget_mask_view);
- ApplicationInfo appInfo = provider.info.providerInfo.applicationInfo;
+ final ActivityInfo activityInfo = provider.info.providerInfo;
+ final ApplicationInfo appInfo = activityInfo != null ? activityInfo.applicationInfo : null;
+ final String packageName = appInfo != null
+ ? appInfo.packageName : provider.id.componentName.getPackageName();
final int appUserId = provider.getUserId();
boolean showBadge = false;
@@ -750,7 +755,7 @@
} else if (provider.maskedBySuspendedPackage) {
showBadge = mUserManager.hasBadge(appUserId);
final UserPackage suspendingPackage = mPackageManagerInternal.getSuspendingPackage(
- appInfo.packageName, appUserId);
+ packageName, appUserId);
// TODO(b/281839596): don't rely on platform always meaning suspended by admin.
if (suspendingPackage != null
&& PLATFORM_PACKAGE_NAME.equals(suspendingPackage.packageName)) {
@@ -759,11 +764,11 @@
} else {
final SuspendDialogInfo dialogInfo =
mPackageManagerInternal.getSuspendedDialogInfo(
- appInfo.packageName, suspendingPackage, appUserId);
+ packageName, suspendingPackage, appUserId);
// onUnsuspend is null because we don't want to start any activity on
// unsuspending from a suspended widget.
onClickIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(
- appInfo.packageName, suspendingPackage, dialogInfo, null, null,
+ packageName, suspendingPackage, dialogInfo, null, null,
appUserId);
}
} else if (provider.maskedByLockedProfile) {
@@ -778,7 +783,7 @@
showBadge = mUserManager.hasBadge(appUserId);
}
- Icon icon = appInfo.icon != 0
+ Icon icon = (appInfo != null && appInfo.icon != 0)
? Icon.createWithResource(appInfo.packageName, appInfo.icon)
: Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
views.setImageViewIcon(R.id.work_widget_app_icon, icon);
@@ -2955,6 +2960,9 @@
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
info.provider = providerId.componentName;
info.providerInfo = ri.activityInfo;
+ if (DEBUG_NULL_PROVIDER_INFO) {
+ Objects.requireNonNull(ri.activityInfo);
+ }
return info;
}
return null;
@@ -2989,6 +2997,9 @@
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
info.provider = providerId.componentName;
info.providerInfo = activityInfo;
+ if (DEBUG_NULL_PROVIDER_INFO) {
+ Objects.requireNonNull(activityInfo);
+ }
final Resources resources;
final long identity = Binder.clearCallingIdentity();
@@ -3564,6 +3575,9 @@
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
info.provider = providerId.componentName;
info.providerInfo = providerInfo;
+ if (DEBUG_NULL_PROVIDER_INFO) {
+ Objects.requireNonNull(providerInfo);
+ }
provider = new Provider();
provider.setPartialInfoLocked(info);
@@ -3580,6 +3594,9 @@
if (info != null) {
info.provider = providerId.componentName;
info.providerInfo = providerInfo;
+ if (DEBUG_NULL_PROVIDER_INFO) {
+ Objects.requireNonNull(providerInfo);
+ }
provider.setInfoLocked(info);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 9c84b12..c4b461f 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -564,6 +564,18 @@
});
}
+ /** Sets focused_autofill_id using view id */
+ public void maybeSetFocusedId(AutofillId id) {
+ maybeSetFocusedId(id.getViewId());
+ }
+
+ /** Sets focused_autofill_id as long as mEventInternal is present */
+ public void maybeSetFocusedId(int id) {
+ mEventInternal.ifPresent(event -> {
+ event.mFocusedId = id;
+ });
+ }
+
public void logAndEndEvent() {
if (!mEventInternal.isPresent()) {
Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
@@ -608,7 +620,8 @@
+ " mIsCredentialRequest=" + event.mIsCredentialRequest
+ " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential
+ " mViewFillableTotalCount=" + event.mViewFillableTotalCount
- + " mViewFillFailureCount=" + event.mViewFillFailureCount);
+ + " mViewFillFailureCount=" + event.mViewFillFailureCount
+ + " mFocusedId=" + event.mFocusedId);
}
// TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -651,7 +664,8 @@
event.mIsCredentialRequest,
event.mWebviewRequestedCredential,
event.mViewFillableTotalCount,
- event.mViewFillFailureCount);
+ event.mViewFillFailureCount,
+ event.mFocusedId);
mEventInternal = Optional.empty();
}
@@ -689,6 +703,7 @@
boolean mWebviewRequestedCredential = false;
int mViewFillableTotalCount = -1;
int mViewFillFailureCount = -1;
+ int mFocusedId = -1;
PresentationStatsEventInternal() {}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 8b13c4b7..03cf74f 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -4669,6 +4669,7 @@
mFieldClassificationIdSnapshot);
mPresentationStatsEventLogger.maybeSetAvailableCount(
response.getDatasets(), mCurrentViewId);
+ mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId);
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 748253f..1a8c3b0 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -35,7 +35,6 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
-import android.os.Bundle;
import android.os.BundleMerger;
import android.os.Debug;
import android.os.DropBoxManager;
@@ -176,6 +175,16 @@
}
};
+ private static final BundleMerger sDropboxEntryAddedExtrasMerger;
+ static {
+ sDropboxEntryAddedExtrasMerger = new BundleMerger();
+ sDropboxEntryAddedExtrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST);
+ sDropboxEntryAddedExtrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME,
+ BundleMerger.STRATEGY_COMPARABLE_MAX);
+ sDropboxEntryAddedExtrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT,
+ BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD);
+ }
+
private final IDropBoxManagerService.Stub mStub = new IDropBoxManagerService.Stub() {
@Override
public void addData(String tag, byte[] data, int flags) {
@@ -284,7 +293,7 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SEND_BROADCAST:
- prepareAndSendBroadcast((Intent) msg.obj, null);
+ prepareAndSendBroadcast((Intent) msg.obj, false);
break;
case MSG_SEND_DEFERRED_BROADCAST:
Intent deferredIntent;
@@ -292,31 +301,42 @@
deferredIntent = mDeferredMap.remove((String) msg.obj);
}
if (deferredIntent != null) {
- prepareAndSendBroadcast(deferredIntent,
- createBroadcastOptions(deferredIntent));
+ prepareAndSendBroadcast(deferredIntent, true);
}
break;
}
}
- private void prepareAndSendBroadcast(Intent intent, Bundle options) {
+ private void prepareAndSendBroadcast(Intent intent, boolean deferrable) {
if (!DropBoxManagerService.this.mBooted) {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
if (Flags.enableReadDropboxPermission()) {
- BroadcastOptions unbundledOptions = (options == null)
- ? BroadcastOptions.makeBasic() : BroadcastOptions.fromBundle(options);
-
- unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true);
+ options.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true);
+ if (deferrable) {
+ final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG)
+ + "-READ_DROPBOX_DATA";
+ setBroadcastOptionsForDeferral(options, matchingKey);
+ }
getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
- Manifest.permission.READ_DROPBOX_DATA, unbundledOptions.toBundle());
+ Manifest.permission.READ_DROPBOX_DATA, options.toBundle());
- unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false);
+ options.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false);
+ if (deferrable) {
+ final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG)
+ + "-READ_LOGS";
+ setBroadcastOptionsForDeferral(options, matchingKey);
+ }
getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
- Manifest.permission.READ_LOGS, unbundledOptions.toBundle());
+ Manifest.permission.READ_LOGS, options.toBundle());
} else {
+ if (deferrable) {
+ final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG);
+ setBroadcastOptionsForDeferral(options, matchingKey);
+ }
getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
- android.Manifest.permission.READ_LOGS, options);
+ android.Manifest.permission.READ_LOGS, options.toBundle());
}
}
@@ -328,21 +348,12 @@
return dropboxIntent;
}
- private Bundle createBroadcastOptions(Intent intent) {
- final BundleMerger extrasMerger = new BundleMerger();
- extrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST);
- extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME,
- BundleMerger.STRATEGY_COMPARABLE_MAX);
- extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT,
- BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD);
-
- return BroadcastOptions.makeBasic()
- .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED)
+ private void setBroadcastOptionsForDeferral(BroadcastOptions options, String matchingKey) {
+ options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED)
.setDeliveryGroupMatchingKey(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED,
- intent.getStringExtra(DropBoxManager.EXTRA_TAG))
- .setDeliveryGroupExtrasMerger(extrasMerger)
- .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
- .toBundle();
+ matchingKey)
+ .setDeliveryGroupExtrasMerger(sDropboxEntryAddedExtrasMerger)
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
}
/**
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d776700..bef5c612 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1168,9 +1168,7 @@
}
private boolean shouldAllowBootCompletedStart(ServiceRecord r, int foregroundServiceType) {
- @PowerExemptionManager.ReasonCode final int fgsStartReasonCode =
- r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mReasonCode
- : REASON_DENIED;
+ @PowerExemptionManager.ReasonCode final int fgsStartReasonCode = r.getFgsAllowStart();
if (Flags.fgsBootCompleted()
&& CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, r.appInfo.uid)
&& fgsStartReasonCode == PowerExemptionManager.REASON_BOOT_COMPLETED) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index cc6ae5c..8647750 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3277,7 +3277,13 @@
}
final int curSchedGroup = state.getCurrentSchedulingGroup();
- if (state.getSetSchedGroup() != curSchedGroup) {
+ if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0
+ && ActivityManager.isProcStateBackground(state.getCurProcState())
+ && !state.hasStartedServices()) {
+ app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED,
+ ApplicationExitInfo.SUBREASON_REMOVE_TASK, true);
+ success = false;
+ } else if (state.getSetSchedGroup() != curSchedGroup) {
int oldSchedGroup = state.getSetSchedGroup();
state.setSetSchedGroup(curSchedGroup);
if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
@@ -3285,74 +3291,66 @@
+ " to " + curSchedGroup + ": " + state.getAdjType();
reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
}
- if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0
- && ActivityManager.isProcStateBackground(state.getSetProcState())
- && !state.hasStartedServices()) {
- app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED,
- ApplicationExitInfo.SUBREASON_REMOVE_TASK, true);
- success = false;
- } else {
- int processGroup;
- switch (curSchedGroup) {
- case SCHED_GROUP_BACKGROUND:
- processGroup = THREAD_GROUP_BACKGROUND;
- break;
- case SCHED_GROUP_TOP_APP:
- case SCHED_GROUP_TOP_APP_BOUND:
- processGroup = THREAD_GROUP_TOP_APP;
- break;
- case SCHED_GROUP_RESTRICTED:
- processGroup = THREAD_GROUP_RESTRICTED;
- break;
- default:
- processGroup = THREAD_GROUP_DEFAULT;
- break;
- }
- mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
- 0 /* unused */, app.getPid(), processGroup, app.processName));
- try {
- final int renderThreadTid = app.getRenderThreadTid();
- if (curSchedGroup == SCHED_GROUP_TOP_APP) {
- // do nothing if we already switched to RT
- if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
- app.getWindowProcessController().onTopProcChanged();
- if (app.useFifoUiScheduling()) {
- // Switch UI pipeline for app to SCHED_FIFO
- state.setSavedPriority(Process.getThreadPriority(app.getPid()));
- ActivityManagerService.setFifoPriority(app, true /* enable */);
- } else {
- // Boost priority for top app UI and render threads
- setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
- if (renderThreadTid != 0) {
- try {
- setThreadPriority(renderThreadTid,
- THREAD_PRIORITY_TOP_APP_BOOST);
- } catch (IllegalArgumentException e) {
- // thread died, ignore
- }
+ int processGroup;
+ switch (curSchedGroup) {
+ case SCHED_GROUP_BACKGROUND:
+ processGroup = THREAD_GROUP_BACKGROUND;
+ break;
+ case SCHED_GROUP_TOP_APP:
+ case SCHED_GROUP_TOP_APP_BOUND:
+ processGroup = THREAD_GROUP_TOP_APP;
+ break;
+ case SCHED_GROUP_RESTRICTED:
+ processGroup = THREAD_GROUP_RESTRICTED;
+ break;
+ default:
+ processGroup = THREAD_GROUP_DEFAULT;
+ break;
+ }
+ mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
+ 0 /* unused */, app.getPid(), processGroup, app.processName));
+ try {
+ final int renderThreadTid = app.getRenderThreadTid();
+ if (curSchedGroup == SCHED_GROUP_TOP_APP) {
+ // do nothing if we already switched to RT
+ if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
+ app.getWindowProcessController().onTopProcChanged();
+ if (app.useFifoUiScheduling()) {
+ // Switch UI pipeline for app to SCHED_FIFO
+ state.setSavedPriority(Process.getThreadPriority(app.getPid()));
+ ActivityManagerService.setFifoPriority(app, true /* enable */);
+ } else {
+ // Boost priority for top app UI and render threads
+ setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
+ if (renderThreadTid != 0) {
+ try {
+ setThreadPriority(renderThreadTid,
+ THREAD_PRIORITY_TOP_APP_BOOST);
+ } catch (IllegalArgumentException e) {
+ // thread died, ignore
}
}
}
- } else if (oldSchedGroup == SCHED_GROUP_TOP_APP
- && curSchedGroup != SCHED_GROUP_TOP_APP) {
- app.getWindowProcessController().onTopProcChanged();
- if (app.useFifoUiScheduling()) {
- // Reset UI pipeline to SCHED_OTHER
- ActivityManagerService.setFifoPriority(app, false /* enable */);
- setThreadPriority(app.getPid(), state.getSavedPriority());
- } else {
- // Reset priority for top app UI and render threads
- setThreadPriority(app.getPid(), 0);
- }
+ }
+ } else if (oldSchedGroup == SCHED_GROUP_TOP_APP
+ && curSchedGroup != SCHED_GROUP_TOP_APP) {
+ app.getWindowProcessController().onTopProcChanged();
+ if (app.useFifoUiScheduling()) {
+ // Reset UI pipeline to SCHED_OTHER
+ ActivityManagerService.setFifoPriority(app, false /* enable */);
+ setThreadPriority(app.getPid(), state.getSavedPriority());
+ } else {
+ // Reset priority for top app UI and render threads
+ setThreadPriority(app.getPid(), 0);
+ }
- if (renderThreadTid != 0) {
- setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY);
- }
+ if (renderThreadTid != 0) {
+ setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY);
}
- } catch (Exception e) {
- if (DEBUG_ALL) {
- Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e);
- }
+ }
+ } catch (Exception e) {
+ if (DEBUG_ALL) {
+ Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e);
}
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 85eb044..1db3483 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2912,10 +2912,12 @@
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
- final int proxiedUid = attributionSource.getNextUid();
final int proxyVirtualDeviceId = attributionSource.getDeviceId();
+
+ final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+ final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
@@ -2952,7 +2954,8 @@
final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
- Process.INVALID_UID, null, null, proxyFlags, !isProxyTrusted,
+ Process.INVALID_UID, null, null,
+ Context.DEVICE_ID_DEFAULT, proxyFlags, !isProxyTrusted,
"proxy " + message, shouldCollectMessage);
if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
@@ -2970,9 +2973,9 @@
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
- proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolveProxyPackageName,
- proxyAttributionTag, proxiedFlags, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage);
+ proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolveProxyPackageName,
+ proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, shouldCollectAsyncNotedOp,
+ message, shouldCollectMessage);
}
@Override
@@ -3023,14 +3026,14 @@
}
return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
virtualDeviceId, Process.INVALID_UID, null, null,
- AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage);
+ Context.DEVICE_ID_DEFAULT, AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp,
+ message, shouldCollectMessage);
}
private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag, int virtualDeviceId, int proxyUid,
- String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
- boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ String proxyPackageName, @Nullable String proxyAttributionTag, int proxyVirtualDeviceId,
+ @OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message,
boolean shouldCollectMessage) {
PackageVerificationResult pvr;
try {
@@ -3161,8 +3164,9 @@
}
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED);
+
attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
- uidState.getState(), flags);
+ getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags);
if (shouldCollectAsyncNotedOp) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
@@ -3528,9 +3532,9 @@
}
return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
- virtualDeviceId, Process.INVALID_UID, null, null, OP_FLAG_SELF,
- startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- attributionFlags, attributionChainId);
+ virtualDeviceId, Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT,
+ OP_FLAG_SELF, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, attributionFlags, attributionChainId);
}
/** @deprecated Use {@link #startProxyOperationWithState} instead. */
@@ -3568,18 +3572,32 @@
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
- final int proxiedUid = attributionSource.getNextUid();
final int proxyVirtualDeviceId = attributionSource.getDeviceId();
+
+ final int proxiedUid = attributionSource.getNextUid();
final String proxiedPackageName = attributionSource.getNextPackageName();
final String proxiedAttributionTag = attributionSource.getNextAttributionTag();
+ final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId();
verifyIncomingProxyUid(attributionSource);
verifyIncomingOp(code);
if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) {
- Slog.w(TAG, "startProxyOperationImpl returned MODE_IGNORED as virtualDeviceId "
- + proxyVirtualDeviceId + " is invalid");
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag,
- proxiedPackageName);
+ Slog.w(
+ TAG,
+ "startProxyOperationImpl returned MODE_IGNORED as proxyVirtualDeviceId "
+ + proxyVirtualDeviceId
+ + " is invalid");
+ return new SyncNotedAppOp(
+ AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName);
+ }
+ if (!isValidVirtualDeviceId(proxiedVirtualDeviceId)) {
+ Slog.w(
+ TAG,
+ "startProxyOperationImpl returned MODE_IGNORED as proxiedVirtualDeviceId "
+ + proxiedVirtualDeviceId
+ + " is invalid");
+ return new SyncNotedAppOp(
+ AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName);
}
if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid))
|| !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) {
@@ -3621,7 +3639,7 @@
// Test if the proxied operation will succeed before starting the proxy operation
final SyncNotedAppOp testProxiedOp = startOperationDryRun(code,
proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag,
- proxyVirtualDeviceId, resolvedProxyPackageName, proxiedFlags,
+ proxiedVirtualDeviceId, resolvedProxyPackageName, proxiedFlags,
startIfModeDefault);
if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
@@ -3633,7 +3651,7 @@
final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
- Process.INVALID_UID, null, null, proxyFlags,
+ Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT, proxyFlags,
startIfModeDefault, !isProxyTrusted, "proxy " + message,
shouldCollectMessage, proxyAttributionFlags, attributionChainId);
if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
@@ -3642,9 +3660,10 @@
}
return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
- proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolvedProxyPackageName,
- proxyAttributionTag, proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp,
- message, shouldCollectMessage, proxiedAttributionFlags, attributionChainId);
+ proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolvedProxyPackageName,
+ proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, proxiedAttributionFlags,
+ attributionChainId);
}
private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
@@ -3654,9 +3673,10 @@
private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
@NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId,
int proxyUid, String proxyPackageName, @Nullable String proxyAttributionTag,
- @OpFlags int flags, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
- @Nullable String message, boolean shouldCollectMessage,
- @AttributionFlags int attributionFlags, int attributionChainId) {
+ int proxyVirtualDeviceId, @OpFlags int flags, boolean startIfModeDefault,
+ boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+ int attributionChainId) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3751,13 +3771,13 @@
+ " flags: " + AppOpsManager.flagsToString(flags));
try {
if (isRestricted) {
- attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
- attributionFlags, attributionChainId);
+ attributedOp.createPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName,
+ proxyAttributionTag, getPersistentId(proxyVirtualDeviceId),
+ uidState.getState(), flags, attributionFlags, attributionChainId);
} else {
- attributedOp.started(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, virtualDeviceId, uidState.getState(), flags,
- attributionFlags, attributionChainId);
+ attributedOp.started(clientId, virtualDeviceId, proxyUid, proxyPackageName,
+ proxyAttributionTag, getPersistentId(proxyVirtualDeviceId),
+ uidState.getState(), flags, attributionFlags, attributionChainId);
startType = START_TYPE_STARTED;
}
} catch (RemoteException e) {
@@ -4946,7 +4966,7 @@
if (accessTime > 0) {
attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
- proxyAttributionTag, uidState, opFlags);
+ proxyAttributionTag, PERSISTENT_DEVICE_ID_DEFAULT, uidState, opFlags);
}
if (rejectTime > 0) {
attributedOp.rejected(rejectTime, uidState, opFlags);
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 2285826..2760ccf 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -24,7 +24,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
-import android.companion.virtual.VirtualDeviceManager;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
@@ -95,16 +94,17 @@
*
* @param proxyUid The uid of the proxy
* @param proxyPackageName The package name of the proxy
- * @param proxyAttributionTag the attributionTag in the proxies package
+ * @param proxyAttributionTag The attributionTag in the proxies package
+ * @param proxyDeviceId The device Id of the proxy
* @param uidState UID state of the app noteOp/startOp was called for
* @param flags OpFlags of the call
*/
public void accessed(int proxyUid, @Nullable String proxyPackageName,
- @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
- @AppOpsManager.OpFlags int flags) {
+ @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
+ @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
long accessTime = System.currentTimeMillis();
- accessed(accessTime, -1, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState, flags);
+ accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId,
+ uidState, flags);
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, tag, uidState, flags, accessTime,
@@ -118,14 +118,16 @@
* @param duration The duration of the event
* @param proxyUid The uid of the proxy
* @param proxyPackageName The package name of the proxy
- * @param proxyAttributionTag the attributionTag in the proxies package
+ * @param proxyAttributionTag The attributionTag in the proxies package
+ * @param proxyDeviceId The device Id of the proxy
* @param uidState UID state of the app noteOp/startOp was called for
* @param flags OpFlags of the call
*/
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
public void accessed(long noteTime, long duration, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
+ @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags) {
long key = makeKey(uidState, flags);
if (mAccessEvents == null) {
@@ -135,7 +137,7 @@
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
- proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ proxyAttributionTag, proxyDeviceId);
}
AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -189,35 +191,36 @@
* Update state when start was called
*
* @param clientId Id of the startOp caller
+ * @param virtualDeviceId The virtual device id of the startOp caller
* @param proxyUid The UID of the proxy app
* @param proxyPackageName The package name of the proxy app
* @param proxyAttributionTag The attribution tag of the proxy app
+ * @param proxyDeviceId The device id of the proxy app
* @param uidState UID state of the app startOp is called for
* @param flags The proxy flags
* @param attributionFlags The attribution flags associated with this operation.
- * @param attributionChainId The if of the attribution chain this operations is a part of.
+ * @param attributionChainId The if of the attribution chain this operations is a part of
*/
- public void started(@NonNull IBinder clientId, int proxyUid,
+ public void started(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
+ @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
@AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
int attributionChainId) throws RemoteException {
- startedOrPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, proxyVirtualDeviceId, uidState, flags,
- /* triggeredByUidStateChange */ false, /* isStarted */ true, attributionFlags,
- attributionChainId);
+ startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag,
+ proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false,
+ true);
}
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
- private void startedOrPaused(@NonNull IBinder clientId, int proxyUid,
+ private void startedOrPaused(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid,
@Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
- @AppOpsManager.OpFlags int flags, boolean triggeredByUidStateChange,
- boolean isStarted, @AppOpsManager.AttributionFlags int attributionFlags,
- int attributionChainId) throws RemoteException {
+ @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId, boolean triggeredByUidStateChange, boolean isStarted)
+ throws RemoteException {
if (!triggeredByUidStateChange && !parent.isRunning() && isStarted) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
- parent.packageName, tag, proxyVirtualDeviceId, true, attributionFlags,
+ parent.packageName, tag, virtualDeviceId, true, attributionFlags,
attributionChainId);
}
@@ -233,9 +236,9 @@
InProgressStartOpEvent event = events.get(clientId);
if (event == null) {
event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
- SystemClock.elapsedRealtime(), clientId, tag, proxyVirtualDeviceId,
+ SystemClock.elapsedRealtime(), clientId, tag, virtualDeviceId,
PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
- proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
+ proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId, uidState, flags,
attributionFlags, attributionChainId);
events.put(clientId, event);
} else {
@@ -366,15 +369,14 @@
/**
* Create an event that will be started, if the op is unpaused.
*/
- public void createPaused(@NonNull IBinder clientId, int proxyUid,
- @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
- int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState,
- @AppOpsManager.OpFlags int flags,
- @AppOpsManager.AttributionFlags int attributionFlags,
+ public void createPaused(@NonNull IBinder clientId, int virtualDeviceId,
+ int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+ @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState,
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags,
int attributionChainId) throws RemoteException {
- startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
- proxyVirtualDeviceId, uidState, flags, false, false,
- attributionFlags, attributionChainId);
+ startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag,
+ proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false,
+ false);
}
/**
@@ -496,16 +498,16 @@
// Call started() to add a new start event object and then add the
// previously removed unfinished start counts back
if (proxy != null) {
- startedOrPaused(event.getClientId(), proxy.getUid(),
- proxy.getPackageName(), proxy.getAttributionTag(),
- event.getVirtualDeviceId(), newState, event.getFlags(),
- true, isRunning,
- event.getAttributionFlags(), event.getAttributionChainId());
+ startedOrPaused(event.getClientId(), event.getVirtualDeviceId(),
+ proxy.getUid(), proxy.getPackageName(), proxy.getAttributionTag(),
+ proxy.getDeviceId(), newState, event.getFlags(),
+ event.getAttributionFlags(), event.getAttributionChainId(), true,
+ isRunning);
} else {
- startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null,
- event.getVirtualDeviceId(), newState, event.getFlags(), true,
- isRunning, event.getAttributionFlags(),
- event.getAttributionChainId());
+ startedOrPaused(event.getClientId(), event.getVirtualDeviceId(),
+ Process.INVALID_UID, null, null, null,
+ newState, event.getFlags(), event.getAttributionFlags(),
+ event.getAttributionChainId(), true, isRunning);
}
events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
@@ -847,7 +849,8 @@
InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
@Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath,
int proxyUid, @Nullable String proxyPackageName,
- @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
+ @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
+ @AppOpsManager.UidState int uidState,
@AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags
int attributionFlags, int attributionChainId) throws RemoteException {
@@ -856,7 +859,7 @@
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
- proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ proxyAttributionTag, proxyDeviceId);
}
if (recycled != null) {
@@ -880,7 +883,8 @@
super(maxUnusedPooledObjects);
}
- AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid,
+ AppOpsManager.OpEventProxyInfo acquire(
+ @IntRange(from = 0) int uid,
@Nullable String packageName,
@Nullable String attributionTag,
@Nullable String deviceId) {
@@ -890,7 +894,7 @@
return recycled;
}
- return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag);
+ return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag, deviceId);
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java
index 3e8acee..7cf2d30 100644
--- a/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java
+++ b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
@@ -63,7 +64,7 @@
intentFilter.addAction(ACTION_FACE_RE_ENROLL_LAUNCH);
intentFilter.addAction(ACTION_FACE_RE_ENROLL_DISMISS);
}
- context.registerReceiver(this, intentFilter);
+ context.registerReceiver(this, intentFilter, Context.RECEIVER_NOT_EXPORTED);
}
@Override
@@ -84,7 +85,8 @@
}
private void launchBiometricEnrollActivity(Context context, String action) {
- context.sendBroadcast(new Intent(ACTION_CLOSE_SYSTEM_DIALOGS));
+ context.sendBroadcast(
+ new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));
final Intent intent = new Intent(action);
intent.setPackage(SETTINGS_PACKAGE);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d7a7dd4..70a1014 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1442,6 +1442,9 @@
// If there's an offload session, we need to set the initial doze brightness before
// the offload session starts controlling the brightness.
+ // During the transition DOZE_SUSPEND -> DOZE -> DOZE_SUSPEND, this brightness strategy
+ // will be selected again, meaning that no new brightness will be sent to the hardware and
+ // the display will stay at the brightness level set by the offload session.
if (Float.isNaN(brightnessState) && mFlags.isDisplayOffloadEnabled()
&& Display.isDozeState(state) && mDisplayOffloadSession != null) {
if (mAutomaticBrightnessController != null
@@ -1459,6 +1462,15 @@
if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) {
brightnessState = clampScreenBrightness(rawBrightnessState);
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL);
+
+ if (mAutomaticBrightnessController != null
+ && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
+ // Keep the brightness in the setting so that we can use it after the screen
+ // turns on, until a lux sample becomes available. We don't do this when
+ // auto-brightness is disabled - in that situation we still want to use
+ // the last brightness from when the screen was on.
+ updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+ }
}
}
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
index 2703a2c0..7e18d84 100644
--- a/services/core/java/com/android/server/hdmi/SendKeyAction.java
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -158,9 +158,11 @@
mTargetAddress, cecKeycodeAndParams), new SendMessageCallback() {
@Override
public void onSendCompleted(int error) {
- if (error != SendMessageResult.SUCCESS) {
+ // Disable System Audio Mode, if the AVR doesn't acknowledge
+ // a <User Control Pressed> message.
+ if (error == SendMessageResult.NACK) {
HdmiLogger.debug(
- "AVR did not respond to <User Control Pressed>");
+ "AVR did not acknowledge <User Control Pressed>");
localDevice().mService.setSystemAudioActivated(false);
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index cbd309e..308aed6 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -156,7 +156,6 @@
private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
- private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 4;
private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
private static final AdditionalDisplayInputProperties
@@ -1324,11 +1323,6 @@
properties -> properties.pointerIconVisible = visible);
}
- private void handlePointerDisplayIdChanged(PointerDisplayIdChangedArgs args) {
- mWindowManagerCallbacks.notifyPointerDisplayIdChanged(
- args.mPointerDisplayId, args.mXPosition, args.mYPosition);
- }
-
private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) {
mNative.setDisplayEligibilityForPointerCapture(displayId, isEligible);
}
@@ -1612,18 +1606,6 @@
// Binder call
@Override
- public void setPointerIconType(int iconType) {
- // TODO(b/311416205): Remove.
- }
-
- // Binder call
- @Override
- public void setCustomPointerIcon(PointerIcon icon) {
- // TODO(b/311416205): Remove.
- }
-
- // Binder call
- @Override
public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId,
IBinder inputToken) {
Objects.requireNonNull(icon);
@@ -2703,9 +2685,7 @@
@SuppressWarnings("unused")
@VisibleForTesting
void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) {
- mHandler.obtainMessage(MSG_POINTER_DISPLAY_ID_CHANGED,
- new PointerDisplayIdChangedArgs(pointerDisplayId, xPosition,
- yPosition)).sendToTarget();
+ // TODO(b/311416205): Remove.
}
@Override
@@ -2860,14 +2840,6 @@
*/
@Nullable
SurfaceControl createSurfaceForGestureMonitor(String name, int displayId);
-
- /**
- * Notify WindowManagerService when the display of the mouse pointer changes.
- * @param displayId The display on which the mouse pointer is shown.
- * @param x The x coordinate of the mouse pointer.
- * @param y The y coordinate of the mouse pointer.
- */
- void notifyPointerDisplayIdChanged(int displayId, float x, float y);
}
/**
@@ -2911,9 +2883,6 @@
boolean inTabletMode = (boolean) args.arg1;
deliverTabletModeChanged(whenNanos, inTabletMode);
break;
- case MSG_POINTER_DISPLAY_ID_CHANGED:
- handlePointerDisplayIdChanged((PointerDisplayIdChangedArgs) msg.obj);
- break;
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 7956e03..79f1a9c 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -330,14 +330,10 @@
mHandwritingSurface.startIntercepting(imePid, imeUid);
// Unset the pointer icon for the stylus in case the app had set it.
- if (com.android.input.flags.Flags.enablePointerChoreographer()) {
- Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon(
- PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED),
- downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0),
- mHandwritingSurface.getInputChannel().getToken());
- } else {
- InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED);
- }
+ Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon(
+ PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED),
+ downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0),
+ mHandwritingSurface.getInputChannel().getToken());
return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(),
mHandwritingBuffer);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0fde760..25e2e3a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2250,7 +2250,9 @@
// Check if the input method is changing.
// We expect the caller has already verified that the client is allowed to access this
// display ID.
- if (isSelectedMethodBoundLocked()) {
+ final String curId = bindingController.getCurId();
+ if (curId != null && curId.equals(bindingController.getSelectedMethodId())
+ && mDisplayIdToShowIme == mCurTokenDisplayId) {
if (cs.mCurSession != null) {
// Fast case: if we are already connected to the input method,
// then just return it.
@@ -2369,13 +2371,6 @@
}
@GuardedBy("ImfLock.class")
- private boolean isSelectedMethodBoundLocked() {
- String curId = getCurIdLocked();
- return curId != null && curId.equals(getSelectedMethodIdLocked())
- && mDisplayIdToShowIme == mCurTokenDisplayId;
- }
-
- @GuardedBy("ImfLock.class")
private void prepareClientSwitchLocked(ClientState cs) {
// If the client is changing, we need to switch over to the new
// one.
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 0049213..d932bd4 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -32,6 +32,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.os.Bundle;
import android.os.Environment;
import android.os.HandlerThread;
import android.os.LocaleList;
@@ -101,6 +102,11 @@
// the application setting the app-locale itself.
private final SharedPreferences mDelegateAppLocalePackages;
private final BroadcastReceiver mUserMonitor;
+ // To determine whether an app is pre-archived, check for Intent.EXTRA_ARCHIVAL upon receiving
+ // the initial PACKAGE_ADDED broadcast. If it is indeed pre-archived, perform the data
+ // restoration during the second PACKAGE_ADDED broadcast, which is sent subsequently when the
+ // app is installed.
+ private final Set<String> mPkgsToRestore;
LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
PackageManager packageManager, HandlerThread broadcastHandlerThread) {
@@ -119,6 +125,7 @@
mStagedData = stagedData;
mDelegateAppLocalePackages = delegateAppLocalePackages != null ? delegateAppLocalePackages
: createPersistedInfo();
+ mPkgsToRestore = new ArraySet<>();
mUserMonitor = new UserMonitor();
IntentFilter filter = new IntentFilter();
@@ -251,6 +258,9 @@
LocalesInfo localesInfo = pkgStates.get(pkgName);
// Check if the application is already installed for the concerned user.
if (isPackageInstalledForUser(pkgName, userId)) {
+ if (mPkgsToRestore != null) {
+ mPkgsToRestore.remove(pkgName);
+ }
// Don't apply the restore if the locales have already been set for the app.
checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId);
} else {
@@ -279,23 +289,18 @@
/**
* <p><b>Note:</b> This is invoked by service's common monitor
- * {@link LocaleManagerServicePackageMonitor#onPackageAdded} when a new package is
+ * {@link LocaleManagerServicePackageMonitor#onPackageAddedWithExtras} when a new package is
* added on device.
*/
- void onPackageAdded(String packageName, int uid) {
- try {
- synchronized (mStagedDataLock) {
- cleanStagedDataForOldEntriesLocked();
-
- int userId = UserHandle.getUserId(uid);
- if (mStagedData.contains(userId)) {
- // Perform lazy restore only if the staged data exists.
- doLazyRestoreLocked(packageName, userId);
- }
+ void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) {
+ boolean archived = false;
+ if (extras != null) {
+ archived = extras.getBoolean(Intent.EXTRA_ARCHIVAL, false);
+ if (archived && mPkgsToRestore != null) {
+ mPkgsToRestore.add(packageName);
}
- } catch (Exception e) {
- Slog.e(TAG, "Exception in onPackageAdded.", e);
}
+ checkStageDataAndApplyRestore(packageName, uid);
}
/**
@@ -305,6 +310,10 @@
*/
void onPackageUpdateFinished(String packageName, int uid) {
int userId = UserHandle.getUserId(uid);
+ if (mPkgsToRestore != null && mPkgsToRestore.contains(packageName)) {
+ mPkgsToRestore.remove(packageName);
+ checkStageDataAndApplyRestore(packageName, uid);
+ }
cleanApplicationLocalesIfNeeded(packageName, userId);
}
@@ -338,6 +347,25 @@
}
}
+ private void checkStageDataAndApplyRestore(String packageName, int uid) {
+ try {
+ synchronized (mStagedDataLock) {
+ cleanStagedDataForOldEntriesLocked();
+
+ int userId = UserHandle.getUserId(uid);
+ if (mStagedData.contains(userId)) {
+ if (mPkgsToRestore != null) {
+ mPkgsToRestore.remove(packageName);
+ }
+ // Perform lazy restore only if the staged data exists.
+ doLazyRestoreLocked(packageName, userId);
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception in onPackageAdded.", e);
+ }
+ }
+
private boolean isPackageInstalledForUser(String packageName, int userId) {
PackageInfo pkgInfo = null;
try {
diff --git a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
index ecd3614..e0a050f 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java
@@ -17,6 +17,7 @@
package com.android.server.locales;
import android.annotation.NonNull;
+import android.os.Bundle;
import android.os.UserHandle;
import com.android.internal.content.PackageMonitor;
@@ -48,8 +49,8 @@
}
@Override
- public void onPackageAdded(String packageName, int uid) {
- mBackupHelper.onPackageAdded(packageName, uid);
+ public void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) {
+ mBackupHelper.onPackageAddedWithExtras(packageName, uid, extras);
}
@Override
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index a2f1942..17f8abe 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -335,6 +335,10 @@
if (Flags.reliableMessageDuplicateDetectionService()
&& didEventHappen(MESSAGE_DUPLICATION_PROBABILITY_PERCENT)) {
+ Log.i(TAG, "[TEST MODE] Duplicating message ("
+ + NUM_MESSAGES_TO_DUPLICATE
+ + " sends) with message sequence number: "
+ + message.getMessageSequenceNumber());
for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) {
handleClientMessageCallback(contextHubId, hostEndpointId,
message, nanoappPermissions, messagePermissions);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 42ec1c3..61054a9 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -11271,6 +11271,9 @@
// Lifetime extended notifications don't need to alert on state change.
record.setPostSilently(true);
+ // We also set FLAG_ONLY_ALERT_ONCE to avoid the notification from HUN-ing again.
+ record.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
+
mHandler.post(new EnqueueNotificationRunnable(record.getUser().getIdentifier(),
record, isAppForeground,
mPostNotificationTrackerFactory.newTracker(null)));
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1309e44..41d6288 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2139,10 +2139,17 @@
continue;
}
+ ComponentName unflattenOriginalComponentName = ComponentName.unflattenFromString(
+ originalComponentName);
+ if (unflattenOriginalComponentName == null) {
+ Slog.d(TAG, "Incorrect component name from the attributes");
+ continue;
+ }
+
activityInfos.add(
new ArchiveState.ArchiveActivityInfo(
title,
- ComponentName.unflattenFromString(originalComponentName),
+ unflattenOriginalComponentName,
iconPath,
monochromeIconPath));
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 4e02470..483d308 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -859,7 +859,6 @@
break;
case android.provider.Settings.System.SCREEN_BRIGHTNESS:
- case android.provider.Settings.System.SCREEN_BRIGHTNESS_FLOAT:
case android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE:
if (callingUid == Process.SYSTEM_UID) {
return false;
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 25885ed..e8faff6 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -96,6 +96,7 @@
interface TransactionReadyListener {
void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction);
default void onTransactionCommitTimeout() {}
+ default void onReadyTimeout() {}
}
/**
@@ -410,6 +411,7 @@
if (allFinished && !mReady) {
Slog.w(TAG, "Sync group " + mSyncId + " timed-out because not ready. If you see "
+ "this, please file a bug.");
+ mListener.onReadyTimeout();
}
finishNow();
removeFromDependencies(this);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4147249..c9a5e71 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -550,15 +550,6 @@
// TODO(multi-display): remove some of the usages.
boolean isDefaultDisplay;
- /** Detect user tapping outside of current focused task bounds .*/
- // TODO(b/315321016): Remove once pointer event detection is removed from WM.
- @VisibleForTesting
- final TaskTapPointerEventListener mTapDetector;
-
- /** Detect user tapping outside of current focused root task bounds .*/
- // TODO(b/315321016): Remove once pointer event detection is removed from WM.
- private Region mTouchExcludeRegion = new Region();
-
/** Save allocating when calculating rects */
private final Rect mTmpRect = new Rect();
private final Rect mTmpRect2 = new Rect();
@@ -571,10 +562,6 @@
final PinnedTaskController mPinnedTaskController;
- final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
- /** A collection of windows that provide tap exclude regions inside of them. */
- final ArraySet<WindowState> mTapExcludeProvidingWindows = new ArraySet<>();
-
private final LinkedList<ActivityRecord> mTmpUpdateAllDrawn = new LinkedList();
private final TaskForResizePointSearchResult mTmpTaskForResizePointSearchResult =
@@ -1193,18 +1180,6 @@
"PointerEventDispatcher" + mDisplayId, mDisplayId);
mPointerEventDispatcher = new PointerEventDispatcher(inputChannel);
- if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
- mTapDetector = null;
- } else {
- // Tap Listeners are supported for:
- // 1. All physical displays (multi-display).
- // 2. VirtualDisplays on VR, AA (and everything else).
- mTapDetector = new TaskTapPointerEventListener(mWmService, this);
- registerPointerEventListener(mTapDetector);
- }
- if (mWmService.mMousePositionTracker != null) {
- registerPointerEventListener(mWmService.mMousePositionTracker);
- }
if (mWmService.mAtmService.getRecentTasks() != null) {
registerPointerEventListener(
mWmService.mAtmService.getRecentTasks().getInputListener());
@@ -3304,117 +3279,6 @@
mTmpTaskForResizePointSearchResult.process(taskDisplayArea, x, y, delta));
}
- void updateTouchExcludeRegion() {
- if (mTapDetector == null) {
- // The touch exclude region is used to detect the region outside of the focused task
- // so that the tap detector can detect outside touches. Don't calculate the exclude
- // region when the tap detector is disabled.
- return;
- }
- final Task focusedTask = (mFocusedApp != null ? mFocusedApp.getTask() : null);
- if (focusedTask == null) {
- mTouchExcludeRegion.setEmpty();
- } else {
- mTouchExcludeRegion.set(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
- final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
- mTmpRect.setEmpty();
- mTmpRect2.setEmpty();
-
- forAllTasks(t -> { processTaskForTouchExcludeRegion(t, focusedTask, delta); });
-
- // If we removed the focused task above, add it back and only leave its
- // outside touch area in the exclusion. TapDetector is not interested in
- // any touch inside the focused task itself.
- if (!mTmpRect2.isEmpty()) {
- mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION);
- }
- }
- if (mInputMethodWindow != null && mInputMethodWindow.isVisible()) {
- // If the input method is visible and the user is typing, we don't want these touch
- // events to be intercepted and used to change focus. This would likely cause a
- // disappearance of the input method.
- mInputMethodWindow.getTouchableRegion(mTmpRegion);
- mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
- }
- for (int i = mTapExcludedWindows.size() - 1; i >= 0; i--) {
- final WindowState win = mTapExcludedWindows.get(i);
- if (!win.isVisible()) {
- continue;
- }
- win.getTouchableRegion(mTmpRegion);
- mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
- }
- amendWindowTapExcludeRegion(mTouchExcludeRegion);
- mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion);
- }
-
- private void processTaskForTouchExcludeRegion(Task task, Task focusedTask, int delta) {
- if (mTapDetector == null) {
- // The touch exclude region is used to detect the region outside of the focused task
- // so that the tap detector can detect outside touches. Don't calculate the exclude
- // region when the tap detector is disabled.
- }
- final ActivityRecord topVisibleActivity = task.getTopVisibleActivity();
-
- if (topVisibleActivity == null || !topVisibleActivity.hasContentToDisplay()) {
- return;
- }
-
- // Exclusion region is the region that TapDetector doesn't care about.
- // Here we want to remove all non-focused tasks from the exclusion region.
- // We also remove the outside touch area for resizing for all freeform
- // tasks (including the focused).
- // We save the focused task region once we find it, and add it back at the end.
- // If the task is root home task and it is resizable and visible (top of its root task),
- // we want to exclude the root docked task from touch so we need the entire screen area
- // and not just a small portion which the root home task currently is resized to.
- if (task.isActivityTypeHome() && task.isVisible() && task.isResizeable()) {
- task.getDisplayArea().getBounds(mTmpRect);
- } else {
- task.getDimBounds(mTmpRect);
- }
-
- if (task == focusedTask) {
- // Add the focused task rect back into the exclude region once we are done
- // processing root tasks.
- // NOTE: this *looks* like a no-op, but this usage of mTmpRect2 is expected by
- // updateTouchExcludeRegion.
- mTmpRect2.set(mTmpRect);
- }
-
- final boolean isFreeformed = task.inFreeformWindowingMode();
- if (task != focusedTask || isFreeformed) {
- if (isFreeformed) {
- // If the task is freeformed, enlarge the area to account for outside
- // touch area for resize.
- mTmpRect.inset(-delta, -delta);
- // Intersect with display content frame. If we have system decor (status bar/
- // navigation bar), we want to exclude that from the tap detection.
- // Otherwise, if the app is partially placed under some system button (eg.
- // Recents, Home), pressing that button would cause a full series of
- // unwanted transfer focus/resume/pause, before we could go home.
- mTmpRect.inset(getInsetsStateController().getRawInsetsState().calculateInsets(
- mTmpRect, systemBars() | ime(), false /* ignoreVisibility */));
- }
- mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
- }
- }
-
- /**
- * Union the region with all the tap exclude region provided by windows on this display.
- *
- * @param inOutRegion The region to be amended.
- */
- private void amendWindowTapExcludeRegion(Region inOutRegion) {
- final Region region = Region.obtain();
- for (int i = mTapExcludeProvidingWindows.size() - 1; i >= 0; i--) {
- final WindowState win = mTapExcludeProvidingWindows.valueAt(i);
- win.getTapExcludeRegion(region);
- inOutRegion.op(region, Op.UNION);
- }
- region.recycle();
- }
-
@Override
void switchUser(int userId) {
super.switchUser(userId);
@@ -3771,7 +3635,6 @@
pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
pw.print(subPrefix + "deferred=" + mDeferredRemoval
+ " mLayoutNeeded=" + mLayoutNeeded);
- pw.println(" mTouchExcludeRegion=" + mTouchExcludeRegion);
pw.println();
super.dump(pw, prefix, dumpAll);
@@ -4120,7 +3983,6 @@
}
getInputMonitor().setFocusedAppLw(newFocus);
- updateTouchExcludeRegion();
return true;
}
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 8116f68..30f2d0d 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -21,13 +21,11 @@
import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
import static android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;
-import static com.android.input.flags.Flags.enablePointerChoreographer;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
-import android.app.ActivityManager;
import android.content.ClipData;
import android.content.Context;
import android.hardware.input.InputManagerGlobal;
@@ -266,16 +264,12 @@
final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
mDragState.broadcastDragStartedLocked(touchX, touchY);
- if (enablePointerChoreographer()) {
- if ((touchSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
- InputManagerGlobal.getInstance().setPointerIcon(
- PointerIcon.getSystemIcon(
- mService.mContext, PointerIcon.TYPE_GRABBING),
- mDragState.mDisplayContent.getDisplayId(), touchDeviceId,
- touchPointerId, mDragState.getInputToken());
- }
- } else {
- mDragState.overridePointerIconLocked(touchSource);
+ if ((touchSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
+ InputManagerGlobal.getInstance().setPointerIcon(
+ PointerIcon.getSystemIcon(
+ mService.mContext, PointerIcon.TYPE_GRABBING),
+ mDragState.mDisplayContent.getDisplayId(), touchDeviceId,
+ touchPointerId, mDragState.getInputToken());
}
// remember the thumb offsets for later
mDragState.mThumbOffsetX = thumbCenterX;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 5ed343a..72ae64c 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -45,7 +45,6 @@
import android.content.ClipDescription;
import android.graphics.Point;
import android.graphics.Rect;
-import android.hardware.input.InputManagerGlobal;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
@@ -58,9 +57,7 @@
import android.view.DragEvent;
import android.view.InputApplicationHandle;
import android.view.InputChannel;
-import android.view.InputDevice;
import android.view.InputWindowHandle;
-import android.view.PointerIcon;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
@@ -110,7 +107,6 @@
boolean mCrossProfileCopyAllowed;
ClipData mData;
ClipDescription mDataDescription;
- int mTouchSource;
boolean mDragResult;
boolean mRelinquishDragSurfaceToDropTarget;
float mAnimatedScale = 1.0f;
@@ -263,12 +259,6 @@
Trace.instant(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DRAG_ENDED");
}
- // Take the cursor back if it has been changed.
- if (isFromSource(InputDevice.SOURCE_MOUSE)) {
- mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
- mTouchSource = 0;
- }
-
// Clear the internal variables.
if (mInputSurface != null) {
mTransaction.remove(mInputSurface).apply();
@@ -762,18 +752,6 @@
return animator;
}
- private boolean isFromSource(int source) {
- return (mTouchSource & source) == source;
- }
-
- void overridePointerIconLocked(int touchSource) {
- mTouchSource = touchSource;
- if (isFromSource(InputDevice.SOURCE_MOUSE)) {
- // TODO(b/293587049): Pointer Icon Refactor: Set the pointer icon from the drag window.
- InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING);
- }
- }
-
private class AnimationListener
implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
@Override
diff --git a/services/core/java/com/android/server/wm/InputConfigAdapter.java b/services/core/java/com/android/server/wm/InputConfigAdapter.java
index 119fafd..ae6e724 100644
--- a/services/core/java/com/android/server/wm/InputConfigAdapter.java
+++ b/services/core/java/com/android/server/wm/InputConfigAdapter.java
@@ -20,8 +20,6 @@
import android.view.InputWindowHandle.InputConfigFlags;
import android.view.WindowManager.LayoutParams;
-import java.util.List;
-
/**
* A helper to determine the {@link InputConfigFlags} that control the behavior of an input window
* from several WM attributes.
@@ -47,7 +45,7 @@
* input configurations that can be mapped directly from a corresponding LayoutParams input
* feature.
*/
- private static final List<FlagMapping> INPUT_FEATURE_TO_CONFIG_MAP = List.of(
+ private static final FlagMapping[] INPUT_FEATURE_TO_CONFIG_MAP = {
new FlagMapping(
LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL,
InputConfig.NO_INPUT_CHANNEL, false /* inverted */),
@@ -59,7 +57,8 @@
InputConfig.SPY, false /* inverted */),
new FlagMapping(
LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY,
- InputConfig.SENSITIVE_FOR_PRIVACY, false /* inverted */));
+ InputConfig.SENSITIVE_FOR_PRIVACY, false /* inverted */)
+ };
@InputConfigFlags
private static final int INPUT_FEATURE_TO_CONFIG_MASK =
@@ -72,7 +71,7 @@
* NOTE: The layout params flag {@link LayoutParams#FLAG_NOT_FOCUSABLE} is not handled by this
* adapter, and must be handled explicitly.
*/
- private static final List<FlagMapping> LAYOUT_PARAM_FLAG_TO_CONFIG_MAP = List.of(
+ private static final FlagMapping[] LAYOUT_PARAM_FLAG_TO_CONFIG_MAP = {
new FlagMapping(
LayoutParams.FLAG_NOT_TOUCHABLE,
InputConfig.NOT_TOUCHABLE, false /* inverted */),
@@ -84,7 +83,8 @@
InputConfig.WATCH_OUTSIDE_TOUCH, false /* inverted */),
new FlagMapping(
LayoutParams.FLAG_SLIPPERY,
- InputConfig.SLIPPERY, false /* inverted */));
+ InputConfig.SLIPPERY, false /* inverted */)
+ };
@InputConfigFlags
private static final int LAYOUT_PARAM_FLAG_TO_CONFIG_MASK =
@@ -119,7 +119,7 @@
}
@InputConfigFlags
- private static int applyMapping(int flags, List<FlagMapping> flagToConfigMap) {
+ private static int applyMapping(int flags, FlagMapping[] flagToConfigMap) {
int inputConfig = 0;
for (final FlagMapping mapping : flagToConfigMap) {
final boolean flagSet = (flags & mapping.mFlag) != 0;
@@ -131,7 +131,7 @@
}
@InputConfigFlags
- private static int computeMask(List<FlagMapping> flagToConfigMap) {
+ private static int computeMask(FlagMapping[] flagToConfigMap) {
int mask = 0;
for (final FlagMapping mapping : flagToConfigMap) {
mask |= mapping.mInputConfig;
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index a84ebd9..22ca82a 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -290,22 +290,6 @@
}
}
- @Override
- public void notifyPointerDisplayIdChanged(int displayId, float x, float y) {
- synchronized (mService.mGlobalLock) {
- mService.setMousePointerDisplayId(displayId);
- if (displayId == Display.INVALID_DISPLAY) return;
-
- final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
- if (dc == null) {
- Slog.wtf(TAG, "The mouse pointer was moved to display " + displayId
- + " that does not have a valid DisplayContent.");
- return;
- }
- mService.restorePointerIconLocked(dc, x, y);
- }
- }
-
/** Waits until the built-in input devices have been configured. */
public boolean waitForInputDevicesReady(long timeoutMillis) {
synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6003c1b..be8c2ae 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -911,7 +911,6 @@
dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
dc.updateSystemGestureExclusion();
dc.updateKeepClearAreas();
- dc.updateTouchExcludeRegion();
});
// Check to see if we are now in a state where the screen should
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index bb86460..3b3eeb4 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -740,16 +740,6 @@
}
@Override
- public void updatePointerIcon(IWindow window) {
- final long identity = Binder.clearCallingIdentity();
- try {
- mService.updatePointerIcon(window);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
public void updateTapExcludeRegion(IWindow window, Region region) {
final long identity = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
deleted file mode 100644
index ac244c7..0000000
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2013 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.wm;
-
-import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_NOT_SPECIFIED;
-import static android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
-
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.hardware.input.InputManagerGlobal;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.WindowManagerPolicyConstants.PointerEventListener;
-
-import com.android.server.wm.WindowManagerService.H;
-
-/**
- * 1. Adjust the top most focus display if touch down on some display.
- * 2. Adjust the pointer icon when cursor moves to the task bounds.
- */
-public class TaskTapPointerEventListener implements PointerEventListener {
-
- private final Region mTouchExcludeRegion = new Region();
- private final WindowManagerService mService;
- private final DisplayContent mDisplayContent;
- private final Rect mTmpRect = new Rect();
- private int mPointerIconType = TYPE_NOT_SPECIFIED;
-
- public TaskTapPointerEventListener(WindowManagerService service,
- DisplayContent displayContent) {
- // TODO(b/315321016): Remove this class when the flag rollout is complete.
- if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) {
- throw new IllegalStateException("TaskTapPointerEventListener should not be used!");
- }
- mService = service;
- mDisplayContent = displayContent;
- }
-
- private void restorePointerIcon(int x, int y) {
- if (mPointerIconType != TYPE_NOT_SPECIFIED) {
- mPointerIconType = TYPE_NOT_SPECIFIED;
- // Find the underlying window and ask it to restore the pointer icon.
- mService.mH.removeMessages(H.RESTORE_POINTER_ICON);
- mService.mH.obtainMessage(H.RESTORE_POINTER_ICON,
- x, y, mDisplayContent).sendToTarget();
- }
- }
-
- @Override
- public void onPointerEvent(MotionEvent motionEvent) {
- switch (motionEvent.getActionMasked()) {
- case MotionEvent.ACTION_DOWN: {
- final int x;
- final int y;
- if (motionEvent.getSource() == InputDevice.SOURCE_MOUSE) {
- x = (int) motionEvent.getXCursorPosition();
- y = (int) motionEvent.getYCursorPosition();
- } else {
- x = (int) motionEvent.getX();
- y = (int) motionEvent.getY();
- }
-
- synchronized (this) {
- if (!mTouchExcludeRegion.contains(x, y)) {
- mService.mTaskPositioningController.handleTapOutsideTask(
- mDisplayContent, x, y);
- }
- }
- }
- break;
- case MotionEvent.ACTION_HOVER_ENTER:
- case MotionEvent.ACTION_HOVER_MOVE: {
- final int x = (int) motionEvent.getX();
- final int y = (int) motionEvent.getY();
- if (mTouchExcludeRegion.contains(x, y)) {
- restorePointerIcon(x, y);
- break;
- }
- final Task task = mDisplayContent.findTaskForResizePoint(x, y);
- int iconType = TYPE_NOT_SPECIFIED;
- if (task != null) {
- task.getDimBounds(mTmpRect);
- if (!mTmpRect.isEmpty() && !mTmpRect.contains(x, y)) {
- if (x < mTmpRect.left) {
- iconType =
- (y < mTmpRect.top) ? TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW :
- (y > mTmpRect.bottom) ? TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW :
- TYPE_HORIZONTAL_DOUBLE_ARROW;
- } else if (x > mTmpRect.right) {
- iconType =
- (y < mTmpRect.top) ? TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW :
- (y > mTmpRect.bottom) ? TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW :
- TYPE_HORIZONTAL_DOUBLE_ARROW;
- } else if (y < mTmpRect.top || y > mTmpRect.bottom) {
- iconType = TYPE_VERTICAL_DOUBLE_ARROW;
- }
- }
- }
- if (mPointerIconType != iconType) {
- mPointerIconType = iconType;
- if (mPointerIconType == TYPE_NOT_SPECIFIED) {
- // Find the underlying window and ask it restore the pointer icon.
- mService.mH.removeMessages(H.RESTORE_POINTER_ICON);
- mService.mH.obtainMessage(H.RESTORE_POINTER_ICON,
- x, y, mDisplayContent).sendToTarget();
- } else {
- InputManagerGlobal.getInstance()
- .setPointerIconType(mPointerIconType);
- }
- }
- }
- break;
- case MotionEvent.ACTION_HOVER_EXIT: {
- final int x = (int) motionEvent.getX();
- final int y = (int) motionEvent.getY();
- restorePointerIcon(x, y);
- }
- break;
- }
- }
-
- void setTouchExcludeRegion(Region newRegion) {
- synchronized (this) {
- mTouchExcludeRegion.set(newRegion);
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1543263..7ec31d5 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1648,14 +1648,6 @@
}
if (mController.useFullReadyTracking()) {
- if (mReadyTracker.mMet.isEmpty()) {
- Slog.e(TAG, "#" + mSyncId + ": No conditions provided");
- } else {
- for (int i = 0; i < mReadyTracker.mConditions.size(); ++i) {
- Slog.e(TAG, "#" + mSyncId + ": unmet condition at ready: "
- + mReadyTracker.mConditions.get(i));
- }
- }
for (int i = 0; i < mReadyTracker.mMet.size(); ++i) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "#%d: Met condition: %s",
mSyncId, mReadyTracker.mMet.get(i));
@@ -3360,6 +3352,18 @@
applyReady();
}
+ @Override
+ public void onReadyTimeout() {
+ if (!mController.useFullReadyTracking()) {
+ Slog.e(TAG, "#" + mSyncId + " readiness timeout, used=" + mReadyTrackerOld.mUsed
+ + " deferReadyDepth=" + mReadyTrackerOld.mDeferReadyDepth
+ + " group=" + mReadyTrackerOld.mReadyGroups);
+ return;
+ }
+ Slog.e(TAG, "#" + mSyncId + " met conditions: " + mReadyTracker.mMet);
+ Slog.e(TAG, "#" + mSyncId + " unmet conditions: " + mReadyTracker.mConditions);
+ }
+
/**
* Represents a condition that must be met before an associated transition can be considered
* ready.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1559be5..e02e5be 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -81,15 +81,11 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
-import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_NONE;
@@ -203,7 +199,6 @@
import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
-import android.hardware.input.InputManager;
import android.hardware.input.InputSettings;
import android.net.Uri;
import android.os.Binder;
@@ -289,8 +284,6 @@
import android.view.InsetsState;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
-import android.view.MotionEvent;
-import android.view.PointerIcon;
import android.view.RemoteAnimationAdapter;
import android.view.ScrollCaptureResponse;
import android.view.Surface;
@@ -1527,18 +1520,6 @@
}
}
- static boolean excludeWindowTypeFromTapOutTask(int windowType) {
- switch (windowType) {
- case TYPE_STATUS_BAR:
- case TYPE_NOTIFICATION_SHADE:
- case TYPE_NAVIGATION_BAR:
- case TYPE_INPUT_METHOD_DIALOG:
- case TYPE_VOLUME_OVERLAY:
- return true;
- }
- return false;
- }
-
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
@@ -1837,10 +1818,6 @@
displayContent.mWinAddedSinceNullFocus.add(win);
}
- if (excludeWindowTypeFromTapOutTask(type)) {
- displayContent.mTapExcludedWindows.add(win);
- }
-
win.mSession.onWindowAdded(win);
mWindowMap.put(client.asBinder(), win);
win.initAppOpsState();
@@ -5720,7 +5697,6 @@
public static final int UPDATE_ANIMATION_SCALE = 51;
public static final int WINDOW_HIDE_TIMEOUT = 52;
- public static final int RESTORE_POINTER_ICON = 55;
public static final int SET_HAS_OVERLAY_UI = 58;
public static final int ANIMATION_FAILSAFE = 60;
public static final int RECOMPUTE_FOCUS = 61;
@@ -5953,12 +5929,6 @@
}
break;
}
- case RESTORE_POINTER_ICON: {
- synchronized (mGlobalLock) {
- restorePointerIconLocked((DisplayContent)msg.obj, msg.arg1, msg.arg2);
- }
- break;
- }
case SET_HAS_OVERLAY_UI: {
mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1);
break;
@@ -7578,144 +7548,6 @@
}
}
- // The mouse position tracker will be obsolete after the Pointer Icon Refactor.
- // TODO(b/293587049): Remove after the refactoring is fully rolled out.
- @Nullable
- final MousePositionTracker mMousePositionTracker =
- com.android.input.flags.Flags.enablePointerChoreographer() ? null
- : new MousePositionTracker();
-
- private static class MousePositionTracker implements PointerEventListener {
- private boolean mLatestEventWasMouse;
- private float mLatestMouseX;
- private float mLatestMouseY;
-
- /**
- * The display that the pointer (mouse cursor) is currently shown on. This is updated
- * directly by InputManagerService when the pointer display changes.
- */
- private int mPointerDisplayId = INVALID_DISPLAY;
-
- /**
- * Update the mouse cursor position as a result of a mouse movement.
- * @return true if the position was successfully updated, false otherwise.
- */
- boolean updatePosition(int displayId, float x, float y) {
- synchronized (this) {
- mLatestEventWasMouse = true;
-
- if (displayId != mPointerDisplayId) {
- // The display of the position update does not match the display on which the
- // mouse pointer is shown, so do not update the position.
- return false;
- }
- mLatestMouseX = x;
- mLatestMouseY = y;
- return true;
- }
- }
-
- void setPointerDisplayId(int displayId) {
- synchronized (this) {
- mPointerDisplayId = displayId;
- }
- }
-
- @Override
- public void onPointerEvent(MotionEvent motionEvent) {
- if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) {
- updatePosition(motionEvent.getDisplayId(), motionEvent.getRawX(),
- motionEvent.getRawY());
- } else {
- synchronized (this) {
- mLatestEventWasMouse = false;
- }
- }
- }
- };
-
- void updatePointerIcon(IWindow client) {
- if (mMousePositionTracker == null) {
- return;
- }
- int pointerDisplayId;
- float mouseX, mouseY;
-
- synchronized(mMousePositionTracker) {
- if (!mMousePositionTracker.mLatestEventWasMouse) {
- return;
- }
- mouseX = mMousePositionTracker.mLatestMouseX;
- mouseY = mMousePositionTracker.mLatestMouseY;
- pointerDisplayId = mMousePositionTracker.mPointerDisplayId;
- }
-
- synchronized (mGlobalLock) {
- if (mDragDropController.dragDropActiveLocked()) {
- // Drag cursor overrides the app cursor.
- return;
- }
- WindowState callingWin = windowForClientLocked(null, client, false);
- if (callingWin == null) {
- ProtoLog.w(WM_ERROR, "Bad requesting window %s", client);
- return;
- }
- final DisplayContent displayContent = callingWin.getDisplayContent();
- if (displayContent == null) {
- return;
- }
- if (pointerDisplayId != displayContent.getDisplayId()) {
- // Do not let the pointer icon be updated by a window on a different display.
- return;
- }
- WindowState windowUnderPointer =
- displayContent.getTouchableWinAtPointLocked(mouseX, mouseY);
- if (windowUnderPointer != callingWin) {
- return;
- }
- try {
- windowUnderPointer.mClient.updatePointerIcon(
- windowUnderPointer.translateToWindowX(mouseX),
- windowUnderPointer.translateToWindowY(mouseY));
- } catch (RemoteException e) {
- ProtoLog.w(WM_ERROR, "unable to update pointer icon");
- }
- }
- }
-
- void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
- if (mMousePositionTracker == null) {
- return;
- }
- // Mouse position tracker has not been getting updates while dragging, update it now.
- if (!mMousePositionTracker.updatePosition(
- displayContent.getDisplayId(), latestX, latestY)) {
- // The mouse position could not be updated, so ignore this request.
- return;
- }
-
- WindowState windowUnderPointer =
- displayContent.getTouchableWinAtPointLocked(latestX, latestY);
- if (windowUnderPointer != null) {
- try {
- windowUnderPointer.mClient.updatePointerIcon(
- windowUnderPointer.translateToWindowX(latestX),
- windowUnderPointer.translateToWindowY(latestY));
- } catch (RemoteException e) {
- ProtoLog.w(WM_ERROR, "unable to restore pointer icon");
- }
- } else {
- mContext.getSystemService(InputManager.class)
- .setPointerIconType(PointerIcon.TYPE_DEFAULT);
- }
- }
- void setMousePointerDisplayId(int displayId) {
- if (mMousePositionTracker == null) {
- return;
- }
- mMousePositionTracker.setPointerDisplayId(displayId);
- }
-
/**
* Update a tap exclude region in the window identified by the provided id. Touches down on this
* region will not:
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 510f69a..dddc7b1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2359,18 +2359,12 @@
}
final int type = mAttrs.type;
- if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) {
- dc.mTapExcludedWindows.remove(this);
- }
if (type == TYPE_PRESENTATION || type == TYPE_PRIVATE_PRESENTATION) {
mWmService.mDisplayManagerInternal.onPresentation(dc.getDisplay().getDisplayId(),
/*isShown=*/ false);
}
- // Remove this window from mTapExcludeProvidingWindows. If it was not registered, this will
- // not do anything.
- dc.mTapExcludeProvidingWindows.remove(this);
dc.getDisplayPolicy().removeWindowLw(this);
disposeInputChannel();
@@ -5526,18 +5520,10 @@
// Clear the tap excluded region if the region passed in is null or empty.
if (region == null || region.isEmpty()) {
mTapExcludeRegion.setEmpty();
- // Remove this window from mTapExcludeProvidingWindows since it won't be providing
- // tap exclude regions.
- currentDisplay.mTapExcludeProvidingWindows.remove(this);
} else {
mTapExcludeRegion.set(region);
- // Make sure that this window is registered as one that provides a tap exclude region
- // for its containing display.
- currentDisplay.mTapExcludeProvidingWindows.add(this);
}
- // Trigger touch exclude region update on current display.
- currentDisplay.updateTouchExcludeRegion();
// Trigger touchable region update for this window.
currentDisplay.getInputMonitor().updateInputWindowsLw(true /* force */);
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 62f5b89..ba89fda 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -272,22 +272,23 @@
void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name);
- base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
+ base::Result<std::unique_ptr<InputChannel>> createInputMonitor(ui::LogicalDisplayId displayId,
const std::string& name,
gui::Pid pid);
status_t removeInputChannel(const sp<IBinder>& connectionToken);
status_t pilferPointers(const sp<IBinder>& token);
- void displayRemoved(JNIEnv* env, int32_t displayId);
- void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj);
- void setFocusedDisplay(int32_t displayId);
+ void displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId);
+ void setFocusedApplication(JNIEnv* env, ui::LogicalDisplayId displayId,
+ jobject applicationHandleObj);
+ void setFocusedDisplay(ui::LogicalDisplayId displayId);
void setMinTimeBetweenUserActivityPokes(int64_t intervalMillis);
void setInputDispatchMode(bool enabled, bool frozen);
void setSystemUiLightsOut(bool lightsOut);
- void setPointerDisplayId(int32_t displayId);
+ void setPointerDisplayId(ui::LogicalDisplayId displayId);
int32_t getMousePointerSpeed();
void setPointerSpeed(int32_t speed);
- void setMousePointerAccelerationEnabled(int32_t displayId, bool enabled);
+ void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled);
void setTouchpadPointerSpeed(int32_t speed);
void setTouchpadNaturalScrollingEnabled(bool enabled);
void setTouchpadTapToClickEnabled(bool enabled);
@@ -300,13 +301,13 @@
void reloadPointerIcons();
void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
- int32_t displayId, DeviceId deviceId, int32_t pointerId,
+ ui::LogicalDisplayId displayId, DeviceId deviceId, int32_t pointerId,
const sp<IBinder>& inputToken);
- void setPointerIconVisibility(int32_t displayId, bool visible);
+ void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible);
void setMotionClassifierEnabled(bool enabled);
std::optional<std::string> getBluetoothAddress(int32_t deviceId);
void setStylusButtonMotionEventsEnabled(bool enabled);
- FloatPoint getMouseCursorPosition(int32_t displayId);
+ FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId);
void setStylusPointerIconEnabled(bool enabled);
void setInputMethodConnectionIsActive(bool isActive);
@@ -325,7 +326,7 @@
void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override;
bool isInputMethodConnectionActive() override;
std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
- int32_t associatedDisplayId) override;
+ ui::LogicalDisplayId associatedDisplayId) override;
/* --- InputDispatcherPolicyInterface implementation --- */
@@ -348,13 +349,15 @@
void notifyVibratorState(int32_t deviceId, bool isOn) override;
bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override;
void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) override;
- void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action,
- nsecs_t when, uint32_t& policyFlags) override;
+ void interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId, uint32_t source,
+ int32_t action, nsecs_t when,
+ uint32_t& policyFlags) override;
nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent,
uint32_t policyFlags) override;
std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent& keyEvent,
uint32_t policyFlags) override;
- void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
+ void pokeUserActivity(nsecs_t eventTime, int32_t eventType,
+ ui::LogicalDisplayId displayId) override;
void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
void setPointerCapture(const PointerCaptureRequest& request) override;
void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
@@ -363,11 +366,13 @@
/* --- PointerControllerPolicyInterface implementation --- */
- virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId);
- virtual void loadPointerResources(PointerResources* outResources, int32_t displayId);
+ virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId);
+ virtual void loadPointerResources(PointerResources* outResources,
+ ui::LogicalDisplayId displayId);
virtual void loadAdditionalMouseResources(
std::map<PointerIconStyle, SpriteIcon>* outResources,
- std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId);
+ std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
+ ui::LogicalDisplayId displayId);
virtual PointerIconStyle getDefaultPointerIconId();
virtual PointerIconStyle getDefaultStylusIconId();
virtual PointerIconStyle getCustomPointerIconId();
@@ -375,7 +380,8 @@
/* --- PointerChoreographerPolicyInterface implementation --- */
std::shared_ptr<PointerControllerInterface> createPointerController(
PointerControllerInterface::ControllerType type) override;
- void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override;
+ void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
+ const FloatPoint& position) override;
/* --- InputFilterPolicyInterface implementation --- */
void notifyStickyModifierStateChanged(uint32_t modifierState,
@@ -399,7 +405,7 @@
int32_t pointerSpeed{0};
// Displays on which its associated mice will have pointer acceleration disabled.
- std::set<int32_t> displaysWithMousePointerAccelerationDisabled{};
+ std::set<ui::LogicalDisplayId> displaysWithMousePointerAccelerationDisabled{};
// True if pointer gestures are enabled.
bool pointerGesturesEnabled{true};
@@ -417,7 +423,7 @@
std::set<int32_t> disabledInputDevices{};
// Associated Pointer controller display.
- int32_t pointerDisplayId{ADISPLAY_ID_DEFAULT};
+ ui::LogicalDisplayId pointerDisplayId{ui::ADISPLAY_ID_DEFAULT};
// True if stylus button reporting through motion events is enabled.
bool stylusButtonMotionEventsEnabled{true};
@@ -450,7 +456,7 @@
void updateInactivityTimeoutLocked();
void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
void ensureSpriteControllerLocked();
- sp<SurfaceControl> getParentSurfaceForPointers(int displayId);
+ sp<SurfaceControl> getParentSurfaceForPointers(ui::LogicalDisplayId displayId);
static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
template <typename T>
std::unordered_map<std::string, T> readMapFromInterleavedJavaArray(
@@ -459,7 +465,7 @@
void forEachPointerControllerLocked(std::function<void(PointerController&)> apply)
REQUIRES(mLock);
- PointerIcon loadPointerIcon(JNIEnv* env, int32_t displayId, PointerIconStyle type);
+ PointerIcon loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type);
static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); }
};
@@ -490,7 +496,9 @@
toString(mLocked.systemUiLightsOut));
dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
dump += StringPrintf(INDENT "Display with Mouse Pointer Acceleration Disabled: %s\n",
- dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled).c_str());
+ dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled,
+ streamableToString)
+ .c_str());
dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
@@ -552,7 +560,7 @@
}
base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor(
- int32_t displayId, const std::string& name, gui::Pid pid) {
+ ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid) {
ATRACE_CALL();
return mInputManager->getDispatcher().createInputMonitor(displayId, name, pid);
}
@@ -735,7 +743,7 @@
}
}
-PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, int32_t displayId,
+PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId,
PointerIconStyle type) {
if (type == PointerIconStyle::TYPE_CUSTOM) {
LOG(FATAL) << __func__ << ": Cannot load non-system icon type";
@@ -766,7 +774,7 @@
return pc;
}
-void NativeInputManager::notifyPointerDisplayIdChanged(int32_t pointerDisplayId,
+void NativeInputManager::notifyPointerDisplayIdChanged(ui::LogicalDisplayId pointerDisplayId,
const FloatPoint& position) {
// Notify the Reader so that devices can be reconfigured.
{ // acquire lock
@@ -775,7 +783,7 @@
return;
}
mLocked.pointerDisplayId = pointerDisplayId;
- ALOGI("%s: pointer displayId set to: %d", __func__, pointerDisplayId);
+ ALOGI("%s: pointer displayId set to: %s", __func__, pointerDisplayId.toString().c_str());
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
InputReaderConfiguration::Change::DISPLAY_INFO);
@@ -795,7 +803,7 @@
checkAndClearExceptionFromCallback(env, "notifyStickyModifierStateChanged");
}
-sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(int displayId) {
+sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(ui::LogicalDisplayId displayId) {
JNIEnv* env = jniEnv();
jlong nativeSurfaceControlPtr =
env->CallLongMethod(mServiceObj, gServiceClassInfo.getParentSurfaceForPointers,
@@ -817,9 +825,10 @@
layer = -1;
}
mLocked.spriteController =
- std::make_shared<SpriteController>(mLooper, layer, [this](int displayId) {
- return getParentSurfaceForPointers(displayId);
- });
+ std::make_shared<SpriteController>(mLooper, layer,
+ [this](ui::LogicalDisplayId displayId) {
+ return getParentSurfaceForPointers(displayId);
+ });
// The SpriteController needs to be shared pointer because the handler callback needs to hold
// a weak reference so that we can avoid racy conditions when the controller is being destroyed.
mLocked.spriteController->setHandlerController(mLocked.spriteController);
@@ -1021,8 +1030,7 @@
jobject tokenObj = javaObjectForIBinder(env, token);
if (tokenObj) {
- env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken,
- tokenObj);
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken, tokenObj);
checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken");
}
}
@@ -1108,12 +1116,12 @@
checkAndClearExceptionFromCallback(env, "notifyVibratorState");
}
-void NativeInputManager::displayRemoved(JNIEnv* env, int32_t displayId) {
+void NativeInputManager::displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId) {
mInputManager->getDispatcher().displayRemoved(displayId);
}
-void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId,
- jobject applicationHandleObj) {
+void NativeInputManager::setFocusedApplication(JNIEnv* env, ui::LogicalDisplayId displayId,
+ jobject applicationHandleObj) {
if (!applicationHandleObj) {
return;
}
@@ -1123,7 +1131,7 @@
mInputManager->getDispatcher().setFocusedApplication(displayId, applicationHandle);
}
-void NativeInputManager::setFocusedDisplay(int32_t displayId) {
+void NativeInputManager::setFocusedDisplay(ui::LogicalDisplayId displayId) {
mInputManager->getDispatcher().setFocusedDisplay(displayId);
}
@@ -1151,7 +1159,7 @@
});
}
-void NativeInputManager::setPointerDisplayId(int32_t displayId) {
+void NativeInputManager::setPointerDisplayId(ui::LogicalDisplayId displayId) {
mInputManager->getChoreographer().setDefaultMouseDisplayId(displayId);
}
@@ -1176,7 +1184,8 @@
InputReaderConfiguration::Change::POINTER_SPEED);
}
-void NativeInputManager::setMousePointerAccelerationEnabled(int32_t displayId, bool enabled) {
+void NativeInputManager::setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId,
+ bool enabled) {
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -1186,8 +1195,8 @@
return;
}
- ALOGI("Setting mouse pointer acceleration to %s on display %d", toString(enabled),
- displayId);
+ ALOGI("Setting mouse pointer acceleration to %s on display %s", toString(enabled),
+ displayId.toString().c_str());
if (enabled) {
mLocked.displaysWithMousePointerAccelerationDisabled.erase(displayId);
} else {
@@ -1326,8 +1335,9 @@
}
bool NativeInputManager::setPointerIcon(
- std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId,
- DeviceId deviceId, int32_t pointerId, const sp<IBinder>& inputToken) {
+ std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
+ ui::LogicalDisplayId displayId, DeviceId deviceId, int32_t pointerId,
+ const sp<IBinder>& inputToken) {
if (!mInputManager->getDispatcher().isPointerInWindow(inputToken, displayId, deviceId,
pointerId)) {
LOG(WARNING) << "Attempted to change the pointer icon for deviceId " << deviceId
@@ -1339,7 +1349,7 @@
return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId);
}
-void NativeInputManager::setPointerIconVisibility(int32_t displayId, bool visible) {
+void NativeInputManager::setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) {
mInputManager->getChoreographer().setPointerIconVisibility(displayId, visible);
}
@@ -1394,7 +1404,7 @@
}
std::optional<DisplayViewport> NativeInputManager::getPointerViewportForAssociatedDisplay(
- int32_t associatedDisplayId) {
+ ui::LogicalDisplayId associatedDisplayId) {
return mInputManager->getChoreographer().getViewportForPointerDevice(associatedDisplayId);
}
@@ -1469,9 +1479,9 @@
handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
}
-void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, uint32_t source,
- int32_t action, nsecs_t when,
- uint32_t& policyFlags) {
+void NativeInputManager::interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId,
+ uint32_t source, int32_t action,
+ nsecs_t when, uint32_t& policyFlags) {
ATRACE_CALL();
// Policy:
// - Ignore untrusted events and pass them along.
@@ -1590,7 +1600,8 @@
return fallbackEvent;
}
-void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) {
+void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType,
+ ui::LogicalDisplayId displayId) {
ATRACE_CALL();
android_server_PowerManagerService_userActivity(eventTime, eventType, displayId);
}
@@ -1621,13 +1632,14 @@
InputReaderConfiguration::Change::POINTER_CAPTURE);
}
-void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) {
+void NativeInputManager::loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) {
ATRACE_CALL();
JNIEnv* env = jniEnv();
*icon = toSpriteIcon(loadPointerIcon(env, displayId, PointerIconStyle::TYPE_ARROW));
}
-void NativeInputManager::loadPointerResources(PointerResources* outResources, int32_t displayId) {
+void NativeInputManager::loadPointerResources(PointerResources* outResources,
+ ui::LogicalDisplayId displayId) {
ATRACE_CALL();
JNIEnv* env = jniEnv();
@@ -1641,7 +1653,8 @@
void NativeInputManager::loadAdditionalMouseResources(
std::map<PointerIconStyle, SpriteIcon>* outResources,
- std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId) {
+ std::map<PointerIconStyle, PointerAnimation>* outAnimationResources,
+ ui::LogicalDisplayId displayId) {
ATRACE_CALL();
JNIEnv* env = jniEnv();
@@ -1708,7 +1721,7 @@
InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING);
}
-FloatPoint NativeInputManager::getMouseCursorPosition(int32_t displayId) {
+FloatPoint NativeInputManager::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
return mInputManager->getChoreographer().getMouseCursorPosition(displayId);
}
@@ -1874,7 +1887,7 @@
jstring nameObj, jint pid) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- if (displayId == ADISPLAY_ID_NONE) {
+ if (displayId == ui::ADISPLAY_ID_NONE.val()) {
std::string message = "InputChannel used as a monitor must be associated with a display";
jniThrowRuntimeException(env, message.c_str());
return nullptr;
@@ -1884,7 +1897,7 @@
std::string name = nameChars.c_str();
base::Result<std::unique_ptr<InputChannel>> inputChannel =
- im->createInputMonitor(displayId, name, gui::Pid{pid});
+ im->createInputMonitor(ui::LogicalDisplayId{displayId}, name, gui::Pid{pid});
if (!inputChannel.ok()) {
std::string message = inputChannel.error().message();
@@ -1931,7 +1944,8 @@
return im->getInputManager()->getDispatcher().setInTouchMode(inTouchMode, gui::Pid{pid},
gui::Uid{static_cast<uid_t>(uid)},
- hasPermission, displayId);
+ hasPermission,
+ ui::LogicalDisplayId{displayId});
}
static void nativeSetMaximumObscuringOpacityForTouch(JNIEnv* env, jobject nativeImplObj,
@@ -2023,20 +2037,20 @@
static void nativeDisplayRemoved(JNIEnv* env, jobject nativeImplObj, jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->displayRemoved(env, displayId);
+ im->displayRemoved(env, ui::LogicalDisplayId{displayId});
}
static void nativeSetFocusedApplication(JNIEnv* env, jobject nativeImplObj, jint displayId,
jobject applicationHandleObj) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setFocusedApplication(env, displayId, applicationHandleObj);
+ im->setFocusedApplication(env, ui::LogicalDisplayId{displayId}, applicationHandleObj);
}
static void nativeSetFocusedDisplay(JNIEnv* env, jobject nativeImplObj, jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setFocusedDisplay(displayId);
+ im->setFocusedDisplay(ui::LogicalDisplayId{displayId});
}
static void nativeSetUserActivityPokeInterval(JNIEnv* env, jobject nativeImplObj,
@@ -2092,8 +2106,8 @@
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
if (im->getInputManager()->getDispatcher().transferTouchOnDisplay(destChannelToken,
- static_cast<int32_t>(
- displayId))) {
+ ui::LogicalDisplayId{
+ displayId})) {
return JNI_TRUE;
} else {
return JNI_FALSE;
@@ -2116,7 +2130,7 @@
jint displayId, jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setMousePointerAccelerationEnabled(displayId, enabled);
+ im->setMousePointerAccelerationEnabled(ui::LogicalDisplayId{displayId}, enabled);
}
static void nativeSetTouchpadPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
@@ -2486,7 +2500,7 @@
icon = pointerIcon.style;
}
- return im->setPointerIcon(std::move(icon), displayId, deviceId, pointerId,
+ return im->setPointerIcon(std::move(icon), ui::LogicalDisplayId{displayId}, deviceId, pointerId,
ibinderForJavaObject(env, inputTokenObj));
}
@@ -2494,13 +2508,14 @@
jboolean visible) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setPointerIconVisibility(displayId, visible);
+ im->setPointerIconVisibility(ui::LogicalDisplayId{displayId}, visible);
}
static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- return im->getInputManager()->getReader().canDispatchToDisplay(deviceId, displayId);
+ return im->getInputManager()->getReader().canDispatchToDisplay(deviceId,
+ ui::LogicalDisplayId{displayId});
}
static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplObj) {
@@ -2512,8 +2527,9 @@
static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj,
jint displayId, jboolean isEligible) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId,
- isEligible);
+ im->getInputManager()
+ ->getDispatcher()
+ .setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId{displayId}, isEligible);
}
static void nativeChangeUniqueIdAssociation(JNIEnv* env, jobject nativeImplObj) {
@@ -2649,7 +2665,7 @@
static void nativeSetPointerDisplayId(JNIEnv* env, jobject nativeImplObj, jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setPointerDisplayId(displayId);
+ im->setPointerDisplayId(ui::LogicalDisplayId{displayId});
}
static jstring nativeGetBluetoothAddress(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
@@ -2667,7 +2683,7 @@
static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj,
jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- const auto p = im->getMouseCursorPosition(displayId);
+ const auto p = im->getMouseCursorPosition(ui::LogicalDisplayId{displayId});
const std::array<float, 2> arr = {{p.x, p.y}};
jfloatArray outArr = env->NewFloatArray(2);
env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data());
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index d0b290c..0733968 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -99,7 +99,7 @@
}
void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType,
- int32_t displayId) {
+ ui::LogicalDisplayId displayId) {
if (gPowerManagerServiceObj) {
// Throttle calls into user activity by event type.
// We're a little conservative about argument checking here in case the caller
@@ -124,8 +124,8 @@
JNIEnv* env = AndroidRuntime::getJNIEnv();
env->CallVoidMethod(gPowerManagerServiceObj,
- gPowerManagerServiceClassInfo.userActivityFromNative,
- nanoseconds_to_milliseconds(eventTime), eventType, displayId, 0);
+ gPowerManagerServiceClassInfo.userActivityFromNative,
+ nanoseconds_to_milliseconds(eventTime), eventType, displayId.val(), 0);
checkAndClearExceptionFromCallback(env, "userActivityFromNative");
}
}
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.h b/services/core/jni/com_android_server_power_PowerManagerService.h
index 36aaceb..ed7fa7c 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.h
+++ b/services/core/jni/com_android_server_power_PowerManagerService.h
@@ -19,6 +19,7 @@
#include <nativehelper/JNIHelp.h>
#include <powermanager/PowerManager.h>
+#include <ui/LogicalDisplayId.h>
#include <utils/Timers.h>
#include "jni.h"
@@ -26,7 +27,7 @@
namespace android {
extern void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType,
- int32_t displayId);
+ ui::LogicalDisplayId displayId);
} // namespace android
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 375fc5a..9eb7b22 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -21605,12 +21605,9 @@
== HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
}
- if (Flags.headlessSingleMinTargetSdk()
- && mInjector.userManagerIsHeadlessSystemUserMode()
- && isSingleUserMode
- && !mInjector.isChangeEnabled(
- PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(),
- caller.getUserId())) {
+ if (Flags.headlessSingleUserFixes() && mInjector.userManagerIsHeadlessSystemUserMode()
+ && isSingleUserMode && !mInjector.isChangeEnabled(
+ PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(), caller.getUserId())) {
throw new IllegalStateException("Device admin is not targeting Android V.");
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 4bf3ff4..09eef45 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -196,19 +196,27 @@
Binder.withCleanCallingIdentity(() -> {
PackageManagerInternal pmi =
LocalServices.getService(PackageManagerInternal.class);
+ AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+
pmi.setOwnerProtectedPackages(userId,
packages == null ? null : packages.stream().toList());
LocalServices.getService(UsageStatsManagerInternal.class)
.setAdminProtectedPackages(
packages == null ? null : new ArraySet<>(packages), userId);
- if (Flags.disallowUserControlBgUsageFix()) {
- if (packages == null) {
- return;
+ if (packages == null || packages.isEmpty()) {
+ return;
+ }
+
+ for (int user : resolveUsers(userId)) {
+ if (Flags.disallowUserControlBgUsageFix()) {
+ setBgUsageAppOp(packages, pmi, user, appOpsManager);
}
- final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
- resolveUsers(userId).forEach(
- user -> setBgUsageAppOp(packages, pmi, user, appOpsManager));
+ if (Flags.disallowUserControlStoppedStateFix()) {
+ for (String packageName : packages) {
+ pmi.setPackageStoppedState(packageName, false, user);
+ }
+ }
}
});
return true;
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index b155829..6499556 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -2046,8 +2046,20 @@
writer.println("Unknown app ID $appId.")
}
}
+ } else if (args[0] == "--package" && args.size == 2) {
+ val packageName = args[1]
+ service.getState {
+ val packageState = state.externalState.packageStates[packageName]
+ if (packageState != null) {
+ writer.dumpAppIdState(packageState.appId, state, indexedSetOf(packageName))
+ } else {
+ writer.println("Unknown package $packageName.")
+ }
+ }
} else {
- writer.println("Usage: dumpsys permission [--app-id APP_ID]")
+ writer.println(
+ "Usage: dumpsys permissionmgr [--app-id <APP_ID>] [--package <PACKAGE_NAME>]"
+ )
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 80f38eb..e5685c7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1790,6 +1790,7 @@
verify(mHolder.animator).animateTo(eq(brightness),
/* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
/* ignoreAnimationLimits= */ anyBoolean());
+ verify(mHolder.brightnessSetting).setBrightness(brightness);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
new file mode 100644
index 0000000..7f2327aa
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.AttributionSource;
+import android.os.Process;
+import android.permission.PermissionManager;
+import android.permission.flags.Flags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.testing.TestableContext;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Objects;
+
+@RunWith(AndroidJUnit4.class)
+public class AppOpsDeviceAwareServiceTest {
+
+ @Rule
+ public final TestableContext mContext =
+ new TestableContext(InstrumentationRegistry.getInstrumentation().getTargetContext());
+
+ @Rule
+ public VirtualDeviceRule virtualDeviceRule =
+ VirtualDeviceRule.withAdditionalPermissions(
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ Manifest.permission.GET_APP_OPS_STATS);
+
+ private static final String ATTRIBUTION_TAG_1 = "attributionTag1";
+ private static final String ATTRIBUTION_TAG_2 = "attributionTag2";
+ private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+ private final PermissionManager mPermissionManager =
+ mContext.getSystemService(PermissionManager.class);
+
+ private VirtualDeviceManager.VirtualDevice mVirtualDevice;
+
+ @Before
+ public void setUp() {
+ mVirtualDevice =
+ virtualDeviceRule.createManagedVirtualDevice(
+ new VirtualDeviceParams.Builder()
+ .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_CUSTOM)
+ .build());
+
+ mPermissionManager.grantRuntimePermission(
+ mContext.getOpPackageName(),
+ Manifest.permission.CAMERA,
+ mVirtualDevice.getPersistentDeviceId());
+
+ mPermissionManager.grantRuntimePermission(
+ mContext.getOpPackageName(),
+ Manifest.permission.CAMERA,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
+ @Test
+ public void noteProxyOp_proxyAppOnDefaultDevice() {
+ AppOpsManager.OpEventProxyInfo proxyInfo =
+ noteProxyOpWithDeviceId(TestableContext.DEVICE_ID_DEFAULT);
+ assertThat(proxyInfo.getDeviceId())
+ .isEqualTo(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED)
+ @Test
+ public void noteProxyOp_proxyAppOnRemoteDevice() {
+ AppOpsManager.OpEventProxyInfo proxyInfo =
+ noteProxyOpWithDeviceId(mVirtualDevice.getDeviceId());
+ assertThat(proxyInfo.getDeviceId()).isEqualTo(mVirtualDevice.getPersistentDeviceId());
+ }
+
+ private AppOpsManager.OpEventProxyInfo noteProxyOpWithDeviceId(int proxyAppDeviceId) {
+ AttributionSource proxiedAttributionSource =
+ new AttributionSource.Builder(Process.myUid())
+ .setPackageName(mContext.getOpPackageName())
+ .setAttributionTag(ATTRIBUTION_TAG_2)
+ .setDeviceId(mVirtualDevice.getDeviceId())
+ .build();
+
+ AttributionSource proxyAttributionSource =
+ new AttributionSource.Builder(Process.myUid())
+ .setPackageName(mContext.getOpPackageName())
+ .setAttributionTag(ATTRIBUTION_TAG_1)
+ .setDeviceId(proxyAppDeviceId)
+ .setNextAttributionSource(proxiedAttributionSource)
+ .build();
+
+ int mode = mAppOpsManager.noteProxyOp(OP_CAMERA, proxyAttributionSource, null, false);
+ assertThat(mode).isEqualTo(AppOpsManager.MODE_ALLOWED);
+
+ List<AppOpsManager.PackageOps> packagesOps =
+ mAppOpsManager.getPackagesForOps(
+ new String[] {AppOpsManager.OPSTR_CAMERA},
+ mVirtualDevice.getPersistentDeviceId());
+
+ AppOpsManager.PackageOps packageOps =
+ packagesOps.stream()
+ .filter(
+ pkg ->
+ Objects.equals(
+ pkg.getPackageName(), mContext.getOpPackageName()))
+ .findFirst()
+ .orElseThrow();
+
+ AppOpsManager.OpEntry opEntry =
+ packageOps.getOps().stream()
+ .filter(op -> op.getOp() == OP_CAMERA)
+ .findFirst()
+ .orElseThrow();
+
+ return opEntry.getLastProxyInfo(OP_FLAGS_ALL_TRUSTED);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java
index 0716a5c..3698d6f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.when;
import android.app.NotificationManager;
+import android.content.Context;
import android.content.Intent;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.UserHandle;
@@ -75,13 +76,15 @@
@Test
public void testFingerprintRegisterReceiver() {
initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FINGERPRINT);
- verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any());
+ verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any(),
+ eq(Context.RECEIVER_NOT_EXPORTED));
}
@Test
public void testFaceRegisterReceiver() {
initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FACE);
- verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any());
+ verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any(),
+ eq(Context.RECEIVER_NOT_EXPORTED));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 40ecaf1..7dd1847 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -42,6 +42,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.Bundle;
import android.os.HandlerThread;
import android.os.LocaleList;
import android.os.Process;
@@ -488,7 +489,7 @@
setUpPackageInstalled(pkgNameA);
- mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
+ mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, new Bundle());
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog
@@ -504,7 +505,7 @@
setUpPackageInstalled(pkgNameB);
- mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
+ mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, new Bundle());
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog
@@ -518,6 +519,66 @@
}
@Test
+ public void testRestore_appInstalledAfterSUW_restoresFromStage_ArchiveEnabled()
+ throws Exception {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ HashMap<String, LocalesInfo> pkgLocalesMap = new HashMap<>();
+ String pkgNameA = "com.android.myAppA";
+ String pkgNameB = "com.android.myAppB";
+ String langTagsA = "ru";
+ String langTagsB = "hi,fr";
+ LocalesInfo localesInfoA = new LocalesInfo(langTagsA, false);
+ LocalesInfo localesInfoB = new LocalesInfo(langTagsB, true);
+ pkgLocalesMap.put(pkgNameA, localesInfoA);
+ pkgLocalesMap.put(pkgNameB, localesInfoB);
+ writeTestPayload(out, pkgLocalesMap);
+ setUpPackageNotInstalled(pkgNameA);
+ setUpPackageNotInstalled(pkgNameB);
+ setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
+ setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
+ setUpPackageNamesForSp(new ArraySet<>());
+
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(Intent.EXTRA_ARCHIVAL, true);
+ mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, bundle);
+ mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, bundle);
+
+ mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+ verifyNothingRestored();
+
+ setUpPackageInstalled(pkgNameA);
+
+ mPackageMonitor.onPackageUpdateFinished(pkgNameA, DEFAULT_UID);
+
+ verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
+ LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+
+ mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameA, false, false);
+
+ verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
+
+ pkgLocalesMap.remove(pkgNameA);
+
+ verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
+
+ setUpPackageInstalled(pkgNameB);
+
+ mPackageMonitor.onPackageUpdateFinished(pkgNameB, DEFAULT_UID);
+
+ verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
+ LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+
+ mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameB, true, false);
+
+ verify(mMockSpEditor, times(1)).putStringSet(Integer.toString(DEFAULT_USER_ID),
+ new ArraySet<>(Arrays.asList(pkgNameB)));
+ checkStageDataDoesNotExist(DEFAULT_USER_ID);
+ }
+
+ @Test
public void testRestore_appInstalledAfterSUWAndLocalesAlreadySet_restoresNothing()
throws Exception {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -535,7 +596,7 @@
setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.forLanguageTags("hi,mr"));
- mPackageMonitor.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+ mPackageMonitor.onPackageAddedWithExtras(DEFAULT_PACKAGE_NAME, DEFAULT_UID, new Bundle());
// Since locales are already set, we should not restore anything for it.
verifyNothingRestored();
@@ -612,7 +673,7 @@
DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
setUpPackageInstalled(pkgNameA);
- mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
+ mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, new Bundle());
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(
pkgNameA, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsA), false,
@@ -627,7 +688,7 @@
DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
setUpPackageInstalled(pkgNameB);
- mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
+ mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, new Bundle());
verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameB), anyInt(),
any(), anyBoolean(), anyInt());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 5e2fe6a..2d672b8 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5969,6 +5969,8 @@
assertThat(captor.getValue().getNotification().flags
& FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+ assertThat(captor.getValue().getNotification().flags
+ & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
assertThat(captor.getValue().shouldPostSilently()).isTrue();
}
@@ -8798,6 +8800,8 @@
assertThat(captor.getValue().getNotification().flags
& FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+ assertThat(captor.getValue().getNotification().flags
+ & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE);
assertThat(captor.getValue().shouldPostSilently()).isTrue();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 44d1b54..87395a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -113,15 +113,10 @@
import android.metrics.LogMaker;
import android.os.Binder;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.ArraySet;
-import android.util.DisplayMetrics;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
@@ -131,7 +126,6 @@
import android.view.ISystemGestureExclusionListener;
import android.view.IWindowManager;
import android.view.InsetsState;
-import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -151,7 +145,6 @@
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.utils.WmDisplayCutout;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -178,10 +171,6 @@
@RunWith(WindowTestRunner.class)
public class DisplayContentTests extends WindowTestsBase {
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@SetupWindows(addAllCommonWindows = true)
@Test
public void testForAllWindows() {
@@ -514,44 +503,6 @@
assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
}
- /**
- * Tests tapping on a root task in different display results in window gaining focus.
- */
- @Test
- @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_REMOVE_POINTER_EVENT_TRACKING_IN_WM)
- public void testInputEventBringsCorrectDisplayInFocus() {
- DisplayContent dc0 = mWm.getDefaultDisplayContentLocked();
- // Create a second display
- final DisplayContent dc1 = createNewDisplay();
-
- // Add root task with activity.
- final Task rootTask0 = createTask(dc0);
- final Task task0 = createTaskInRootTask(rootTask0, 0 /* userId */);
- final ActivityRecord activity = createNonAttachedActivityRecord(dc0);
- task0.addChild(activity, 0);
- dc0.configureDisplayPolicy();
- assertNotNull(dc0.mTapDetector);
-
- final Task rootTask1 = createTask(dc1);
- final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */);
- final ActivityRecord activity1 = createNonAttachedActivityRecord(dc0);
- task1.addChild(activity1, 0);
- dc1.configureDisplayPolicy();
- assertNotNull(dc1.mTapDetector);
-
- // tap on primary display.
- tapOnDisplay(dc0);
- // Check focus is on primary display.
- assertEquals(mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus,
- dc0.findFocusedWindow());
-
- // Tap on secondary display.
- tapOnDisplay(dc1);
- // Check focus is on secondary.
- assertEquals(mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus,
- dc1.findFocusedWindow());
- }
-
@Test
public void testFocusedWindowMultipleDisplays() {
doTestFocusedWindowMultipleDisplays(false /* perDisplayFocusEnabled */, Q);
@@ -2959,33 +2910,4 @@
throw new RuntimeException(e);
}
}
-
- private void tapOnDisplay(final DisplayContent dc) {
- final DisplayMetrics dm = dc.getDisplayMetrics();
- final float x = dm.widthPixels / 2;
- final float y = dm.heightPixels / 2;
- final long downTime = SystemClock.uptimeMillis();
- final long eventTime = SystemClock.uptimeMillis() + 100;
- // sending ACTION_DOWN
- final MotionEvent downEvent = MotionEvent.obtain(
- downTime,
- downTime,
- MotionEvent.ACTION_DOWN,
- x,
- y,
- 0 /*metaState*/);
- downEvent.setDisplayId(dc.getDisplayId());
- dc.mTapDetector.onPointerEvent(downEvent);
-
- // sending ACTION_UP
- final MotionEvent upEvent = MotionEvent.obtain(
- downTime,
- eventTime,
- MotionEvent.ACTION_UP,
- x,
- y,
- 0 /*metaState*/);
- upEvent.setDisplayId(dc.getDisplayId());
- dc.mTapDetector.onPointerEvent(upEvent);
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index cda26f1..4fc222b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -95,10 +95,6 @@
}
@Override
- public void updatePointerIcon(float x, float y) throws RemoteException {
- }
-
- @Override
public void dispatchWindowShown() throws RemoteException {
}
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 3b9ee80..709f58d 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -169,12 +169,6 @@
localService.setDisplayViewports(viewports)
verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java))
verify(native).setPointerDisplayId(displayId)
-
- val x = 42f
- val y = 314f
- service.onPointerDisplayIdChanged(displayId, x, y)
- testLooper.dispatchNext()
- verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y)
}
@Test
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
index e3f84c1..f126000 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
@@ -24,6 +24,7 @@
import android.app.Activity;
import android.os.Bundle;
+import android.os.Process;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
@@ -47,8 +48,9 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Log.v(TAG, "Create MainActivity as user " + getUserId() + " on display "
- + getDisplayId());
+ Log.v(TAG, "Create MainActivity as user "
+ + Process.myUserHandle().getIdentifier() + " on display "
+ + getDisplay().getDisplayId());
setContentView(R.layout.main_activity);
mImm = getSystemService(InputMethodManager.class);
mEditor = requireViewById(R.id.edit_text);