Merge "Adding a logger to log long-press effect events" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 7a1add3..6b8baf8 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -99,6 +99,7 @@
"framework_graphics_flags_java_lib",
"hwui_flags_java_lib",
"libcore_exported_aconfig_flags_lib",
+ "libgui_flags_java_lib",
"power_flags_lib",
"sdk_sandbox_flags_lib",
"surfaceflinger_flags_java_lib",
@@ -1208,6 +1209,12 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "libgui_flags_java_lib",
+ aconfig_declarations: "libgui_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Content Capture
aconfig_declarations {
name: "android.view.contentcapture.flags-aconfig",
diff --git a/Android.bp b/Android.bp
index 6fa6650..7f4871f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -401,6 +401,7 @@
],
sdk_version: "core_platform",
static_libs: [
+ "aconfig_storage_reader_java",
"android.hardware.common.fmq-V1-java",
"bouncycastle-repackaged-unbundled",
"com.android.sysprop.foldlockbehavior",
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index c3fe031..d92351d 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1990,7 +1990,7 @@
}
}
if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
- if (!Flags.avoidIdleCheck()) {
+ if (!Flags.avoidIdleCheck() || mInjector.getBootPhase() >= PHASE_BOOT_COMPLETED) {
postCheckIdleStates(userId);
}
}
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 531537c..c83dd65 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -304,6 +304,12 @@
public boolean isTopActivityStyleFloating;
/**
+ * The last non-fullscreen bounds the task was launched in or resized to.
+ * @hide
+ */
+ public Rect lastNonFullscreenBounds;
+
+ /**
* The URI of the intent that generated the top-most activity opened using a URL.
* @hide
*/
@@ -450,6 +456,7 @@
&& Objects.equals(topActivity, that.topActivity)
&& isTopActivityTransparent == that.isTopActivityTransparent
&& isTopActivityStyleFloating == that.isTopActivityStyleFloating
+ && lastNonFullscreenBounds == this.lastNonFullscreenBounds
&& Objects.equals(capturedLink, that.capturedLink)
&& capturedLinkTimestamp == that.capturedLinkTimestamp
&& appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo);
@@ -522,6 +529,7 @@
displayAreaFeatureId = source.readInt();
isTopActivityTransparent = source.readBoolean();
isTopActivityStyleFloating = source.readBoolean();
+ lastNonFullscreenBounds = source.readTypedObject(Rect.CREATOR);
capturedLink = source.readTypedObject(Uri.CREATOR);
capturedLinkTimestamp = source.readLong();
appCompatTaskInfo = source.readTypedObject(AppCompatTaskInfo.CREATOR);
@@ -572,6 +580,7 @@
dest.writeInt(displayAreaFeatureId);
dest.writeBoolean(isTopActivityTransparent);
dest.writeBoolean(isTopActivityStyleFloating);
+ dest.writeTypedObject(lastNonFullscreenBounds, flags);
dest.writeTypedObject(capturedLink, flags);
dest.writeLong(capturedLinkTimestamp);
dest.writeTypedObject(appCompatTaskInfo, flags);
@@ -612,6 +621,7 @@
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " isTopActivityTransparent=" + isTopActivityTransparent
+ " isTopActivityStyleFloating=" + isTopActivityStyleFloating
+ + " lastNonFullscreenBounds=" + lastNonFullscreenBounds
+ " capturedLink=" + capturedLink
+ " capturedLinkTimestamp=" + capturedLinkTimestamp
+ " appCompatTaskInfo=" + appCompatTaskInfo
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 55278f6..19de793 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -117,3 +117,11 @@
description: "Enable high resolution scroll"
bug: "335160780"
}
+
+flag {
+ name: "camera_multiple_input_streams"
+ is_exported: true
+ namespace: "virtual_devices"
+ description: "Expose multiple surface for the virtual camera owner for different stream resolution"
+ bug: "341083465"
+}
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index f4e2a7e..62b3682 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -64,3 +64,13 @@
purpose: PURPOSE_FEATURE
}
}
+
+flag {
+ namespace: "haptics"
+ name: "throttle_vibration_params_requests"
+ description: "Control the frequency of vibration params requests to prevent overloading the vendor service"
+ bug: "355320860"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 634469d..cf329d3 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -164,6 +164,9 @@
float width, float height, float vecX, float vecY,
float maxStretchAmountX, float maxStretchAmountY, float childRelativeLeft,
float childRelativeTop, float childRelativeRight, float childRelativeBottom);
+ private static native void nativeSetEdgeExtensionEffect(long transactionObj, long nativeObj,
+ boolean leftEdge, boolean rightEdge,
+ boolean topEdge, boolean bottomEdge);
private static native void nativeSetTrustedOverlay(long transactionObj, long nativeObject,
int isTrustedOverlay);
private static native void nativeSetDropInputMode(
@@ -3513,6 +3516,19 @@
/**
* @hide
*/
+ public Transaction setEdgeExtensionEffect(SurfaceControl sc, int edge) {
+ checkPreconditions(sc);
+
+ nativeSetEdgeExtensionEffect(
+ mNativeObject, sc.mNativeObject,
+ (edge & WindowInsets.Side.LEFT) != 0, (edge & WindowInsets.Side.RIGHT) != 0,
+ (edge & WindowInsets.Side.TOP) != 0, (edge & WindowInsets.Side.BOTTOM) != 0);
+ return this;
+ }
+
+ /**
+ * @hide
+ */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
checkPreconditions(sc);
@@ -4882,4 +4898,5 @@
public static void notifyShutdown() {
nativeNotifyShutdown();
}
+
}
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index f3cde43..376e66f 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -19,6 +19,7 @@
import static android.view.accessibility.AccessibilityNodeInfo.FOCUS_ACCESSIBILITY;
+import android.annotation.Nullable;
import android.os.Build;
import android.os.SystemClock;
import android.util.ArraySet;
@@ -48,6 +49,8 @@
private boolean mEnabled = true;
+ private final SparseArray<String> mWindowIdToEventSourceClassName = new SparseArray<>();
+
/**
* {@link AccessibilityEvent} types that are critical for the cache to stay up to date
*
@@ -273,8 +276,11 @@
clearSubTreeLocked(event.getWindowId(), event.getSourceNodeId());
} break;
- case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
+ case AccessibilityEvent.TYPE_WINDOWS_CHANGED: {
mValidWindowCacheTimeStamp = event.getEventTime();
+ if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_REMOVED) {
+ mWindowIdToEventSourceClassName.remove(event.getWindowId());
+ }
if (event.getWindowChanges()
== AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) {
// Don't need to clear all cache. Unless the changes are related to
@@ -282,8 +288,15 @@
clearWindowCacheLocked();
break;
}
+ clear();
+ }
+ break;
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
mValidWindowCacheTimeStamp = event.getEventTime();
+ if (event.getContentChangeTypes() == 0 && event.getClassName() != null) {
+ mWindowIdToEventSourceClassName.put(event.getWindowId(),
+ event.getClassName().toString());
+ }
clear();
} break;
}
@@ -907,6 +920,12 @@
}
}
+ /** Returns the source class associated with the window with the given id. */
+ @Nullable
+ public String getEventSourceClassName(int windowId) {
+ return mWindowIdToEventSourceClassName.get(windowId);
+ }
+
// Layer of indirection included to break dependency chain for testing
public static class AccessibilityNodeRefresher {
/** Refresh the given AccessibilityNodeInfo object. */
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 09306c7..2af935d 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -28,6 +28,7 @@
import android.os.SystemProperties;
import android.util.AttributeSet;
import android.util.TypedValue;
+import android.view.WindowInsets;
import dalvik.system.CloseGuard;
@@ -881,12 +882,13 @@
}
/**
- * @return if a window animation has outsets applied to it.
+ * @return the edges to which outsets can be applied to
*
* @hide
*/
- public boolean hasExtension() {
- return false;
+ @WindowInsets.Side.InsetsSide
+ public int getExtensionEdges() {
+ return 0x0;
}
/**
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 5aaa994..bbdc9d0 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -21,6 +21,7 @@
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
+import android.view.WindowInsets;
import java.util.ArrayList;
import java.util.List;
@@ -540,12 +541,12 @@
/** @hide */
@Override
- public boolean hasExtension() {
+ @WindowInsets.Side.InsetsSide
+ public int getExtensionEdges() {
+ int edge = 0x0;
for (Animation animation : mAnimations) {
- if (animation.hasExtension()) {
- return true;
- }
+ edge |= animation.getExtensionEdges();
}
- return false;
+ return edge;
}
}
diff --git a/core/java/android/view/animation/ExtendAnimation.java b/core/java/android/view/animation/ExtendAnimation.java
index 210eb8a..ed047c7 100644
--- a/core/java/android/view/animation/ExtendAnimation.java
+++ b/core/java/android/view/animation/ExtendAnimation.java
@@ -20,6 +20,7 @@
import android.content.res.TypedArray;
import android.graphics.Insets;
import android.util.AttributeSet;
+import android.view.WindowInsets;
/**
* An animation that controls the outset of an object.
@@ -50,6 +51,8 @@
private float mToRightValue;
private float mToBottomValue;
+ private int mExtensionEdges = 0x0;
+
/**
* Constructor used when an ExtendAnimation is loaded from a resource.
*
@@ -151,9 +154,22 @@
/** @hide */
@Override
- public boolean hasExtension() {
- return mFromInsets.left < 0 || mFromInsets.top < 0 || mFromInsets.right < 0
- || mFromInsets.bottom < 0;
+ @WindowInsets.Side.InsetsSide
+ public int getExtensionEdges() {
+ mExtensionEdges = 0x0;
+ if (mFromLeftValue > 0 || mToLeftValue > 0) {
+ mExtensionEdges |= WindowInsets.Side.LEFT;
+ }
+ if (mFromRightValue > 0 || mToRightValue > 0) {
+ mExtensionEdges |= WindowInsets.Side.RIGHT;
+ }
+ if (mFromTopValue > 0 || mToTopValue > 0) {
+ mExtensionEdges |= WindowInsets.Side.TOP;
+ }
+ if (mFromBottomValue > 0 || mToBottomValue > 0) {
+ mExtensionEdges |= WindowInsets.Side.BOTTOM;
+ }
+ return mExtensionEdges;
}
@Override
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index ec4e3e9..3cfde87 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -30,6 +30,8 @@
import android.os.Parcelable;
import android.view.WindowManager;
+import com.android.window.flags.Flags;
+
/**
* A parcelable filter that can be used for rerouting transitions to a remote. This is a local
* representation so that the transition system doesn't need to make blocking queries over
@@ -183,6 +185,9 @@
public ComponentName mTopActivity;
public IBinder mLaunchCookie;
+ /** If non-null, requires the change to specifically have or not-have a custom animation. */
+ public Boolean mCustomAnimation = null;
+
public Requirement() {
}
@@ -196,6 +201,9 @@
mOrder = in.readInt();
mTopActivity = in.readTypedObject(ComponentName.CREATOR);
mLaunchCookie = in.readStrongBinder();
+ // 0: null, 1: false, 2: true
+ final int customAnimRaw = in.readInt();
+ mCustomAnimation = customAnimRaw == 0 ? null : Boolean.valueOf(customAnimRaw == 2);
}
/** Go through changes and find if at-least one change matches this filter */
@@ -237,6 +245,23 @@
if (!matchesCookie(change.getTaskInfo())) {
continue;
}
+ if (mCustomAnimation != null
+ // only applies to activity/task
+ && (change.getTaskInfo() != null
+ || change.getActivityComponent() != null)) {
+ final TransitionInfo.AnimationOptions opts =
+ Flags.moveAnimationOptionsToChange() ? change.getAnimationOptions()
+ : info.getAnimationOptions();
+ if (opts != null) {
+ boolean canActuallyOverride = change.getTaskInfo() == null
+ || opts.getOverrideTaskTransition();
+ if (mCustomAnimation != canActuallyOverride) {
+ continue;
+ }
+ } else if (mCustomAnimation) {
+ continue;
+ }
+ }
return true;
}
return false;
@@ -286,6 +311,8 @@
dest.writeInt(mOrder);
dest.writeTypedObject(mTopActivity, flags);
dest.writeStrongBinder(mLaunchCookie);
+ int customAnimRaw = mCustomAnimation == null ? 0 : (mCustomAnimation ? 2 : 1);
+ dest.writeInt(customAnimRaw);
}
@NonNull
@@ -327,6 +354,9 @@
out.append(" order=" + containerOrderToString(mOrder));
out.append(" topActivity=").append(mTopActivity);
out.append(" launchCookie=").append(mLaunchCookie);
+ if (mCustomAnimation != null) {
+ out.append(" customAnim=").append(mCustomAnimation.booleanValue());
+ }
out.append("}");
return out.toString();
}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 76989f9..725d496 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -158,17 +158,6 @@
}
flag {
- name: "keyguard_appear_transition"
- namespace: "windowing_frontend"
- description: "Add transition when keyguard appears"
- bug: "327970608"
- is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "get_dimmer_on_closing"
namespace: "windowing_frontend"
description: "Change check for when to ignore a closing task's dim"
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 9ce7658..0f53164 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -22,6 +22,7 @@
#include <android/graphics/properties.h>
#include <android/graphics/region.h>
#include <android/gui/BnWindowInfosReportedListener.h>
+#include <android/gui/EdgeExtensionParameters.h>
#include <android/gui/JankData.h>
#include <android/hardware/display/IDeviceProductInfoConstants.h>
#include <android/os/IInputConstants.h>
@@ -799,6 +800,20 @@
transaction->setStretchEffect(ctrl, stretch);
}
+static void nativeSetEdgeExtensionEffect(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObj, jboolean leftEdge, jboolean rightEdge,
+ jboolean topEdge, jboolean bottomEdge) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ auto* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObj);
+
+ auto effect = gui::EdgeExtensionParameters();
+ effect.extendLeft = leftEdge;
+ effect.extendRight = rightEdge;
+ effect.extendTop = topEdge;
+ effect.extendBottom = bottomEdge;
+ transaction->setEdgeExtensionEffect(ctrl, effect);
+}
+
static void nativeSetFlags(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jint flags, jint mask) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2340,6 +2355,8 @@
(void*)nativeSetBlurRegions },
{"nativeSetStretchEffect", "(JJFFFFFFFFFF)V",
(void*) nativeSetStretchEffect },
+ {"nativeSetEdgeExtensionEffect", "(JJZZZZ)V",
+ (void*) nativeSetEdgeExtensionEffect },
{"nativeSetShadowRadius", "(JJF)V",
(void*)nativeSetShadowRadius },
{"nativeSetFrameRate", "(JJFII)V",
diff --git a/core/proto/android/app/appexitinfo.proto b/core/proto/android/app/appexitinfo.proto
index e560a94..8ee7962 100644
--- a/core/proto/android/app/appexitinfo.proto
+++ b/core/proto/android/app/appexitinfo.proto
@@ -20,7 +20,7 @@
package android.app;
import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
+import "frameworks/proto_logging/stats/enums/app_shared/app_enums.proto";
/**
* An android.app.ApplicationExitInfo object.
diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto
index c137533..8de5458 100644
--- a/core/proto/android/app/appstartinfo.proto
+++ b/core/proto/android/app/appstartinfo.proto
@@ -20,7 +20,7 @@
package android.app;
import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
+import "frameworks/proto_logging/stats/enums/app_shared/app_enums.proto";
/**
* An android.app.ApplicationStartInfo object.
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 90069f1..58f39a9 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -35,7 +35,7 @@
import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
import "frameworks/base/core/proto/android/util/common.proto";
import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
+import "frameworks/proto_logging/stats/enums/app_shared/app_enums.proto";
option java_multiple_files = true;
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index 593bbc6..8fd5d71 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -26,7 +26,7 @@
import "frameworks/base/core/proto/android/providers/settings.proto";
import "frameworks/base/core/proto/android/server/wirelesschargerdetector.proto";
import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
+import "frameworks/proto_logging/stats/enums/app_shared/app_enums.proto";
import "frameworks/proto_logging/stats/enums/os/enums.proto";
import "frameworks/proto_logging/stats/enums/view/enums.proto";
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
index eebc578..ef7df59 100644
--- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
@@ -29,9 +29,9 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityTestActivity;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/text/BidiFormatterTest.java b/core/tests/coretests/src/android/text/BidiFormatterTest.java
index 312fb68..307c95b 100644
--- a/core/tests/coretests/src/android/text/BidiFormatterTest.java
+++ b/core/tests/coretests/src/android/text/BidiFormatterTest.java
@@ -20,8 +20,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java b/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java
index cca1ad3..81c4982 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java
@@ -23,8 +23,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
index 5939c06..5ff659b 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
@@ -23,8 +23,8 @@
import android.platform.test.annotations.Presubmit;
import android.text.method.OffsetMapping;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutTest.java b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
index 699243b..1036928 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
@@ -30,8 +30,8 @@
import android.text.style.ReplacementSpan;
import android.util.ArraySet;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/EmojiConsistencyTest.java b/core/tests/coretests/src/android/text/EmojiConsistencyTest.java
index c6e9e9c..72d09c8 100644
--- a/core/tests/coretests/src/android/text/EmojiConsistencyTest.java
+++ b/core/tests/coretests/src/android/text/EmojiConsistencyTest.java
@@ -18,8 +18,8 @@
import static junit.framework.Assert.assertEquals;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/EmojiTest.java b/core/tests/coretests/src/android/text/EmojiTest.java
index 0aeeb74..21f346e 100644
--- a/core/tests/coretests/src/android/text/EmojiTest.java
+++ b/core/tests/coretests/src/android/text/EmojiTest.java
@@ -22,8 +22,8 @@
import android.icu.lang.UCharacterDirection;
import android.icu.text.Bidi;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/LayoutBidiCursorPathTest.java b/core/tests/coretests/src/android/text/LayoutBidiCursorPathTest.java
index 96e7fb9..7728866 100644
--- a/core/tests/coretests/src/android/text/LayoutBidiCursorPathTest.java
+++ b/core/tests/coretests/src/android/text/LayoutBidiCursorPathTest.java
@@ -26,8 +26,8 @@
import android.view.KeyEvent;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index 98f8b7f..25f9cb7 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -42,8 +42,8 @@
import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.google.common.truth.Expect;
diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
index 02b67e2..921a6bd 100644
--- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
@@ -26,8 +26,8 @@
import android.graphics.text.MeasuredText;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/PackedIntVectorTest.java b/core/tests/coretests/src/android/text/PackedIntVectorTest.java
index ba15b92..e8d706d 100644
--- a/core/tests/coretests/src/android/text/PackedIntVectorTest.java
+++ b/core/tests/coretests/src/android/text/PackedIntVectorTest.java
@@ -20,8 +20,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpanColorsTest.java b/core/tests/coretests/src/android/text/SpanColorsTest.java
index 3d8d8f9..d2cb8c1 100644
--- a/core/tests/coretests/src/android/text/SpanColorsTest.java
+++ b/core/tests/coretests/src/android/text/SpanColorsTest.java
@@ -25,8 +25,8 @@
import android.text.style.ImageSpan;
import android.text.style.UnderlineSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
index 91b8c6a..b725133 100644
--- a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
+++ b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
@@ -25,8 +25,8 @@
import android.text.style.SubscriptSpan;
import android.text.style.UnderlineSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
index 9149f7b..a2952f6 100644
--- a/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
+++ b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
@@ -24,8 +24,8 @@
import android.text.style.QuoteSpan;
import android.text.style.UnderlineSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpannableTest.java b/core/tests/coretests/src/android/text/SpannableTest.java
index d248a1f..a3e6a78 100644
--- a/core/tests/coretests/src/android/text/SpannableTest.java
+++ b/core/tests/coretests/src/android/text/SpannableTest.java
@@ -21,8 +21,8 @@
import android.platform.test.annotations.Presubmit;
import android.test.MoreAsserts;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
index ca43733..3e2516f 100644
--- a/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
+++ b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
@@ -24,8 +24,8 @@
import android.text.style.QuoteSpan;
import android.text.style.UnderlineSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpannedTest.java b/core/tests/coretests/src/android/text/SpannedTest.java
index 3ab0755..e9a357c 100644
--- a/core/tests/coretests/src/android/text/SpannedTest.java
+++ b/core/tests/coretests/src/android/text/SpannedTest.java
@@ -26,8 +26,8 @@
import android.text.style.TextAppearanceSpan;
import android.text.style.TypefaceSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
index 32370b3e..3deda8c 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
@@ -21,8 +21,8 @@
import android.platform.test.annotations.Presubmit;
import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java b/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
index 4221ac2..bc7efe4 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
@@ -22,8 +22,8 @@
import android.text.Layout.Directions;
import android.text.StaticLayoutTest.LayoutBuilder;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index 0ebf03f..3541900 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -31,8 +31,8 @@
import android.text.style.LocaleSpan;
import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTextMeasuringTest.java b/core/tests/coretests/src/android/text/StaticLayoutTextMeasuringTest.java
index 0d42326..b32e94a 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTextMeasuringTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTextMeasuringTest.java
@@ -21,8 +21,8 @@
import android.text.Layout.Alignment;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/TextLayoutTest.java b/core/tests/coretests/src/android/text/TextLayoutTest.java
index 15fbc9e..1584bc3 100644
--- a/core/tests/coretests/src/android/text/TextLayoutTest.java
+++ b/core/tests/coretests/src/android/text/TextLayoutTest.java
@@ -18,8 +18,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java
index 8ae5669..2997853 100644
--- a/core/tests/coretests/src/android/text/TextLineTest.java
+++ b/core/tests/coretests/src/android/text/TextLineTest.java
@@ -30,10 +30,10 @@
import android.text.style.ReplacementSpan;
import android.text.style.TabStopSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/TextShaperTest.java b/core/tests/coretests/src/android/text/TextShaperTest.java
index 77b14e6..84112ae 100644
--- a/core/tests/coretests/src/android/text/TextShaperTest.java
+++ b/core/tests/coretests/src/android/text/TextShaperTest.java
@@ -21,8 +21,8 @@
import android.graphics.fonts.Font;
import android.graphics.fonts.FontFileUtil;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
index c4bcfd4..f552265 100644
--- a/core/tests/coretests/src/android/text/TextUtilsTest.java
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -34,9 +34,9 @@
import android.text.util.Rfc822Tokenizer;
import android.view.View;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.google.android.collect.Lists;
diff --git a/core/tests/coretests/src/android/text/VariationParserTest.java b/core/tests/coretests/src/android/text/VariationParserTest.java
index 0afe811..8e93dd4 100644
--- a/core/tests/coretests/src/android/text/VariationParserTest.java
+++ b/core/tests/coretests/src/android/text/VariationParserTest.java
@@ -22,8 +22,8 @@
import android.graphics.fonts.FontVariationAxis;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java
index 212cc44..59af6dd 100644
--- a/core/tests/coretests/src/android/text/format/DateFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java
@@ -25,8 +25,8 @@
import android.icu.text.DateFormatSymbols;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index 9750de3..a07d399 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -42,8 +42,8 @@
import android.icu.util.ULocale;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
index 381c051..47be893 100644
--- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
@@ -23,8 +23,8 @@
import android.os.LocaleList;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java
index 986cee5..555292e 100644
--- a/core/tests/coretests/src/android/text/format/FormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/FormatterTest.java
@@ -28,8 +28,8 @@
import android.text.format.Formatter.BytesResult;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
index 2337802..cd31950 100644
--- a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
@@ -36,8 +36,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java b/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java
index b605520..c8cb5f3 100644
--- a/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java
@@ -20,8 +20,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/text/format/TimeTest.java b/core/tests/coretests/src/android/text/format/TimeTest.java
index ac00411..6138ea1 100644
--- a/core/tests/coretests/src/android/text/format/TimeTest.java
+++ b/core/tests/coretests/src/android/text/format/TimeTest.java
@@ -24,9 +24,9 @@
import android.util.Log;
import android.util.TimeFormatException;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/method/BackspaceTest.java b/core/tests/coretests/src/android/text/method/BackspaceTest.java
index 19c2c61..a7ff244 100644
--- a/core/tests/coretests/src/android/text/method/BackspaceTest.java
+++ b/core/tests/coretests/src/android/text/method/BackspaceTest.java
@@ -24,8 +24,8 @@
import android.widget.TextView.BufferType;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java b/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
index 652622d..1e4024d 100644
--- a/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
+++ b/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
@@ -24,8 +24,8 @@
import android.widget.TextView.BufferType;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
index 9ef137b..2f336ab 100644
--- a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
+++ b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
@@ -27,9 +27,8 @@
import android.view.View;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
import org.junit.BeforeClass;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/method/WordIteratorTest.java b/core/tests/coretests/src/android/text/method/WordIteratorTest.java
index cc345f5..046496a 100644
--- a/core/tests/coretests/src/android/text/method/WordIteratorTest.java
+++ b/core/tests/coretests/src/android/text/method/WordIteratorTest.java
@@ -23,8 +23,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java b/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java
index a0d2f85..043960d0 100644
--- a/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java
+++ b/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java
@@ -24,8 +24,8 @@
import android.text.StaticLayout;
import android.text.TextPaint;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/util/LinkifyTest.java b/core/tests/coretests/src/android/text/util/LinkifyTest.java
index 107ecd7..52f3b2e 100644
--- a/core/tests/coretests/src/android/text/util/LinkifyTest.java
+++ b/core/tests/coretests/src/android/text/util/LinkifyTest.java
@@ -31,8 +31,8 @@
import android.widget.TextView;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index dd8cc6e..e5ad561 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -42,8 +42,8 @@
import android.view.Display;
import android.view.View;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import com.google.common.base.Throwables;
@@ -1053,6 +1053,28 @@
assertFalse(mAccessibilityCache.isNodeInCache(childInfo));
}
+ @Test
+ public void getEventSourceClassName_windowStateChangedThenRemoved() {
+ final String sourceActivityClassName = "com.example.SomeActivity";
+ final AccessibilityEvent windowStateChangedEvent = new AccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ final View mockView = getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1);
+ windowStateChangedEvent.setSource(mockView);
+ windowStateChangedEvent.setClassName(sourceActivityClassName);
+
+ mAccessibilityCache.onAccessibilityEvent(windowStateChangedEvent);
+ assertEquals(mAccessibilityCache.getEventSourceClassName(WINDOW_ID_1),
+ sourceActivityClassName);
+
+ final AccessibilityEvent windowRemovedEvent = new AccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOWS_CHANGED);
+ windowRemovedEvent.setWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED);
+ windowRemovedEvent.setSource(mockView);
+
+ mAccessibilityCache.onAccessibilityEvent(windowRemovedEvent);
+ assertNull(mAccessibilityCache.getEventSourceClassName(WINDOW_ID_1));
+ }
+
private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) {
AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain();
windowInfo.setId(windowId);
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
index ddc27aa..3b8f66a 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
@@ -23,7 +23,7 @@
import android.os.Parcel;
import android.view.Display;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
index 3e061d2..eb482f2e 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
@@ -26,8 +26,8 @@
import android.os.Bundle;
import android.os.RemoteException;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import libcore.util.EmptyArray;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index ce36ee0..82e3427 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -55,7 +55,7 @@
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.R;
import com.android.internal.accessibility.common.ShortcutConstants;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 3d4918b..a5137bdf 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -28,8 +28,8 @@
import android.util.ArraySet;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.CollectionUtils;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 9f5ed29..37625e2 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -67,8 +67,8 @@
import androidx.lifecycle.Lifecycle;
import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
index f01ac6f..8608f6c 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
@@ -27,7 +27,7 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Flags;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.accessibility.common.ShortcutConstants;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
index 9cac312..5339d91 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
@@ -41,8 +41,8 @@
import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityManager;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.accessibility.TestUtils;
import com.android.internal.accessibility.common.ShortcutConstants;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/util/AccessibilityUtilsTest.java b/core/tests/coretests/src/com/android/internal/accessibility/util/AccessibilityUtilsTest.java
index 58ab92a..f37ec9b 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/util/AccessibilityUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/util/AccessibilityUtilsTest.java
@@ -33,7 +33,7 @@
import android.text.SpannableString;
import android.text.style.LocaleSpan;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 8d30db6..86e0f14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -146,6 +146,11 @@
/** To be overridden by subclasses to adjust the animation surface change. */
void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
// Update the surface position and alpha.
+ if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
+ && mAnimation.getExtensionEdges() != 0) {
+ t.setEdgeExtensionEffect(mLeash, mAnimation.getExtensionEdges());
+ }
+
mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
t.setAlpha(mLeash, mTransformation.getAlpha());
@@ -165,7 +170,7 @@
if (!cropRect.intersect(mWholeAnimationBounds)) {
// Hide the surface when it is outside of the animation area.
t.setAlpha(mLeash, 0);
- } else if (mAnimation.hasExtension()) {
+ } else if (mAnimation.getExtensionEdges() != 0) {
// Allow the surface to be shown in its original bounds in case we want to use edge
// extensions.
cropRect.union(mContentBounds);
@@ -180,6 +185,7 @@
@CallSuper
void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
onAnimationUpdate(t, mAnimation.getDuration());
+ t.setEdgeExtensionEffect(mLeash, /* edge */ 0);
}
final long getDurationHint() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 5696a54..d2cef4b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -144,8 +144,10 @@
// ending states.
prepareForJumpCut(info, startTransaction);
} else {
- addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
- postStartTransactionCallbacks, adapters);
+ if (!com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
+ addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
+ postStartTransactionCallbacks, adapters);
+ }
addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
duration = Math.max(duration, adapter.getDurationHint());
@@ -341,7 +343,7 @@
@NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
final Animation animation = adapter.mAnimation;
- if (!animation.hasExtension()) {
+ if (animation.getExtensionEdges() == 0) {
continue;
}
if (adapter.mChange.hasFlags(FLAG_TRANSLUCENT)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index df3803d..999ab95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -416,6 +416,17 @@
// location now.
mSpringingToTouch = false;
+ // Boost the velocityX if it's zero to forcefully push it towards the nearest edge.
+ // We don't simply change the xEndValue below since the PhysicsAnimator would rely on the
+ // same velocityX to find out which edge to snap to.
+ if (velocityX == 0) {
+ final int motionCenterX = mPipBoundsState
+ .getMotionBoundsState().getBoundsInMotion().centerX();
+ final int displayCenterX = mPipBoundsState
+ .getDisplayBounds().centerX();
+ velocityX = (motionCenterX < displayCenterX) ? -0.001f : 0.001f;
+ }
+
mTemporaryBoundsPhysicsAnimator
.spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig)
.spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index ea02de9d..e1e072a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -416,6 +416,17 @@
// location now.
mSpringingToTouch = false;
+ // Boost the velocityX if it's zero to forcefully push it towards the nearest edge.
+ // We don't simply change the xEndValue below since the PhysicsAnimator would rely on the
+ // same velocityX to find out which edge to snap to.
+ if (velocityX == 0) {
+ final int motionCenterX = mPipBoundsState
+ .getMotionBoundsState().getBoundsInMotion().centerX();
+ final int displayCenterX = mPipBoundsState
+ .getDisplayBounds().centerX();
+ velocityX = (motionCenterX < displayCenterX) ? -0.001f : 0.001f;
+ }
+
mTemporaryBoundsPhysicsAnimator
.spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig)
.spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 7784784..d8c8c60 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -502,7 +502,8 @@
backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a,
backgroundColorForTransition);
- if (!isTask && a.hasExtension()) {
+ if (!com.android.graphics.libgui.flags.Flags.edgeExtensionShader() && !isTask
+ && a.getExtensionEdges() != 0) {
if (!TransitionUtil.isOpeningType(mode)) {
// Can screenshot now (before startTransaction is applied)
edgeExtendWindow(change, a, startTransaction, finishTransaction);
@@ -512,6 +513,8 @@
postStartTransactionCallbacks
.add(t -> edgeExtendWindow(change, a, t, finishTransaction));
}
+ } else if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
+ finishTransaction.setEdgeExtensionEffect(change.getLeash(), /* edge */ 0);
}
final Rect clipRect = TransitionUtil.isClosingType(mode)
@@ -1008,6 +1011,10 @@
Point position, float cornerRadius, @Nullable Rect immutableClipRect) {
tmpTransformation.clear();
anim.getTransformation(time, tmpTransformation);
+ if (anim.getExtensionEdges() != 0
+ && com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
+ t.setEdgeExtensionEffect(leash, anim.getExtensionEdges());
+ }
if (position != null) {
tmpTransformation.getMatrix().postTranslate(position.x, position.y);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 409b877..81e6d07 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -442,6 +442,27 @@
}
@Test
+ public void testTransitionFilterAnimOverride() {
+ TransitionFilter filter = new TransitionFilter();
+ filter.mRequirements =
+ new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
+ filter.mRequirements[0].mCustomAnimation = true;
+ filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+
+ final RunningTaskInfo taskInf = createTaskInfo(1);
+ final TransitionInfo openTask = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, taskInf).build();
+ assertFalse(filter.matches(openTask));
+
+ final TransitionInfo.AnimationOptions overOpts =
+ TransitionInfo.AnimationOptions.makeCustomAnimOptions("pakname", 0, 0, 0, true);
+ final TransitionInfo openTaskOpts = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, taskInf).build();
+ openTaskOpts.getChanges().get(0).setAnimationOptions(overOpts);
+ assertTrue(filter.matches(openTaskOpts));
+ }
+
+ @Test
public void testRegisteredRemoteTransition() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
index e78b8a7..99d5891 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
@@ -62,6 +62,9 @@
/** Whether the device is in audio sharing. */
val inAudioSharing: Flow<Boolean>
+ /** The primary headset groupId in audio sharing. */
+ val primaryGroupId: StateFlow<Int>
+
/** The secondary headset groupId in audio sharing. */
val secondaryGroupId: StateFlow<Int>
@@ -109,6 +112,16 @@
awaitClose { contentResolver.unregisterContentObserver(callback) }
}
+ override val primaryGroupId: StateFlow<Int> =
+ primaryChange
+ .map { BluetoothUtils.getPrimaryGroupIdForBroadcast(contentResolver) }
+ .onStart { emit(BluetoothUtils.getPrimaryGroupIdForBroadcast(contentResolver)) }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ BluetoothUtils.getPrimaryGroupIdForBroadcast(contentResolver))
+
override val secondaryGroupId: StateFlow<Int> =
merge(
btManager.profileManager.leAudioBroadcastAssistantProfile
@@ -121,7 +134,7 @@
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
}
.map { getSecondaryGroupId() },
- primaryChange.map { getSecondaryGroupId() })
+ primaryGroupId.map { getSecondaryGroupId() })
.onStart { emit(getSecondaryGroupId()) }
.flowOn(backgroundCoroutineContext)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), getSecondaryGroupId())
@@ -193,6 +206,8 @@
class AudioSharingRepositoryEmptyImpl : AudioSharingRepository {
override val inAudioSharing: Flow<Boolean> = flowOf(false)
+ override val primaryGroupId: StateFlow<Int> =
+ MutableStateFlow(BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
override val secondaryGroupId: StateFlow<Int> =
MutableStateFlow(BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
override val volumeMap: StateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap())
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
index 94595d3..c54a2e4 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
@@ -131,6 +131,10 @@
`when`(deviceManager.findDevice(device2)).thenReturn(cachedDevice2)
`when`(receiveState.bisSyncState).thenReturn(arrayListOf(TEST_RECEIVE_STATE_CONTENT))
`when`(assistant.getAllSources(any())).thenReturn(listOf(receiveState))
+ Settings.Secure.putInt(
+ contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID_INVALID)
underTest =
AudioSharingRepositoryImpl(
contentResolver,
@@ -156,6 +160,22 @@
}
@Test
+ fun primaryGroupIdChange_emitValues() {
+ testScope.runTest {
+ val groupIds = mutableListOf<Int?>()
+ underTest.primaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+ triggerContentObserverChange()
+ runCurrent()
+
+ Truth.assertThat(groupIds)
+ .containsExactly(
+ TEST_GROUP_ID_INVALID,
+ TEST_GROUP_ID2)
+ }
+ }
+
+ @Test
fun secondaryGroupIdChange_emitValues() {
testScope.runTest {
val groupIds = mutableListOf<Int?>()
@@ -217,7 +237,7 @@
fun setSecondaryVolume_setValue() {
testScope.runTest {
Settings.Secure.putInt(
- context.contentResolver,
+ contentResolver,
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
TEST_GROUP_ID2)
`when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
@@ -248,7 +268,7 @@
private fun triggerSourceAdded() {
verify(assistant).registerServiceCallBack(any(), assistantCallbackCaptor.capture())
Settings.Secure.putInt(
- context.contentResolver,
+ contentResolver,
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
TEST_GROUP_ID1)
`when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
@@ -259,7 +279,7 @@
verify(assistant).registerServiceCallBack(any(), assistantCallbackCaptor.capture())
`when`(assistant.allConnectedDevices).thenReturn(listOf(device1))
Settings.Secure.putInt(
- context.contentResolver,
+ contentResolver,
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
TEST_GROUP_ID1)
assistantCallbackCaptor.value.sourceRemoved(device2)
@@ -269,7 +289,7 @@
verify(eventManager).registerCallback(btCallbackCaptor.capture())
`when`(assistant.allConnectedDevices).thenReturn(listOf(device1))
Settings.Secure.putInt(
- context.contentResolver,
+ contentResolver,
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
TEST_GROUP_ID1)
btCallbackCaptor.value.onProfileConnectionStateChanged(cachedDevice2, state, profile)
@@ -283,7 +303,7 @@
contentObserverCaptor.capture())
`when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
Settings.Secure.putInt(
- context.contentResolver,
+ contentResolver,
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
TEST_GROUP_ID2)
contentObserverCaptor.value.primaryChanged()
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index e4f27aa..27f4305 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1217,6 +1217,9 @@
namespace: "systemui"
description: "Enables fullscreen vertical swiping in hub mode to bring up and down the bouncer and shade"
bug: "340177049"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index b4e513c..e29e0fd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -69,8 +69,10 @@
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Check
@@ -92,6 +94,7 @@
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
@@ -145,6 +148,7 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.times
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.viewinterop.AndroidView
@@ -1000,7 +1004,9 @@
shape = RoundedCornerShape(68.adjustedDp, 34.adjustedDp, 68.adjustedDp, 34.adjustedDp)
) {
Column(
- modifier = Modifier.fillMaxSize().padding(vertical = 32.dp, horizontal = 50.dp),
+ modifier =
+ Modifier.fillMaxSize()
+ .padding(vertical = 32.adjustedDp, horizontal = 50.adjustedDp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
@@ -1009,47 +1015,57 @@
contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
modifier = Modifier.size(Dimensions.IconSize).clearAndSetSemantics {},
)
- Spacer(modifier = Modifier.size(6.dp))
+ Spacer(modifier = Modifier.size(6.adjustedDp))
Text(
text = stringResource(R.string.cta_label_to_edit_widget),
style = MaterialTheme.typography.titleLarge,
fontSize = nonScalableTextSize(22.dp),
lineHeight = nonScalableTextSize(28.dp),
+ modifier = Modifier.verticalScroll(rememberScrollState()).weight(1F)
)
- Spacer(modifier = Modifier.size(16.dp))
+ Spacer(modifier = Modifier.size(16.adjustedDp))
Row(
- modifier = Modifier.fillMaxWidth().height(56.dp),
- horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally),
+ modifier = Modifier.fillMaxWidth().height(56.adjustedDp),
+ horizontalArrangement =
+ Arrangement.spacedBy(16.adjustedDp, Alignment.CenterHorizontally),
) {
- OutlinedButton(
- modifier = Modifier.fillMaxHeight(),
- colors =
- ButtonDefaults.buttonColors(
- contentColor = colors.onPrimary,
- ),
- border = BorderStroke(width = 1.0.dp, color = colors.primaryContainer),
- contentPadding = PaddingValues(26.dp, 8.dp),
- onClick = viewModel::onDismissCtaTile,
+ CompositionLocalProvider(
+ LocalDensity provides
+ Density(
+ LocalDensity.current.density,
+ LocalDensity.current.fontScale.coerceIn(0f, 1.25f)
+ )
) {
- Text(
- text = stringResource(R.string.cta_tile_button_to_dismiss),
- fontSize = nonScalableTextSize(14.dp),
- )
- }
- Button(
- modifier = Modifier.fillMaxHeight(),
- colors =
- ButtonDefaults.buttonColors(
- containerColor = colors.primaryContainer,
- contentColor = colors.onPrimaryContainer,
- ),
- contentPadding = PaddingValues(26.dp, 8.dp),
- onClick = viewModel::onOpenWidgetEditor
- ) {
- Text(
- text = stringResource(R.string.cta_tile_button_to_open_widget_editor),
- fontSize = nonScalableTextSize(14.dp),
- )
+ OutlinedButton(
+ modifier = Modifier.fillMaxHeight().weight(1F),
+ colors =
+ ButtonDefaults.buttonColors(
+ contentColor = colors.onPrimary,
+ ),
+ border = BorderStroke(width = 1.0.dp, color = colors.primaryContainer),
+ onClick = viewModel::onDismissCtaTile,
+ contentPadding = PaddingValues(0.dp, 0.dp, 0.dp, 0.dp),
+ ) {
+ Text(
+ text = stringResource(R.string.cta_tile_button_to_dismiss),
+ fontSize = 14.sp,
+ )
+ }
+ Button(
+ modifier = Modifier.fillMaxHeight().weight(1F),
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = colors.primaryContainer,
+ contentColor = colors.onPrimaryContainer,
+ ),
+ onClick = viewModel::onOpenWidgetEditor,
+ contentPadding = PaddingValues(0.dp, 0.dp, 0.dp, 0.dp),
+ ) {
+ Text(
+ text = stringResource(R.string.cta_tile_button_to_open_widget_editor),
+ fontSize = 14.sp,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index c4970c5..76a7a10 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -290,6 +290,7 @@
val isCurrentGestureOverscroll =
viewModel.isCurrentGestureOverscroll.collectAsStateWithLifecycle(false)
val expansionFraction by viewModel.expandFraction.collectAsStateWithLifecycle(0f)
+ val shadeToQsFraction by viewModel.shadeToQsFraction.collectAsStateWithLifecycle(0f)
val topPadding = dimensionResource(id = R.dimen.notification_side_paddings)
val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
@@ -385,14 +386,26 @@
modifier
.element(Notifications.Elements.NotificationScrim)
.offset {
- // if scrim is expanded while transitioning to Gone scene, increase the offset
- // in step with the transition so that it is 0 when it completes.
+ // if scrim is expanded while transitioning to Gone or QS scene, increase the
+ // offset in step with the corresponding transition so that it is 0 when it
+ // completes.
if (
scrimOffset.value < 0 &&
layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Gone) ||
layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
) {
IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
+ } else if (
+ scrimOffset.value < 0 &&
+ layoutState.isTransitioning(
+ from = Scenes.Shade,
+ to = Scenes.QuickSettings
+ )
+ ) {
+ IntOffset(
+ x = 0,
+ y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt()
+ )
} else {
IntOffset(x = 0, y = scrimOffset.value.roundToInt())
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 242e822..e2a6a55 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -35,6 +35,8 @@
import android.view.accessibility.AccessibilityManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCapture
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
@@ -70,6 +72,7 @@
import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -108,6 +111,7 @@
@Mock private lateinit var inflater: LayoutInflater
@Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var lazyViewCapture: kotlin.Lazy<ViewCapture>
@Mock private lateinit var accessibilityManager: AccessibilityManager
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@@ -192,7 +196,8 @@
UdfpsControllerOverlay(
context,
inflater,
- windowManager,
+ ViewCaptureAwareWindowManager(windowManager, lazyViewCapture,
+ isViewCaptureEnabled = false),
accessibilityManager,
statusBarStateController,
statusBarKeyguardViewManager,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 54e0725..d86890b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -65,12 +65,12 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
-import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -87,6 +87,7 @@
import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.dump.DumpManager;
@@ -118,6 +119,8 @@
import dagger.Lazy;
+import javax.inject.Provider;
+
import kotlinx.coroutines.CoroutineScope;
import org.junit.Before;
@@ -152,7 +155,7 @@
@Mock
private FingerprintManager mFingerprintManager;
@Mock
- private WindowManager mWindowManager;
+ private ViewCaptureAwareWindowManager mWindowManager;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
@@ -261,6 +264,8 @@
private Lazy<DeviceEntryUdfpsTouchOverlayViewModel> mDeviceEntryUdfpsTouchOverlayViewModel;
@Mock
private Lazy<DefaultUdfpsTouchOverlayViewModel> mDefaultUdfpsTouchOverlayViewModel;
+ @Mock
+ private Provider<CameraGestureHelper> mCameraGestureHelper;
@Before
public void setUp() {
@@ -269,7 +274,8 @@
mPowerRepository,
mock(FalsingCollector.class),
mock(ScreenOffAnimationController.class),
- mStatusBarStateController
+ mStatusBarStateController,
+ mCameraGestureHelper
);
mPowerRepository.updateWakefulness(
WakefulnessState.AWAKE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
similarity index 67%
rename from packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index bea0db6..a0928ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -18,6 +18,7 @@
import android.app.ActivityManager
import android.app.IActivityTaskManager
+import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.ContentResolver
import android.content.Intent
@@ -29,82 +30,73 @@
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
-import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class CameraGestureHelperTest : SysuiTestCase() {
- @Mock
- lateinit var centralSurfaces: CentralSurfaces
- @Mock
- lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
- @Mock
- lateinit var keyguardStateController: KeyguardStateController
- @Mock
- lateinit var packageManager: PackageManager
- @Mock
- lateinit var activityManager: ActivityManager
- @Mock
- lateinit var activityStarter: ActivityStarter
- @Mock
- lateinit var activityIntentHelper: ActivityIntentHelper
- @Mock
- lateinit var activityTaskManager: IActivityTaskManager
- @Mock
- lateinit var cameraIntents: CameraIntentsWrapper
- @Mock
- lateinit var contentResolver: ContentResolver
- @Mock
- lateinit var mSelectedUserInteractor: SelectedUserInteractor
+ @Mock lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock lateinit var keyguardStateController: KeyguardStateController
+ @Mock lateinit var packageManager: PackageManager
+ @Mock lateinit var activityManager: ActivityManager
+ @Mock lateinit var activityStarter: ActivityStarter
+ @Mock lateinit var activityIntentHelper: ActivityIntentHelper
+ @Mock lateinit var activityTaskManager: IActivityTaskManager
+ @Mock lateinit var cameraIntents: CameraIntentsWrapper
+ @Mock lateinit var contentResolver: ContentResolver
+ @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
+ @Mock lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock lateinit var lockscreenUserManager: NotificationLockscreenUserManager
private lateinit var underTest: CameraGestureHelper
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(cameraIntents.getSecureCameraIntent(anyInt())).thenReturn(
- Intent(CameraIntents.DEFAULT_SECURE_CAMERA_INTENT_ACTION)
- )
- whenever(cameraIntents.getInsecureCameraIntent(anyInt())).thenReturn(
- Intent(CameraIntents.DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
- )
+ whenever(cameraIntents.getSecureCameraIntent(any()))
+ .thenReturn(Intent(CameraIntents.DEFAULT_SECURE_CAMERA_INTENT_ACTION))
+ whenever(cameraIntents.getInsecureCameraIntent(any()))
+ .thenReturn(Intent(CameraIntents.DEFAULT_INSECURE_CAMERA_INTENT_ACTION))
prepare()
- underTest = CameraGestureHelper(
- context = mock(),
- centralSurfaces = centralSurfaces,
- keyguardStateController = keyguardStateController,
- statusBarKeyguardViewManager = statusBarKeyguardViewManager,
- packageManager = packageManager,
- activityManager = activityManager,
- activityStarter = activityStarter,
- activityIntentHelper = activityIntentHelper,
- activityTaskManager = activityTaskManager,
- cameraIntents = cameraIntents,
- contentResolver = contentResolver,
- uiExecutor = MoreExecutors.directExecutor(),
- selectedUserInteractor = mSelectedUserInteractor,
- )
+ underTest =
+ CameraGestureHelper(
+ context = mock(),
+ keyguardStateController = keyguardStateController,
+ statusBarKeyguardViewManager = statusBarKeyguardViewManager,
+ packageManager = packageManager,
+ activityManager = activityManager,
+ activityStarter = activityStarter,
+ activityIntentHelper = activityIntentHelper,
+ activityTaskManager = activityTaskManager,
+ cameraIntents = cameraIntents,
+ contentResolver = contentResolver,
+ uiExecutor = MoreExecutors.directExecutor(),
+ selectedUserInteractor = mSelectedUserInteractor,
+ devicePolicyManager = devicePolicyManager,
+ lockscreenUserManager = lockscreenUserManager,
+ )
}
/**
@@ -116,13 +108,13 @@
* @param isCameraAllowedByAdmin Whether the device administrator allows use of the camera app
* @param installedCameraAppCount The number of installed camera apps on the device
* @param isUsingSecureScreenLockOption Whether the user-controlled setting for Screen Lock is
- * set with a "secure" option that requires the user to provide some secret/credentials to be
- * able to unlock the device, for example "Face Unlock", "PIN", or "Password". Examples of
- * non-secure options are "None" and "Swipe"
+ * set with a "secure" option that requires the user to provide some secret/credentials to be
+ * able to unlock the device, for example "Face Unlock", "PIN", or "Password". Examples of
+ * non-secure options are "None" and "Swipe"
* @param isCameraActivityRunningOnTop Whether the camera activity is running at the top of the
- * most recent/current task of activities
+ * most recent/current task of activities
* @param isTaskListEmpty Whether there are no active activity tasks at all. Note that this is
- * treated as `false` if [isCameraActivityRunningOnTop] is set to `true`
+ * treated as `false` if [isCameraActivityRunningOnTop] is set to `true`
*/
private fun prepare(
isCameraAllowedByAdmin: Boolean = true,
@@ -131,7 +123,13 @@
isCameraActivityRunningOnTop: Boolean = false,
isTaskListEmpty: Boolean = false,
) {
- whenever(centralSurfaces.isCameraAllowedByAdmin).thenReturn(isCameraAllowedByAdmin)
+ whenever(lockscreenUserManager.getCurrentUserId()).thenReturn(1)
+ if (isCameraAllowedByAdmin) {
+ whenever(devicePolicyManager.getCameraDisabled(isNull(), any())).thenReturn(false)
+ whenever(keyguardStateController.isMethodSecure).thenReturn(false)
+ } else {
+ whenever(devicePolicyManager.getCameraDisabled(isNull(), any())).thenReturn(true)
+ }
whenever(activityIntentHelper.wouldLaunchResolverActivity(any(), anyInt()))
.thenReturn(installedCameraAppCount > 1)
@@ -141,30 +139,26 @@
.thenReturn(!isUsingSecureScreenLockOption)
if (installedCameraAppCount >= 1) {
- val resolveInfo = ResolveInfo().apply {
- this.activityInfo = ActivityInfo().apply {
- packageName = CAMERA_APP_PACKAGE_NAME
+ val resolveInfo =
+ ResolveInfo().apply {
+ this.activityInfo =
+ ActivityInfo().apply { packageName = CAMERA_APP_PACKAGE_NAME }
}
- }
- whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt())).thenReturn(
- resolveInfo
- )
+ whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(resolveInfo)
} else {
- whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt())).thenReturn(
- null
- )
+ whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(null)
}
when {
isCameraActivityRunningOnTop -> {
- val runningTaskInfo = ActivityManager.RunningTaskInfo().apply {
- topActivity = ComponentName(CAMERA_APP_PACKAGE_NAME, "cameraActivity")
- }
- whenever(activityManager.getRunningTasks(anyInt())).thenReturn(
- listOf(
- runningTaskInfo
- )
- )
+ val runningTaskInfo =
+ ActivityManager.RunningTaskInfo().apply {
+ topActivity = ComponentName(CAMERA_APP_PACKAGE_NAME, "cameraActivity")
+ }
+ whenever(activityManager.getRunningTasks(anyInt()))
+ .thenReturn(listOf(runningTaskInfo))
}
isTaskListEmpty -> {
whenever(activityManager.getRunningTasks(anyInt())).thenReturn(emptyList())
@@ -289,28 +283,28 @@
) {
val intentCaptor = KotlinArgumentCaptor(Intent::class.java)
if (isSecure && !moreThanOneCameraAppInstalled) {
- verify(activityTaskManager).startActivityAsUser(
- any(),
- any(),
- any(),
- intentCaptor.capture(),
- any(),
- any(),
- any(),
- anyInt(),
- anyInt(),
- any(),
- any(),
- anyInt()
- )
+ verify(activityTaskManager)
+ .startActivityAsUser(
+ isNull(),
+ isNull(),
+ isNull(),
+ intentCaptor.capture(),
+ isNull(),
+ isNull(),
+ isNull(),
+ anyInt(),
+ anyInt(),
+ isNull(),
+ any(),
+ anyInt()
+ )
} else {
verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
}
val intent = intentCaptor.value
assertThat(CameraIntents.isSecureCameraIntent(intent)).isEqualTo(isSecure)
- assertThat(intent.getIntExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, -1))
- .isEqualTo(source)
+ assertThat(intent.getIntExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, -1)).isEqualTo(source)
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 2bf50b3..91259a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -58,7 +58,6 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -78,7 +77,6 @@
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.statusbar.commandQueue
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
@@ -116,7 +114,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
- private val kosmos = testKosmos().apply { this.commandQueue = this.fakeCommandQueue }
+ private val kosmos = testKosmos()
private lateinit var underTest: DeviceEntryFaceAuthRepositoryImpl
@Mock private lateinit var faceManager: FaceManager
@@ -162,7 +160,6 @@
private val displayStateInteractor = kosmos.displayStateInteractor
private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
private val displayRepository = kosmos.displayRepository
- private val fakeCommandQueue = kosmos.fakeCommandQueue
private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
private lateinit var featureFlags: FakeFeatureFlags
@@ -572,9 +569,7 @@
bouncerRepository.setAlternateVisible(false)
// Keyguard is occluded when secure camera is active.
keyguardRepository.setKeyguardOccluded(true)
- fakeCommandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
- }
+ keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
}
}
@@ -589,9 +584,7 @@
assertThat(canFaceAuthRun()).isTrue()
// launch secure camera
- fakeCommandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
- }
+ keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
assertThat(canFaceAuthRun()).isFalse()
@@ -870,9 +863,7 @@
bouncerRepository.setAlternateVisible(false)
// Keyguard is occluded when secure camera is active.
keyguardRepository.setKeyguardOccluded(true)
- fakeCommandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
- }
+ keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
index 5115f5a..3b8ffcd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
@@ -173,6 +173,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun transitionToGone_whenOpeningGlanceableHubEditMode() =
testScope.runTest {
kosmos.fakeKeyguardBouncerRepository.setAlternateVisible(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 7906a82..fc827a14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -32,7 +32,7 @@
import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
+import com.android.systemui.keyguard.shared.model.CameraLaunchType
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -47,7 +47,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -87,31 +86,17 @@
val cameraLaunchSource = collectLastValue(flow)
runCurrent()
- commandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)
- }
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.WIGGLE)
+ underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+ assertThat(cameraLaunchSource()!!.type).isEqualTo(CameraLaunchType.POWER_DOUBLE_TAP)
- commandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
- )
- }
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.POWER_DOUBLE_TAP)
+ underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)
+ assertThat(cameraLaunchSource()!!.type).isEqualTo(CameraLaunchType.WIGGLE)
- commandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)
- }
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.LIFT_TRIGGER)
+ underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)
+ assertThat(cameraLaunchSource()!!.type).isEqualTo(CameraLaunchType.LIFT_TRIGGER)
- commandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE
- )
- }
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.QUICK_AFFORDANCE)
-
- flow.onCompletion { assertThat(commandQueue.callbackCount()).isEqualTo(0) }
+ underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ assertThat(cameraLaunchSource()!!.type).isEqualTo(CameraLaunchType.QUICK_AFFORDANCE)
}
@Test
@@ -121,11 +106,7 @@
val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
runCurrent()
- commandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
- )
- }
+ underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
assertThat(secureCameraActive()).isTrue()
@@ -146,11 +127,7 @@
val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
runCurrent()
- commandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
- )
- }
+ underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
assertThat(secureCameraActive()).isTrue()
// Keyguard is showing and not occluded
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index d9708a4..9762fd8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
-import android.app.StatusBarManager
+import android.app.StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
@@ -42,7 +42,6 @@
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.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
@@ -59,7 +58,6 @@
import com.android.systemui.power.domain.interactor.powerInteractor
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
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -93,13 +91,12 @@
private val kosmos =
testKosmos().apply {
fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
- this.commandQueue = fakeCommandQueue
}
private val testScope = kosmos.testScope
private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
- private var commandQueue = kosmos.fakeCommandQueue
private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private val transitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
private lateinit var featureFlags: FakeFeatureFlags
@@ -1724,11 +1721,7 @@
reset(transitionRepository)
// ...AND WHEN the camera gesture is detected quickly afterwards
- commandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
- )
- }
+ keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
runCurrent()
// THEN a transition from DOZING => OCCLUDED should occur
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
similarity index 76%
rename from packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
index 12c9eb9..53dec69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
@@ -21,22 +21,23 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.cameraGestureHelper
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.power.shared.model.WakeSleepReason
-import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
-import kotlin.test.assertEquals
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,6 +48,9 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class PowerInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val cameraGestureHelper = kosmos.cameraGestureHelper
private lateinit var underTest: PowerInteractor
private lateinit var repository: FakePowerRepository
@@ -66,33 +70,30 @@
falsingCollector,
screenOffAnimationController,
statusBarStateController,
+ { cameraGestureHelper },
)
+
+ whenever(cameraGestureHelper.canCameraGestureBeLaunched(any())).thenReturn(true)
}
@Test
fun isInteractive_screenTurnsOff() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
repository.setInteractive(true)
- var value: Boolean? = null
- val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
+ val isInteractive by collectLastValue(underTest.isInteractive)
repository.setInteractive(false)
-
- assertThat(value).isFalse()
- job.cancel()
+ assertThat(isInteractive).isFalse()
}
@Test
fun isInteractive_becomesInteractive() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
repository.setInteractive(false)
- var value: Boolean? = null
- val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
+ val isInteractive by collectLastValue(underTest.isInteractive)
repository.setInteractive(true)
-
- assertThat(value).isTrue()
- job.cancel()
+ assertThat(isInteractive).isTrue()
}
@Test
@@ -203,6 +204,23 @@
}
@Test
+ fun onCameraLaunchGestureDetected_isNotTrueWhenCannotLaunch() {
+ whenever(cameraGestureHelper.canCameraGestureBeLaunched(any())).thenReturn(false)
+ underTest.onStartedWakingUp(
+ PowerManager.WAKE_REASON_POWER_BUTTON,
+ /*powerButtonLaunchGestureTriggeredDuringSleep= */ false
+ )
+ underTest.onFinishedWakingUp()
+ underTest.onCameraLaunchGestureDetected()
+
+ assertThat(repository.wakefulness.value.internalWakefulnessState)
+ .isEqualTo(WakefulnessState.AWAKE)
+ assertThat(repository.wakefulness.value.lastWakeReason)
+ .isEqualTo(WakeSleepReason.POWER_BUTTON)
+ assertFalse(repository.wakefulness.value.powerButtonLaunchGestureTriggered)
+ }
+
+ @Test
fun onCameraLaunchGestureDetected_maintainsAllOtherState() {
underTest.onStartedWakingUp(
PowerManager.WAKE_REASON_POWER_BUTTON,
@@ -211,8 +229,10 @@
underTest.onFinishedWakingUp()
underTest.onCameraLaunchGestureDetected()
- assertEquals(WakefulnessState.AWAKE, repository.wakefulness.value.internalWakefulnessState)
- assertEquals(WakeSleepReason.POWER_BUTTON, repository.wakefulness.value.lastWakeReason)
+ assertThat(repository.wakefulness.value.internalWakefulnessState)
+ .isEqualTo(WakefulnessState.AWAKE)
+ assertThat(repository.wakefulness.value.lastWakeReason)
+ .isEqualTo(WakeSleepReason.POWER_BUTTON)
assertTrue(repository.wakefulness.value.powerButtonLaunchGestureTriggered)
}
@@ -221,21 +241,23 @@
underTest.onCameraLaunchGestureDetected()
// Ensure that the 'false' here does not clear the direct launch detection call earlier.
// This state should only be reset onStartedGoingToSleep.
- underTest.onFinishedGoingToSleep(/*powerButtonLaunchGestureTriggeredDuringSleep= */ false)
+ underTest.onFinishedGoingToSleep(/* powerButtonLaunchGestureTriggeredDuringSleep= */ false)
underTest.onStartedWakingUp(
PowerManager.WAKE_REASON_POWER_BUTTON,
/*powerButtonLaunchGestureTriggeredDuringSleep= */ false
)
underTest.onFinishedWakingUp()
- assertEquals(WakefulnessState.AWAKE, repository.wakefulness.value.internalWakefulnessState)
- assertEquals(WakeSleepReason.POWER_BUTTON, repository.wakefulness.value.lastWakeReason)
+ assertThat(repository.wakefulness.value.internalWakefulnessState)
+ .isEqualTo(WakefulnessState.AWAKE)
+ assertThat(repository.wakefulness.value.lastWakeReason)
+ .isEqualTo(WakeSleepReason.POWER_BUTTON)
assertTrue(repository.wakefulness.value.powerButtonLaunchGestureTriggered)
}
@Test
fun cameraLaunchDetectedOnGoingToSleep_stillTrue_ifGestureNotDetectedOnWakingUp() {
- underTest.onFinishedGoingToSleep(/*powerButtonLaunchGestureTriggeredDuringSleep= */ true)
+ underTest.onFinishedGoingToSleep(/* powerButtonLaunchGestureTriggeredDuringSleep= */ true)
// Ensure that the 'false' here does not clear the direct launch detection call earlier.
// This state should only be reset onStartedGoingToSleep.
underTest.onStartedWakingUp(
@@ -244,12 +266,10 @@
)
underTest.onFinishedWakingUp()
- assertEquals(WakefulnessState.AWAKE, repository.wakefulness.value.internalWakefulnessState)
- assertEquals(WakeSleepReason.POWER_BUTTON, repository.wakefulness.value.lastWakeReason)
+ assertThat(repository.wakefulness.value.internalWakefulnessState)
+ .isEqualTo(WakefulnessState.AWAKE)
+ assertThat(repository.wakefulness.value.lastWakeReason)
+ .isEqualTo(WakeSleepReason.POWER_BUTTON)
assertTrue(repository.wakefulness.value.powerButtonLaunchGestureTriggered)
}
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 636d5a7..4a7b887 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -46,6 +46,7 @@
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
@@ -89,7 +90,7 @@
@RunWithLooper(setAsMainLooper = true)
@SmallTest
public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
- @Mock private WindowManager mWindowManager;
+ @Mock private ViewCaptureAwareWindowManager mWindowManager;
@Mock private DozeParameters mDozeParameters;
@Spy private final NotificationShadeWindowView mNotificationShadeWindowView = spy(
new NotificationShadeWindowView(mContext, null));
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 6f09931..6f1bc7e 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
@@ -60,7 +60,6 @@
import com.android.systemui.res.R
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.shade.shadeTestUtil
-import com.android.systemui.statusbar.notification.NotificationUtils.interpolate
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -590,6 +589,43 @@
}
@Test
+ @DisableSceneContainer
+ fun boundsDoNotChangeWhileLockscreenToAodTransitionIsActive() =
+ testScope.runTest {
+ val bounds by collectLastValue(underTest.bounds)
+
+ // Start on lockscreen
+ showLockscreen()
+
+ keyguardInteractor.setNotificationContainerBounds(
+ NotificationContainerBounds(top = 1f, bottom = 1f)
+ )
+ assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 1f))
+
+ // Begin transition to AOD
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(LOCKSCREEN, AOD, 0f, TransitionState.STARTED)
+ )
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(LOCKSCREEN, AOD, 0.5f, TransitionState.RUNNING)
+ )
+
+ // Attempt to update bounds
+ keyguardInteractor.setNotificationContainerBounds(
+ NotificationContainerBounds(top = 5f, bottom = 5f)
+ )
+ // Bounds should not have moved
+ assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 1f))
+
+ // Transition is over, now move
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(LOCKSCREEN, AOD, 1f, TransitionState.FINISHED)
+ )
+ assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 5f, bottom = 5f))
+ }
+
+ @Test
@DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
@DisableSceneContainer
fun boundsOnLockscreenInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
@@ -820,54 +856,6 @@
@Test
@DisableSceneContainer
- fun updateBounds_fromKeyguardRoot() =
- testScope.runTest {
- val startProgress = 0f
- val startStep = TransitionStep(LOCKSCREEN, AOD, startProgress, TransitionState.STARTED)
- val boundsChangingProgress = 0.2f
- val boundsChangingStep =
- TransitionStep(LOCKSCREEN, AOD, boundsChangingProgress, TransitionState.RUNNING)
- val boundsInterpolatingProgress = 0.6f
- val boundsInterpolatingStep =
- TransitionStep(
- LOCKSCREEN,
- AOD,
- boundsInterpolatingProgress,
- TransitionState.RUNNING
- )
- val finishProgress = 1.0f
- val finishStep =
- TransitionStep(LOCKSCREEN, AOD, finishProgress, TransitionState.FINISHED)
-
- val bounds by collectLastValue(underTest.bounds)
- val top = 123f
- val bottom = 456f
-
- kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(startStep)
- runCurrent()
- kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsChangingStep)
- runCurrent()
- keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
-
- kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsInterpolatingStep)
- runCurrent()
- val adjustedProgress =
- (boundsInterpolatingProgress - boundsChangingProgress) /
- (1 - boundsChangingProgress)
- val interpolatedTop = interpolate(0f, top, adjustedProgress)
- val interpolatedBottom = interpolate(0f, bottom, adjustedProgress)
- assertThat(bounds)
- .isEqualTo(
- NotificationContainerBounds(top = interpolatedTop, bottom = interpolatedBottom)
- )
-
- kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep)
- runCurrent()
- assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom))
- }
-
- @Test
- @DisableSceneContainer
fun updateBounds_fromGone_withoutTransitions() =
testScope.runTest {
// Start step is already at 1.0
@@ -878,9 +866,9 @@
val top = 123f
val bottom = 456f
- kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(runningStep)
+ keyguardTransitionRepository.sendTransitionStep(runningStep)
runCurrent()
- kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep)
+ keyguardTransitionRepository.sendTransitionStep(finishStep)
runCurrent()
keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
index 142631e..a1fcfcd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
@@ -16,8 +16,10 @@
package com.android.systemui.volume.domain.interactor
+import android.media.AudioManager.STREAM_MUSIC
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -40,17 +42,57 @@
@Before
fun setUp() {
- with(kosmos) { underTest = audioSharingInteractor }
+ with(kosmos) {
+ with(audioSharingRepository) { setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME)) }
+ underTest = audioSharingInteractor
+ }
}
@Test
- fun volumeChanges_returnVolume() {
+ fun handlePrimaryGroupChange_nullVolume() {
with(kosmos) {
testScope.runTest {
- with(audioSharingRepository) {
- setSecondaryGroupId(TEST_GROUP_ID)
- setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME))
- }
+ with(audioSharingRepository) { setPrimaryGroupId(TEST_GROUP_ID_INVALID) }
+ val preMusicStream by
+ collectLastValue(
+ audioVolumeInteractor.getAudioStream(AudioStream(STREAM_MUSIC))
+ )
+ val preVolume = preMusicStream?.volume
+ runCurrent()
+ underTest.handlePrimaryGroupChange()
+ val musicStream by
+ collectLastValue(
+ audioVolumeInteractor.getAudioStream(AudioStream(STREAM_MUSIC))
+ )
+ runCurrent()
+
+ Truth.assertThat(musicStream?.volume).isEqualTo(preVolume)
+ }
+ }
+ }
+
+ @Test
+ fun handlePrimaryGroupChange_setStreamVolume() {
+ with(kosmos) {
+ testScope.runTest {
+ with(audioSharingRepository) { setPrimaryGroupId(TEST_GROUP_ID) }
+ underTest.handlePrimaryGroupChange()
+ val musicStream by
+ collectLastValue(
+ audioVolumeInteractor.getAudioStream(AudioStream(STREAM_MUSIC))
+ )
+ runCurrent()
+
+ Truth.assertThat(musicStream?.volume).isEqualTo(TEST_MUSIC_VOLUME)
+ }
+ }
+ }
+
+ @Test
+ fun secondaryGroupVolumeChanges_returnVolume() {
+ with(kosmos) {
+ testScope.runTest {
+ with(audioSharingRepository) { setSecondaryGroupId(TEST_GROUP_ID) }
val volume by collectLastValue(underTest.volume)
runCurrent()
@@ -60,13 +102,10 @@
}
@Test
- fun volumeChanges_returnNull() {
+ fun secondaryGroupVolumeChanges_returnNull() {
with(kosmos) {
testScope.runTest {
- with(audioSharingRepository) {
- setSecondaryGroupId(TEST_GROUP_ID_INVALID)
- setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME))
- }
+ with(audioSharingRepository) { setSecondaryGroupId(TEST_GROUP_ID_INVALID) }
val volume by collectLastValue(underTest.volume)
runCurrent()
@@ -76,7 +115,7 @@
}
@Test
- fun volumeChanges_returnDefaultVolume() {
+ fun secondaryGroupVolumeChanges_returnDefaultVolume() {
with(kosmos) {
testScope.runTest {
with(audioSharingRepository) {
@@ -94,7 +133,8 @@
private companion object {
const val TEST_GROUP_ID = 1
const val TEST_GROUP_ID_INVALID = -1
- const val TEST_VOLUME = 10
+ const val TEST_MUSIC_VOLUME = 10
+ const val TEST_VOLUME = 255
const val TEST_VOLUME_DEFAULT = 20
}
}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e56b638..28138a5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3214,9 +3214,6 @@
<!-- Provider Model: Default title of the mobile network in the mobile layout. [CHAR LIMIT=50] -->
<string name="mobile_data_settings_title">Mobile data</string>
- <!-- Provider Model: Summary text separator for preferences including a short description
- (eg. "Connected / 5G"). [CHAR LIMIT=50] -->
- <string name="preference_summary_default_combination"><xliff:g id="state" example="Connected">%1$s</xliff:g> / <xliff:g id="networkMode" example="LTE">%2$s</xliff:g></string>
<!-- Provider Model:
Summary indicating that a SIM has an active mobile data connection [CHAR LIMIT=50] -->
<string name="mobile_data_connection_active">Connected</string>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 3dd3758..5ffb9ab2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -56,13 +56,13 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
@@ -147,7 +147,7 @@
private final Execution mExecution;
private final FingerprintManager mFingerprintManager;
@NonNull private final LayoutInflater mInflater;
- private final WindowManager mWindowManager;
+ private final ViewCaptureAwareWindowManager mWindowManager;
private final DelayableExecutor mFgExecutor;
@NonNull private final Executor mBiometricExecutor;
@NonNull private final StatusBarStateController mStatusBarStateController;
@@ -693,7 +693,7 @@
@NonNull Execution execution,
@NonNull LayoutInflater inflater,
@Nullable FingerprintManager fingerprintManager,
- @NonNull WindowManager windowManager,
+ @NonNull ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
@NonNull StatusBarStateController statusBarStateController,
@Main DelayableExecutor fgExecutor,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -741,7 +741,7 @@
// The fingerprint manager is queried for UDFPS before this class is constructed, so the
// fingerprint manager should never be null.
mFingerprintManager = checkNotNull(fingerprintManager);
- mWindowManager = windowManager;
+ mWindowManager = viewCaptureAwareWindowManager;
mFgExecutor = fgExecutor;
mStatusBarStateController = statusBarStateController;
mKeyguardStateController = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index e03d160..1bac0bc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -44,6 +44,7 @@
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
import androidx.annotation.LayoutRes
import androidx.annotation.VisibleForTesting
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
@@ -94,7 +95,7 @@
constructor(
private val context: Context,
private val inflater: LayoutInflater,
- private val windowManager: WindowManager,
+ private val windowManager: ViewCaptureAwareWindowManager,
private val accessibilityManager: AccessibilityManager,
private val statusBarStateController: StatusBarStateController,
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index ecbd3f9..6757edb 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -19,6 +19,7 @@
import android.app.ActivityManager
import android.app.ActivityOptions
import android.app.IActivityTaskManager
+import android.app.admin.DevicePolicyManager
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
@@ -32,8 +33,8 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -45,9 +46,10 @@
* the camera).
*/
@SysUISingleton
-class CameraGestureHelper @Inject constructor(
+class CameraGestureHelper
+@Inject
+constructor(
private val context: Context,
- private val centralSurfaces: CentralSurfaces,
private val keyguardStateController: KeyguardStateController,
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
private val packageManager: PackageManager,
@@ -59,24 +61,25 @@
private val contentResolver: ContentResolver,
@Main private val uiExecutor: Executor,
private val selectedUserInteractor: SelectedUserInteractor,
+ private val devicePolicyManager: DevicePolicyManager,
+ private val lockscreenUserManager: NotificationLockscreenUserManager,
) {
- /**
- * Whether the camera application can be launched for the camera launch gesture.
- */
+ /** Whether the camera application can be launched for the camera launch gesture. */
fun canCameraGestureBeLaunched(statusBarState: Int): Boolean {
- if (!centralSurfaces.isCameraAllowedByAdmin) {
+ if (!isCameraAllowedByAdmin()) {
return false
}
- val resolveInfo: ResolveInfo? = packageManager.resolveActivityAsUser(
- getStartCameraIntent(selectedUserInteractor.getSelectedUserId()),
- PackageManager.MATCH_DEFAULT_ONLY,
- selectedUserInteractor.getSelectedUserId()
- )
+ val resolveInfo: ResolveInfo? =
+ packageManager.resolveActivityAsUser(
+ getStartCameraIntent(selectedUserInteractor.getSelectedUserId()),
+ PackageManager.MATCH_DEFAULT_ONLY,
+ selectedUserInteractor.getSelectedUserId()
+ )
val resolvedPackage = resolveInfo?.activityInfo?.packageName
return (resolvedPackage != null &&
- (statusBarState != StatusBarState.SHADE ||
- !activityManager.isInForeground(resolvedPackage)))
+ (statusBarState != StatusBarState.SHADE ||
+ !activityManager.isInForeground(resolvedPackage)))
}
/**
@@ -87,9 +90,11 @@
fun launchCamera(source: Int) {
val intent: Intent = getStartCameraIntent(selectedUserInteractor.getSelectedUserId())
intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source)
- val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
- intent, selectedUserInteractor.getSelectedUserId()
- )
+ val wouldLaunchResolverActivity =
+ activityIntentHelper.wouldLaunchResolverActivity(
+ intent,
+ selectedUserInteractor.getSelectedUserId()
+ )
if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
uiExecutor.execute {
// Normally an activity will set its requested rotation animation on its window.
@@ -101,7 +106,7 @@
val activityOptions = ActivityOptions.makeBasic()
activityOptions.setDisallowEnterPictureInPictureWhileLaunching(true)
activityOptions.rotationAnimationHint =
- WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+ WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
try {
activityTaskManager.startActivityAsUser(
null,
@@ -118,11 +123,7 @@
selectedUserInteractor.getSelectedUserId(true),
)
} catch (e: RemoteException) {
- Log.w(
- "CameraGestureHelper",
- "Unable to start camera activity",
- e
- )
+ Log.w("CameraGestureHelper", "Unable to start camera activity", e)
}
}
} else {
@@ -131,9 +132,6 @@
activityStarter.startActivity(intent, false /* dismissShade */)
}
- // Call this to make sure that the keyguard returns if the app that is being launched
- // crashes after a timeout.
- centralSurfaces.startLaunchTransitionTimeout()
// Call this to make sure the keyguard is ready to be dismissed once the next intent is
// handled by the OS (in our case it is the activity we started right above)
statusBarKeyguardViewManager.readyForKeyguardDone()
@@ -152,4 +150,17 @@
cameraIntents.getInsecureCameraIntent(userId)
}
}
+
+ private fun isCameraAllowedByAdmin(): Boolean {
+ if (devicePolicyManager.getCameraDisabled(null, lockscreenUserManager.getCurrentUserId())) {
+ return false
+ } else if (keyguardStateController.isShowing() && statusBarKeyguardViewManager.isSecure()) {
+ // Check if the admin has disabled the camera specifically for the keyguard
+ return (devicePolicyManager.getKeyguardDisabledFeatures(
+ null,
+ lockscreenUserManager.getCurrentUserId()
+ ) and DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0
+ }
+ return true
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt
index f649be2..b859cdc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt
@@ -20,6 +20,7 @@
import android.graphics.Paint
import android.graphics.PixelFormat
import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyboard.docking.ui.KeyboardDockingIndicationView
@@ -37,7 +38,7 @@
context: Context,
@Application private val applicationScope: CoroutineScope,
private val viewModel: KeyboardDockingIndicationViewModel,
- private val windowManager: WindowManager
+ private val windowManager: ViewCaptureAwareWindowManager,
) {
private val windowLayoutParams =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index ae751db..edf17c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -34,6 +34,7 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
@@ -237,6 +238,9 @@
/** Observable updated when keyguardDone should be called either now or soon. */
val keyguardDone: Flow<KeyguardDone>
+ /** Last camera launch detection event */
+ val onCameraLaunchDetected: MutableStateFlow<CameraLaunchSourceModel>
+
/**
* Emits after the keyguard is done animating away.
*
@@ -380,6 +384,8 @@
private val _keyguardAlpha = MutableStateFlow(1f)
override val keyguardAlpha = _keyguardAlpha.asStateFlow()
+ override val onCameraLaunchDetected = MutableStateFlow(CameraLaunchSourceModel())
+
override val panelAlpha: MutableStateFlow<Float> = MutableStateFlow(1f)
private val _clockShouldBeCentered = MutableStateFlow(true)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 046e79c..42490c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -23,10 +23,7 @@
import android.graphics.Point
import android.util.MathUtils
import com.android.app.animation.Interpolators
-import com.android.app.tracing.FlowTracing.tracedAwaitClose
-import com.android.app.tracing.FlowTracing.tracedConflatedCallbackFlow
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -35,9 +32,11 @@
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
+import com.android.systemui.keyguard.shared.model.CameraLaunchType
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
@@ -48,11 +47,8 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.notification.NotificationUtils.interpolate
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
-import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import javax.inject.Provider
@@ -69,9 +65,7 @@
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -87,7 +81,6 @@
@Inject
constructor(
private val repository: KeyguardRepository,
- private val commandQueue: CommandQueue,
powerInteractor: PowerInteractor,
bouncerRepository: KeyguardBouncerRepository,
configurationInteractor: ConfigurationInteractor,
@@ -102,55 +95,34 @@
// TODO(b/296118689): move to a repository
private val _notificationPlaceholderBounds = MutableStateFlow(NotificationContainerBounds())
- // When going to AOD, we interpolate bounds when receiving the new bounds
- // When going back to LS, we'll apply new bounds directly
- private val _nonSplitShadeNotifciationPlaceholderBounds =
- _notificationPlaceholderBounds.pairwise().flatMapLatest { (oldBounds, newBounds) ->
- val lastChangeStep = keyguardTransitionInteractor.transitionState.first()
- if (lastChangeStep.to == AOD) {
- keyguardTransitionInteractor.transitionState.map { step ->
- val startingProgress = lastChangeStep.value
- val progress = step.value
- if (step.to == AOD && progress >= startingProgress && startingProgress < 1f) {
- val adjustedProgress =
- ((progress - startingProgress) / (1F - startingProgress)).coerceIn(
- 0F,
- 1F
- )
- val top = interpolate(oldBounds.top, newBounds.top, adjustedProgress)
- val bottom =
- interpolate(
- oldBounds.bottom,
- newBounds.bottom,
- adjustedProgress.coerceIn(0F, 1F)
- )
- NotificationContainerBounds(top = top, bottom = bottom)
- } else {
- newBounds
- }
- }
- } else {
- flow { emit(newBounds) }
- }
- }
-
/** Bounds of the notification container. */
val notificationContainerBounds: StateFlow<NotificationContainerBounds> by lazy {
SceneContainerFlag.assertInLegacyMode()
- combine(
+ combineTransform(
_notificationPlaceholderBounds,
- _nonSplitShadeNotifciationPlaceholderBounds,
sharedNotificationContainerInteractor.get().configurationBasedDimensions,
- ) { bounds, nonSplitShadeBounds: NotificationContainerBounds, cfg ->
+ keyguardTransitionInteractor.isInTransition(
+ edge = Edge.create(from = LOCKSCREEN, to = AOD)
+ ),
+ ) { bounds, cfg, isTransitioningToAod ->
+ if (isTransitioningToAod) {
+ // Keep bounds stable during this transition, to prevent cases like smartspace
+ // popping in and adjusting the bounds. A prime example would be media playing,
+ // which then updates smartspace on transition to AOD
+ return@combineTransform
+ }
+
// We offset the placeholder bounds by the configured top margin to account for
// legacy placement behavior within notifications for splitshade.
- if (MigrateClocksToBlueprint.isEnabled) {
- if (cfg.useSplitShade) {
- bounds.copy(bottom = bounds.bottom - cfg.keyguardSplitShadeTopMargin)
- } else {
- nonSplitShadeBounds
- }
- } else bounds
+ emit(
+ if (MigrateClocksToBlueprint.isEnabled) {
+ if (cfg.useSplitShade) {
+ bounds.copy(bottom = bounds.bottom - cfg.keyguardSplitShadeTopMargin)
+ } else {
+ bounds
+ }
+ } else bounds
+ )
}
.stateIn(
scope = applicationScope,
@@ -198,22 +170,7 @@
/** Event for when the camera gesture is detected */
val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> =
- tracedConflatedCallbackFlow("KeyguardInteractor#onCameraLaunchDetected") {
- val callback =
- object : CommandQueue.Callbacks {
- override fun onCameraLaunchGestureDetected(source: Int) {
- trySendWithFailureLogging(
- cameraLaunchSourceIntToModel(source),
- TAG,
- "updated onCameraLaunchGestureDetected"
- )
- }
- }
-
- commandQueue.addCallback(callback)
-
- tracedAwaitClose("onCameraLaunchDetected") { commandQueue.removeCallback(callback) }
- }
+ repository.onCameraLaunchDetected.filter { it.type != CameraLaunchType.IGNORE }
/**
* Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
@@ -310,7 +267,7 @@
when {
isKeyguardVisible -> false
isPrimaryBouncerShowing -> false
- else -> cameraLaunchEvent == CameraLaunchSourceModel.POWER_DOUBLE_TAP
+ else -> cameraLaunchEvent.type == CameraLaunchType.POWER_DOUBLE_TAP
}
}
.onStart { emit(false) }
@@ -440,16 +397,15 @@
return repository.isKeyguardShowing()
}
- private fun cameraLaunchSourceIntToModel(value: Int): CameraLaunchSourceModel {
+ private fun cameraLaunchSourceIntToType(value: Int): CameraLaunchType {
return when (value) {
- StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE -> CameraLaunchSourceModel.WIGGLE
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE -> CameraLaunchType.WIGGLE
StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP ->
- CameraLaunchSourceModel.POWER_DOUBLE_TAP
- StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER ->
- CameraLaunchSourceModel.LIFT_TRIGGER
+ CameraLaunchType.POWER_DOUBLE_TAP
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER -> CameraLaunchType.LIFT_TRIGGER
StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE ->
- CameraLaunchSourceModel.QUICK_AFFORDANCE
- else -> throw IllegalArgumentException("Invalid CameraLaunchSourceModel value: $value")
+ CameraLaunchType.QUICK_AFFORDANCE
+ else -> throw IllegalArgumentException("Invalid CameraLaunchType value: $value")
}
}
@@ -508,6 +464,11 @@
fromLockscreenTransitionInteractor.get().dismissKeyguard()
}
+ fun onCameraLaunchDetected(source: Int) {
+ repository.onCameraLaunchDetected.value =
+ CameraLaunchSourceModel(type = cameraLaunchSourceIntToType(source))
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt
index 19baf77..c017651 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt
@@ -15,14 +15,8 @@
*/
package com.android.systemui.keyguard.shared.model
-/** Camera launch sources */
-enum class CameraLaunchSourceModel {
- /** Device is wiggled */
- WIGGLE,
- /** Power button has been double tapped */
- POWER_DOUBLE_TAP,
- /** Device has been lifted */
- LIFT_TRIGGER,
- /** Quick affordance button has been pressed */
- QUICK_AFFORDANCE,
-}
+/** Camera launch source, with type and time detected */
+data class CameraLaunchSourceModel(
+ val type: CameraLaunchType = CameraLaunchType.IGNORE,
+ val detectedTime: Long = System.currentTimeMillis(),
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchType.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchType.kt
new file mode 100644
index 0000000..984abbb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchType.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** Camera launch sources */
+enum class CameraLaunchType {
+ /** Models no value */
+ IGNORE,
+ /** Device is wiggled */
+ WIGGLE,
+ /** Power button has been double tapped */
+ POWER_DOUBLE_TAP,
+ /** Device has been lifted */
+ LIFT_TRIGGER,
+ /** Quick affordance button has been pressed */
+ QUICK_AFFORDANCE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index 9380d44..8d48c1d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -18,6 +18,7 @@
package com.android.systemui.power.domain.interactor
import android.os.PowerManager
+import com.android.systemui.camera.CameraGestureHelper
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorActual
import com.android.systemui.dagger.SysUISingleton
@@ -28,6 +29,7 @@
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -41,6 +43,7 @@
@FalsingCollectorActual private val falsingCollector: FalsingCollector,
private val screenOffAnimationController: ScreenOffAnimationController,
private val statusBarStateController: StatusBarStateController,
+ private val cameraGestureHelper: Provider<CameraGestureHelper>,
) {
/** Whether the screen is on or off. */
val isInteractive: Flow<Boolean> = repository.isInteractive
@@ -206,7 +209,13 @@
}
fun onCameraLaunchGestureDetected() {
- repository.updateWakefulness(powerButtonLaunchGestureTriggered = true)
+ if (
+ cameraGestureHelper
+ .get()
+ .canCameraGestureBeLaunched(statusBarStateController.getState())
+ ) {
+ repository.updateWakefulness(powerButtonLaunchGestureTriggered = true)
+ }
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
index 5432793d..0f49c94 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
@@ -19,7 +19,7 @@
* all use cases. If you need more granular information about a waking/sleeping transition, use
* the [KeyguardTransitionInteractor].
*/
- internal val internalWakefulnessState: WakefulnessState = WakefulnessState.AWAKE,
+ val internalWakefulnessState: WakefulnessState = WakefulnessState.AWAKE,
val lastWakeReason: WakeSleepReason = WakeSleepReason.OTHER,
val lastSleepReason: WakeSleepReason = WakeSleepReason.OTHER,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index dbfe818..abc0453 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -586,6 +586,15 @@
)
)
)
+ } else {
+ if (isLongClickable) {
+ info.addAction(
+ AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
+ resources.getString(R.string.accessibility_long_click_tile)
+ )
+ )
+ }
}
if (!TextUtils.isEmpty(accessibilityClass)) {
info.className =
@@ -597,14 +606,6 @@
if (Switch::class.java.name == accessibilityClass) {
info.isChecked = tileState
info.isCheckable = true
- if (isLongClickable) {
- info.addAction(
- AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
- resources.getString(R.string.accessibility_long_click_tile)
- )
- )
- }
}
}
if (position != INVALID) {
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 158eb6e..b2873c5 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
@@ -736,7 +736,8 @@
// Set network description for the carrier network when connecting to the carrier network
// under the airplane mode ON.
if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
- summary = context.getString(R.string.preference_summary_default_combination,
+ summary = context.getString(
+ com.android.settingslib.R.string.preference_summary_default_combination,
context.getString(
isForDds // if nonDds is active, explains Dds status as poor connection
? (isOnNonDds ? R.string.mobile_data_poor_connection
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index ba0a8d6..2cdcc24 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.os.UserHandle
import android.util.Log
-import androidx.annotation.GuardedBy
import com.android.internal.logging.InstanceId
import com.android.systemui.Dumpable
import com.android.systemui.animation.Expandable
@@ -34,6 +33,7 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.io.PrintWriter
+import java.util.concurrent.CopyOnWriteArraySet
import java.util.function.Supplier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -57,10 +57,8 @@
private val context
get() = qsHost.context
- @GuardedBy("callbacks")
- private val callbacks: MutableCollection<QSTile.Callback> = mutableSetOf()
- @GuardedBy("listeningClients")
- private val listeningClients: MutableCollection<Any> = mutableSetOf()
+ private val callbacks = CopyOnWriteArraySet<QSTile.Callback>()
+ private val listeningClients = CopyOnWriteArraySet<Any>()
// Cancels the jobs when the adapter is no longer alive
private var tileAdapterJob: Job? = null
@@ -113,19 +111,17 @@
override fun addCallback(callback: QSTile.Callback?) {
callback ?: return
- synchronized(callbacks) {
- callbacks.add(callback)
- state?.let(callback::onStateChanged)
- }
+ callbacks.add(callback)
+ state?.let(callback::onStateChanged)
}
override fun removeCallback(callback: QSTile.Callback?) {
callback ?: return
- synchronized(callbacks) { callbacks.remove(callback) }
+ callbacks.remove(callback)
}
override fun removeCallbacks() {
- synchronized(callbacks) { callbacks.clear() }
+ callbacks.clear()
}
override fun click(expandable: Expandable?) {
@@ -163,32 +159,28 @@
override fun setListening(client: Any?, listening: Boolean) {
client ?: return
- synchronized(listeningClients) {
- if (listening) {
- listeningClients.add(client)
- if (listeningClients.size == 1) {
- stateJob =
- qsTileViewModel.state
- .filterNotNull()
- .map { mapState(context, it, qsTileViewModel.config) }
- .onEach { legacyState ->
- synchronized(callbacks) {
- callbacks.forEach { it.onStateChanged(legacyState) }
- }
- }
- .launchIn(applicationScope)
- }
- } else {
- listeningClients.remove(client)
- if (listeningClients.isEmpty()) {
- stateJob?.cancel()
- }
+ if (listening) {
+ listeningClients.add(client)
+ if (listeningClients.size == 1) {
+ stateJob =
+ qsTileViewModel.state
+ .filterNotNull()
+ .map { mapState(context, it, qsTileViewModel.config) }
+ .onEach { legacyState ->
+ callbacks.forEach { it.onStateChanged(legacyState) }
+ }
+ .launchIn(applicationScope)
+ }
+ } else {
+ listeningClients.remove(client)
+ if (listeningClients.isEmpty()) {
+ stateJob?.cancel()
}
}
}
override fun isListening(): Boolean =
- synchronized(listeningClients) { listeningClients.isNotEmpty() }
+ listeningClients.isNotEmpty()
override fun setDetailListening(show: Boolean) {
// do nothing like QSTileImpl
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index bc5cf2a..b60c193 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -41,10 +41,10 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
-import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
import com.android.systemui.biometrics.AuthController;
@@ -101,7 +101,7 @@
private final Context mContext;
private final WindowRootViewComponent.Factory mWindowRootViewComponentFactory;
- private final WindowManager mWindowManager;
+ private final ViewCaptureAwareWindowManager mWindowManager;
private final IActivityManager mActivityManager;
private final DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
@@ -145,7 +145,7 @@
public NotificationShadeWindowControllerImpl(
Context context,
WindowRootViewComponent.Factory windowRootViewComponentFactory,
- WindowManager windowManager,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
IActivityManager activityManager,
DozeParameters dozeParameters,
StatusBarStateController statusBarStateController,
@@ -165,7 +165,7 @@
Lazy<CommunalInteractor> communalInteractor) {
mContext = context;
mWindowRootViewComponentFactory = windowRootViewComponentFactory;
- mWindowManager = windowManager;
+ mWindowManager = viewCaptureAwareWindowManager;
mActivityManager = activityManager;
mDozeParameters = dozeParameters;
mKeyguardStateController = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 1a7bc16..e8a7840 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -91,6 +91,12 @@
val expandFraction: Flow<Float> = shadeInteractor.anyExpansion.dumpValue("expandFraction")
/**
+ * The amount [0-1] that quick settings has been opened. At 0, the shade may be open or closed;
+ * at 1, the quick settings are open.
+ */
+ val shadeToQsFraction: Flow<Float> = shadeInteractor.qsExpansion.dumpValue("shadeToQsFraction")
+
+ /**
* The amount in px that the notification stack should scroll due to internal expansion. This
* should only happen when a notification expansion hits the bottom of the screen, so it is
* necessary to scroll up to keep expanding the notification.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index a0d4ca2..ae31151 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -261,8 +261,6 @@
boolean isScreenFullyOff();
- boolean isCameraAllowedByAdmin();
-
boolean isGoingToSleep();
void notifyBiometricAuthModeChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index c8a4450..5209d0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -49,6 +49,7 @@
import com.android.systemui.emergency.EmergencyGesture;
import com.android.systemui.emergency.EmergencyGestureModule.EmergencyGestureIntentFactory;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QSPanelController;
@@ -109,6 +110,7 @@
private final Lazy<CameraLauncher> mCameraLauncherLazy;
private final QuickSettingsController mQsController;
private final QSHost mQSHost;
+ private final KeyguardInteractor mKeyguardInteractor;
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@@ -148,6 +150,7 @@
UserTracker userTracker,
QSHost qsHost,
ActivityStarter activityStarter,
+ KeyguardInteractor keyguardInteractor,
EmergencyGestureIntentFactory emergencyGestureIntentFactory) {
mCentralSurfaces = centralSurfaces;
mQsController = quickSettingsController;
@@ -176,7 +179,7 @@
mCameraLauncherLazy = cameraLauncherLazy;
mUserTracker = userTracker;
mQSHost = qsHost;
-
+ mKeyguardInteractor = keyguardInteractor;
mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
mVibratorOptional, resources);
@@ -351,6 +354,8 @@
}
return;
}
+ mKeyguardInteractor.onCameraLaunchDetected(source);
+
if (!mCentralSurfaces.isDeviceInteractive()) {
mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_CAMERA_LAUNCH,
"com.android.systemui:CAMERA_GESTURE");
@@ -383,6 +388,7 @@
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.reset(true /* hide */);
}
+ mCentralSurfaces.startLaunchTransitionTimeout();
mCameraLauncherLazy.get().launchCamera(source,
mPanelExpansionInteractor.isFullyCollapsed());
mCentralSurfaces.updateScrimController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 88d3e07..d4f2a93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -34,72 +34,127 @@
*/
abstract class CentralSurfacesEmptyImpl : CentralSurfaces {
override val lifecycle = LifecycleRegistry(this)
+
override fun updateIsKeyguard() = false
+
override fun updateIsKeyguard(forceStateChange: Boolean) = false
+
override fun getKeyguardMessageArea(): AuthKeyguardMessageArea? = null
+
override fun isLaunchingActivityOverLockscreen() = false
+
override fun isDismissingShadeForActivityLaunch() = false
+
override fun onKeyguardViewManagerStatesUpdated() {}
+
override fun getCommandQueuePanelsEnabled() = false
+
override fun showWirelessChargingAnimation(batteryLevel: Int) {}
+
override fun checkBarModes() {}
+
override fun updateBubblesVisibility() {}
+
override fun setInteracting(barWindow: Int, interacting: Boolean) {}
+
override fun getDisplayWidth() = 0f
+
override fun getDisplayHeight() = 0f
+
override fun showKeyguard() {}
+
override fun hideKeyguard() = false
+
override fun showKeyguardImpl() {}
+
override fun fadeKeyguardAfterLaunchTransition(
beforeFading: Runnable?,
endRunnable: Runnable?,
cancelRunnable: Runnable?,
) {}
+
override fun startLaunchTransitionTimeout() {}
+
override fun hideKeyguardImpl(forceStateChange: Boolean) = false
+
override fun keyguardGoingAway() {}
+
override fun setKeyguardFadingAway(startTime: Long, delay: Long, fadeoutDuration: Long) {}
+
override fun finishKeyguardFadingAway() {}
+
override fun userActivity() {}
+
override fun endAffordanceLaunch() {}
+
override fun shouldKeyguardHideImmediately() = false
+
override fun showBouncerWithDimissAndCancelIfKeyguard(
performAction: OnDismissAction?,
cancelAction: Runnable?,
) {}
+
override fun getNavigationBarView(): NavigationBarView? = null
+
override fun setBouncerShowing(bouncerShowing: Boolean) {}
+
override fun isScreenFullyOff() = false
- override fun isCameraAllowedByAdmin() = false
+
override fun isGoingToSleep() = false
+
override fun notifyBiometricAuthModeChanged() {}
+
override fun setTransitionToFullShadeProgress(transitionToFullShadeProgress: Float) {}
+
override fun setPrimaryBouncerHiddenFraction(expansion: Float) {}
+
override fun updateScrimController() {}
+
override fun shouldIgnoreTouch() = false
+
override fun isDeviceInteractive() = false
+
override fun handleExternalShadeWindowTouch(event: MotionEvent?) {}
+
override fun handleCommunalHubTouch(event: MotionEvent?) {}
+
override fun awakenDreams() {}
+
override fun isBouncerShowing() = false
+
override fun isBouncerShowingScrimmed() = false
+
override fun updateNotificationPanelTouchState() {}
+
override fun getRotation() = 0
+
override fun setBarStateForTest(state: Int) {}
+
override fun acquireGestureWakeLock(time: Long) {}
+
override fun resendMessage(msg: Int) {}
+
override fun resendMessage(msg: Any?) {}
+
override fun setLastCameraLaunchSource(source: Int) {}
+
override fun setLaunchCameraOnFinishedGoingToSleep(launch: Boolean) {}
+
override fun setLaunchCameraOnFinishedWaking(launch: Boolean) {}
+
override fun setLaunchEmergencyActionOnFinishedGoingToSleep(launch: Boolean) {}
+
override fun setLaunchEmergencyActionOnFinishedWaking(launch: Boolean) {}
+
override fun getQSPanelController(): QSPanelController? = null
+
override fun getDisplayDensity() = 0f
+
override fun setIsLaunchingActivityOverLockscreen(
isLaunchingActivityOverLockscreen: Boolean,
dismissShade: Boolean,
) {}
+
override fun getAnimatorControllerFromNotification(
associatedView: ExpandableNotificationRow?,
): ActivityTransitionAnimator.Controller? = null
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 462ae7a..461a38d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -47,7 +47,6 @@
import android.app.TaskInfo;
import android.app.UiModeManager;
import android.app.WallpaperManager;
-import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -883,8 +882,6 @@
// start old BaseStatusBar.start().
mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
- mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
mAccessibilityManager = (AccessibilityManager)
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
@@ -2627,6 +2624,7 @@
mStackScrollerController.updateSensitivenessForOccludedWakeup();
}
if (mLaunchCameraWhenFinishedWaking) {
+ startLaunchTransitionTimeout();
mCameraLauncherLazy.get().launchCamera(mLastCameraLaunchSource,
mShadeSurface.isFullyCollapsed());
mLaunchCameraWhenFinishedWaking = false;
@@ -2701,21 +2699,6 @@
}
@Override
- public boolean isCameraAllowedByAdmin() {
- if (mDevicePolicyManager.getCameraDisabled(null,
- mLockscreenUserManager.getCurrentUserId())) {
- return false;
- } else if (mKeyguardStateController.isShowing()
- && mStatusBarKeyguardViewManager.isSecure()) {
- // Check if the admin has disabled the camera specifically for the keyguard
- return (mDevicePolicyManager.getKeyguardDisabledFeatures(null,
- mLockscreenUserManager.getCurrentUserId())
- & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0;
- }
- return true;
- }
-
- @Override
public boolean isGoingToSleep() {
return mWakefulnessLifecycle.getWakefulness()
== WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;
@@ -2864,7 +2847,6 @@
protected boolean mDeviceInteractive;
- protected DevicePolicyManager mDevicePolicyManager;
private final PowerManager mPowerManager;
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index e08dbb9..b2035e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -389,10 +389,13 @@
// OnReorderingAllowedListener:
private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
- mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
if (NotificationThrottleHun.isEnabled()) {
mAvalancheController.setEnableAtRuntime(true);
+ if (mEntriesToRemoveWhenReorderingAllowed.isEmpty()) {
+ return;
+ }
}
+ mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
if (isHeadsUpEntry(entry.getKey())) {
// Maybe the heads-up was removed already
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 a963826..5ba5c06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -54,6 +54,7 @@
// allowed again.
logDroppedHunsInBackground(getWaitingKeys().size)
clearNext()
+ headsUpEntryShowing = null
}
if (field != value) {
field = value
@@ -222,15 +223,19 @@
/**
* Returns duration based on
- * 1) Whether HeadsUpEntry is the last one tracked byAvalancheController
+ * 1) Whether HeadsUpEntry is the last one tracked by AvalancheController
* 2) The priority of the top HUN in the next batch Used by
* BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
*/
- fun getDurationMs(entry: HeadsUpEntry, autoDismissMs: Int): Int {
+ fun getDurationMs(entry: HeadsUpEntry?, autoDismissMs: Int): Int {
if (!isEnabled()) {
// Use default duration, like we did before AvalancheController existed
return autoDismissMs
}
+ if (entry == null) {
+ // This should never happen
+ return autoDismissMs
+ }
val showingList: MutableList<HeadsUpEntry> = mutableListOf()
if (headsUpEntryShowing != null) {
showingList.add(headsUpEntryShowing!!)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index dcd9cae..e23f6ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -344,6 +344,15 @@
}
protected boolean hasFullScreenIntent(@NonNull NotificationEntry entry) {
+ if (entry == null) {
+ return false;
+ }
+ if (entry.getSbn() == null) {
+ return false;
+ }
+ if (entry.getSbn().getNotification() == null) {
+ return false;
+ }
return entry.getSbn().getNotification().fullScreenIntent != null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningAction.kt b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningAction.kt
new file mode 100644
index 0000000..a77acb5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningAction.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.volume
+
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
+import android.app.PendingIntent.FLAG_UPDATE_CURRENT
+import android.content.Context
+import android.content.Intent
+
+/**
+ * label: Notification action label text. intent: The Intent used to start Activity or Broadcast.
+ * isActivity: Defines if the pending intent should start an activity. Default is to broadcast
+ */
+data class CsdWarningAction(
+ val label: String? = null,
+ val intent: Intent? = null,
+ val isActivity: Boolean = false,
+) {
+ fun toPendingIntent(context: Context): PendingIntent? {
+ if (label == null || intent == null) {
+ return null
+ }
+ if (isActivity) {
+ return PendingIntent.getActivity(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ }
+ return PendingIntent.getBroadcast(context, 0, intent, FLAG_IMMUTABLE)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
index bb230e6..a63660b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
@@ -30,7 +30,6 @@
import android.media.AudioManager;
import android.provider.Settings;
import android.util.Log;
-import android.util.Pair;
import android.view.KeyEvent;
import android.view.WindowManager;
@@ -109,7 +108,7 @@
private long mShowTime;
@VisibleForTesting public int mCachedMediaStreamVolume;
- private Optional<ImmutableList<Pair<String, Intent>>> mActionIntents;
+ private Optional<ImmutableList<CsdWarningAction>> mActionIntents;
private final BroadcastDispatcher mBroadcastDispatcher;
/**
@@ -121,7 +120,7 @@
CsdWarningDialog create(
int csdWarning,
Runnable onCleanup,
- Optional<ImmutableList<Pair<String, Intent>>> actionIntents);
+ Optional<ImmutableList<CsdWarningAction>> actionIntents);
}
@AssistedInject
@@ -132,7 +131,7 @@
NotificationManager notificationManager,
@Background DelayableExecutor delayableExecutor,
@Assisted Runnable onCleanup,
- @Assisted Optional<ImmutableList<Pair<String, Intent>>> actionIntents,
+ @Assisted Optional<ImmutableList<CsdWarningAction>> actionIntents,
BroadcastDispatcher broadcastDispatcher) {
super(context);
mCsdWarning = csdWarning;
@@ -351,39 +350,45 @@
if (Flags.sounddoseCustomization()
&& mActionIntents.isPresent()
&& !mActionIntents.get().isEmpty()) {
- ImmutableList<Pair<String, Intent>> actionIntentsList = mActionIntents.get();
- for (Pair<String, Intent> intentPair : actionIntentsList) {
- if (intentPair != null && intentPair.first != null && intentPair.second != null) {
- PendingIntent pendingActionIntent =
- PendingIntent.getBroadcast(mContext, 0, intentPair.second,
- FLAG_IMMUTABLE);
- builder.addAction(0, intentPair.first, pendingActionIntent);
- // Register receiver to undo volume only when
- // notification conaining the undo action would be sent.
- if (intentPair.first == mContext.getString(R.string.volume_undo_action)) {
- final IntentFilter filterUndo = new IntentFilter(
- VolumeDialog.ACTION_VOLUME_UNDO);
- mBroadcastDispatcher.registerReceiver(mReceiverUndo,
- filterUndo,
- /* executor = default */ null,
- /* user = default */ null,
- Context.RECEIVER_NOT_EXPORTED,
- /* permission = default */ null);
+ ImmutableList<CsdWarningAction> actionIntentsList = mActionIntents.get();
+ for (CsdWarningAction action : actionIntentsList) {
+ if (action.getLabel() == null || action.getIntent() == null) {
+ Log.w(TAG, "Null action intent received. Skipping addition to notification");
+ continue;
+ }
+ PendingIntent pendingActionIntent = action.toPendingIntent(mContext);
+ if (pendingActionIntent == null) {
+ Log.w(TAG, "Null pending intent received. Skipping addition to notification");
+ continue;
+ }
+ builder.addAction(0, action.getLabel(), pendingActionIntent);
- // Register receiver to learn if notification has been dismissed.
- // This is required to unregister receivers to prevent leak.
- Intent dismissIntent = new Intent(DISMISS_CSD_NOTIFICATION)
- .setPackage(mContext.getPackageName());
- PendingIntent pendingDismissIntent = PendingIntent.getBroadcast(mContext,
- 0, dismissIntent, FLAG_IMMUTABLE);
- mBroadcastDispatcher.registerReceiver(mReceiverDismissNotification,
- new IntentFilter(DISMISS_CSD_NOTIFICATION),
- /* executor = default */ null,
- /* user = default */ null,
- Context.RECEIVER_NOT_EXPORTED,
- /* permission = default */ null);
- builder.setDeleteIntent(pendingDismissIntent);
- }
+ // Register receiver to undo volume only when
+ // notification conaining the undo action would be sent.
+ if (action.getLabel().equals(mContext.getString(R.string.volume_undo_action))) {
+ final IntentFilter filterUndo = new IntentFilter(
+ VolumeDialog.ACTION_VOLUME_UNDO);
+ mBroadcastDispatcher.registerReceiver(mReceiverUndo,
+ filterUndo,
+ /* executor = default */ null,
+ /* user = default */ null,
+ Context.RECEIVER_NOT_EXPORTED,
+ /* permission = default */ null);
+
+ // Register receiver to learn if notification has been dismissed.
+ // This is required to unregister receivers to prevent leak.
+ Intent dismissIntent = new Intent(DISMISS_CSD_NOTIFICATION)
+ .setPackage(mContext.getPackageName());
+ PendingIntent pendingDismissIntent = PendingIntent.getBroadcast(
+ mContext,
+ 0, dismissIntent, FLAG_IMMUTABLE);
+ mBroadcastDispatcher.registerReceiver(mReceiverDismissNotification,
+ new IntentFilter(DISMISS_CSD_NOTIFICATION),
+ /* executor = default */ null,
+ /* user = default */ null,
+ Context.RECEIVER_NOT_EXPORTED,
+ /* permission = default */ null);
+ builder.setDeleteIntent(pendingDismissIntent);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 0770d89..e56f6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -51,7 +51,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -79,7 +78,6 @@
import android.provider.Settings.Global;
import android.text.InputFilter;
import android.util.Log;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.view.ContextThemeWrapper;
@@ -322,8 +320,8 @@
private final VolumePanelFlag mVolumePanelFlag;
private final VolumeDialogInteractor mInteractor;
// Optional actions for soundDose
- private Optional<ImmutableList<Pair<String, Intent>>> mCsdWarningNotificationActions =
- Optional.of(ImmutableList.of());
+ private Optional<ImmutableList<CsdWarningAction>>
+ mCsdWarningNotificationActions = Optional.of(ImmutableList.of());
public VolumeDialogImpl(
Context context,
@@ -2231,7 +2229,7 @@
}
public void setCsdWarningNotificationActionIntents(
- ImmutableList<Pair<String, Intent>> actionIntent) {
+ ImmutableList<CsdWarningAction> actionIntent) {
mCsdWarningNotificationActions = Optional.of(actionIntent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index dc2b80c..68d12f6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -16,6 +16,8 @@
package com.android.systemui.volume;
+import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;
+
import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
@@ -26,6 +28,7 @@
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor;
import java.io.PrintWriter;
@@ -41,23 +44,29 @@
private boolean mEnabled;
private final Context mContext;
private VolumeDialogComponent mVolumeComponent;
+ private AudioSharingInteractor mAudioSharingInteractor;
@Inject
- public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent) {
+ public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent,
+ AudioSharingInteractor audioSharingInteractor) {
mContext = context;
mVolumeComponent = volumeDialogComponent;
+ mAudioSharingInteractor = audioSharingInteractor;
}
@Override
public void start() {
boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
boolean enableSafetyWarning =
- mContext.getResources().getBoolean(R.bool.enable_safety_warning);
+ mContext.getResources().getBoolean(R.bool.enable_safety_warning);
mEnabled = enableVolumeUi || enableSafetyWarning;
if (!mEnabled) return;
mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning);
setDefaultVolumeController();
+ if (volumeDialogAudioSharingFix()) {
+ mAudioSharingInteractor.handlePrimaryGroupChange();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 0c1bc21..efaca7a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -75,7 +75,6 @@
@Provides
@SysUISingleton
fun provideAudioSharingRepository(
- @Application context: Context,
contentResolver: ContentResolver,
localBluetoothManager: LocalBluetoothManager?,
@Application coroutineScope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
index aba3015..2170c36 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
@@ -17,18 +17,28 @@
package com.android.systemui.volume.domain.interactor
import android.bluetooth.BluetoothCsipSetCoordinator
+import android.media.AudioManager.STREAM_MUSIC
import androidx.annotation.IntRange
import com.android.settingslib.volume.data.repository.AudioSharingRepository
import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
+import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
+import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
interface AudioSharingInteractor {
/** Audio sharing secondary headset volume changes. */
@@ -45,6 +55,16 @@
@IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
level: Int
)
+
+ /**
+ * Handle primary group change in audio sharing.
+ *
+ * Once the primary group is changed, we need to sync its volume to STREAM_MUSIC to make sure
+ * the volume adjustment during audio sharing can be kept after the sharing ends.
+ *
+ * TODO(b/355396988) Migrate to audio framework solution once it is in place.
+ */
+ fun handlePrimaryGroupChange()
}
@SysUISingleton
@@ -52,26 +72,60 @@
@Inject
constructor(
@Application private val coroutineScope: CoroutineScope,
+ @Background private val backgroundCoroutineContext: CoroutineContext,
+ private val audioVolumeInteractor: AudioVolumeInteractor,
private val audioSharingRepository: AudioSharingRepository
) : AudioSharingInteractor {
override val volume: Flow<Int?> =
combine(audioSharingRepository.secondaryGroupId, audioSharingRepository.volumeMap) {
- secondaryGroupId,
- volumeMap ->
- if (secondaryGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) null
- else volumeMap.getOrDefault(secondaryGroupId, DEFAULT_VOLUME)
- }
+ secondaryGroupId,
+ volumeMap ->
+ if (secondaryGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) null
+ else volumeMap.getOrDefault(secondaryGroupId, DEFAULT_VOLUME)
+ }
+ .distinctUntilChanged()
override val volumeMin: Int = AUDIO_SHARING_VOLUME_MIN
override val volumeMax: Int = AUDIO_SHARING_VOLUME_MAX
- override fun setStreamVolume(level: Int) {
+ override fun setStreamVolume(
+ @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+ level: Int
+ ) {
coroutineScope.launch { audioSharingRepository.setSecondaryVolume(level) }
}
+ override fun handlePrimaryGroupChange() {
+ coroutineScope.launch {
+ audioSharingRepository.primaryGroupId
+ .map { primaryGroupId -> audioSharingRepository.volumeMap.value[primaryGroupId] }
+ .filterNotNull()
+ .distinctUntilChanged()
+ .collect {
+ // Once primary device change, we need to update the STREAM_MUSIC volume to get
+ // align with the primary device's volume
+ setMusicStreamVolume(it)
+ }
+ }
+ }
+
+ private suspend fun setMusicStreamVolume(volume: Int) {
+ withContext(backgroundCoroutineContext) {
+ val musicStream =
+ audioVolumeInteractor.getAudioStream(AudioStream(STREAM_MUSIC)).first()
+ val musicVolume =
+ Math.round(
+ volume.toFloat() * (musicStream.maxVolume - musicStream.minVolume) /
+ (AUDIO_SHARING_VOLUME_MAX - AUDIO_SHARING_VOLUME_MIN)
+ )
+ audioVolumeInteractor.setVolume(AudioStream(STREAM_MUSIC), musicVolume)
+ }
+ }
+
private companion object {
+ const val TAG = "AudioSharingInteractor"
const val DEFAULT_VOLUME = 20
}
}
@@ -82,7 +136,12 @@
override val volumeMin: Int = EMPTY_VOLUME
override val volumeMax: Int = EMPTY_VOLUME
- override fun setStreamVolume(level: Int) {}
+ override fun setStreamVolume(
+ @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+ level: Int
+ ) {}
+
+ override fun handlePrimaryGroupChange() {}
private companion object {
const val EMPTY_VOLUME = 0
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index e68a4a5..9de7528 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -70,10 +70,10 @@
import android.view.RemoteAnimationTarget;
import android.view.View;
import android.view.ViewRootImpl;
-import android.view.WindowManager;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
@@ -167,7 +167,7 @@
private @Mock BroadcastDispatcher mBroadcastDispatcher;
private @Mock DismissCallbackRegistry mDismissCallbackRegistry;
private @Mock DumpManager mDumpManager;
- private @Mock WindowManager mWindowManager;
+ private @Mock ViewCaptureAwareWindowManager mWindowManager;
private @Mock IActivityManager mActivityManager;
private @Mock ConfigurationController mConfigurationController;
private @Mock PowerManager mPowerManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index 196bbb9..413aa55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -307,7 +307,7 @@
mNavigationBarController.mIsLargeScreen = false;
mNavigationBarController.mIsPhone = true;
- assertFalse(mNavigationBarController.supportsTaskbar());
+ assertTrue(mNavigationBarController.supportsTaskbar());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 988769f..90ffaf1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -25,6 +25,7 @@
import android.view.ContextThemeWrapper
import android.view.View
import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.Button
import android.widget.TextView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -381,6 +382,47 @@
}
@Test
+ fun testNonSwitchA11yClass_longClickActionHasCorrectLabel() {
+ val state =
+ QSTile.State().apply {
+ expandedAccessibilityClassName = Button::class.java.name
+ handlesLongClick = true
+ }
+ tileView.changeState(state)
+ val info = AccessibilityNodeInfo(tileView)
+ tileView.onInitializeAccessibilityNodeInfo(info)
+
+ assertThat(
+ info.actionList
+ .find {
+ it.id == AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id
+ }
+ ?.label
+ )
+ .isEqualTo(context.getString(R.string.accessibility_long_click_tile))
+ }
+
+ @Test
+ fun testNonSwitchA11yClass_disabledByPolicy_noLongClickAction() {
+ val state =
+ QSTile.State().apply {
+ expandedAccessibilityClassName = Button::class.java.name
+ handlesLongClick = true
+ disabledByPolicy = true
+ }
+ tileView.changeState(state)
+ val info = AccessibilityNodeInfo(tileView)
+ tileView.onInitializeAccessibilityNodeInfo(info)
+
+ assertThat(
+ info.actionList.find {
+ it.id == AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id
+ }
+ )
+ .isNull()
+ }
+
+ @Test
fun onStateChange_longPressEffectActive_withInvalidDuration_doesNotInitializeEffect() {
val state = QSTile.State() // A state that handles longPress
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 06a883c..b7ce336 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -44,7 +44,6 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -190,7 +189,6 @@
mKosmos.getKeyguardTransitionInteractor();
KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
mKeyguardRepository,
- new FakeCommandQueue(),
powerInteractor,
new FakeKeyguardBouncerRepository(),
new ConfigurationInteractor(configurationRepository),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index e46906f..4762527 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -66,7 +66,8 @@
repository = powerRepository,
falsingCollector = mock(),
screenOffAnimationController = mock(),
- statusBarStateController = mock()
+ statusBarStateController = mock(),
+ cameraGestureHelper = mock(),
)
private val configurationRepository =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 5e5586d..d9e9495 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.emergency.EmergencyGestureModule.EmergencyGestureIntentFactory;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
import com.android.systemui.recents.ScreenPinningRequest;
@@ -103,6 +104,7 @@
@Mock private QSHost mQSHost;
@Mock private ActivityStarter mActivityStarter;
@Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
+ @Mock private KeyguardInteractor mKeyguardInteractor;
CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
@@ -140,6 +142,7 @@
mUserTracker,
mQSHost,
mActivityStarter,
+ mKeyguardInteractor,
mEmergencyGestureIntentFactory);
when(mUserTracker.getUserHandle()).thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
index 49aedcc..bebf1cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
@@ -36,7 +36,6 @@
import android.media.AudioManager;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
-import android.util.Pair;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -70,6 +69,8 @@
private CsdWarningDialog mDialog;
private static final String DISMISS_CSD_NOTIFICATION =
"com.android.systemui.volume.DISMISS_CSD_NOTIFICATION";
+ private final Optional<ImmutableList<CsdWarningAction>> mEmptyActions =
+ Optional.of(ImmutableList.of());
@Before
public void setup() {
@@ -87,7 +88,7 @@
// instantiate directly instead of via factory; we don't want executor to be @Background
mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext,
mAudioManager, mNotificationManager, executor, null,
- Optional.of(ImmutableList.of(new Pair("", new Intent()))),
+ mEmptyActions,
mFakeBroadcastDispatcher);
mDialog.show();
@@ -104,7 +105,7 @@
FakeExecutor executor = new FakeExecutor(new FakeSystemClock());
mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
mAudioManager, mNotificationManager, executor, null,
- Optional.of(ImmutableList.of(new Pair("", new Intent()))),
+ mEmptyActions,
mFakeBroadcastDispatcher);
mDialog.show();
@@ -121,7 +122,7 @@
.setPackage(mContext.getPackageName());
mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
mAudioManager, mNotificationManager, executor, null,
- Optional.of(ImmutableList.of(new Pair("Undo", undoIntent))),
+ Optional.of(ImmutableList.of(new CsdWarningAction("Undo", undoIntent, false))),
mFakeBroadcastDispatcher);
when(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)).thenReturn(25);
@@ -148,7 +149,7 @@
.setPackage(mContext.getPackageName());
mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
mAudioManager, mNotificationManager, executor, null,
- Optional.of(ImmutableList.of(new Pair("Undo", undoIntent))),
+ Optional.of(ImmutableList.of(new CsdWarningAction("Undo", undoIntent, false))),
mFakeBroadcastDispatcher);
Intent dismissIntent = new Intent(DISMISS_CSD_NOTIFICATION)
.setPackage(mContext.getPackageName());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 3f5dc82..8b7d921 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -252,7 +252,6 @@
}
@Test
- @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
public void handleAudioSharingStreamVolumeChanges_updateState() {
ArgumentCaptor<VolumeDialogController.State> stateCaptor =
ArgumentCaptor.forClass(VolumeDialogController.State.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index b5cbf59..caa1779 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -44,7 +44,6 @@
import static org.mockito.Mockito.when;
import android.app.KeyguardManager;
-import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -57,7 +56,6 @@
import android.provider.Settings;
import android.testing.TestableLooper;
import android.util.Log;
-import android.util.Pair;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -166,7 +164,7 @@
new CsdWarningDialog.Factory() {
@Override
public CsdWarningDialog create(int warningType, Runnable onCleanup,
- Optional<ImmutableList<Pair<String, Intent>>> actionIntents) {
+ Optional<ImmutableList<CsdWarningAction>> actionIntents) {
return mCsdWarningDialog;
}
};
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 60b5b5d..d6b4d2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -98,6 +98,8 @@
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
@@ -210,6 +212,7 @@
import java.util.Optional;
import java.util.concurrent.Executor;
+import kotlin.Lazy;
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
@@ -342,6 +345,8 @@
private Icon mAppBubbleIcon;
@Mock
private Display mDefaultDisplay;
+ @Mock
+ private Lazy<ViewCapture> mLazyViewCapture;
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private ShadeInteractor mShadeInteractor;
@@ -407,7 +412,8 @@
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
new FakeWindowRootViewComponent.Factory(mNotificationShadeWindowView),
- mWindowManager,
+ new ViewCaptureAwareWindowManager(mWindowManager, mLazyViewCapture,
+ /* isViewCaptureEnabled= */ false),
mActivityManager,
mDozeParameters,
mStatusBarStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 0b6b816..5063140 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -33,6 +33,7 @@
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.biometrics.AuthController
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.camera.CameraGestureHelper
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -143,6 +144,7 @@
@get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
@get:Provides val keyguardStateController: KeyguardStateController = mock(),
@get:Provides val globalSettings: GlobalSettings = mock(),
+ @get:Provides val cameraGestureHelper: CameraGestureHelper = mock(),
// log buffers
@get:[Provides BroadcastDispatcherLog]
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/camera/CameraGestureHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/CameraGestureHelperKosmos.kt
new file mode 100644
index 0000000..931567e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/CameraGestureHelperKosmos.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.camera
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import org.mockito.Mockito.mock
+
+val Kosmos.cameraGestureHelper: CameraGestureHelper by
+ Kosmos.Fixture<CameraGestureHelper> {
+ mock(CameraGestureHelper::class.java).also { helper ->
+ whenever(helper.canCameraGestureBeLaunched(any())).thenReturn(true)
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 87143ef..727de9e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -22,6 +22,7 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardDone
@@ -138,6 +139,8 @@
private val _canIgnoreAuthAndReturnToGone = MutableStateFlow(false)
override val canIgnoreAuthAndReturnToGone = _canIgnoreAuthAndReturnToGone.asStateFlow()
+ override val onCameraLaunchDetected = MutableStateFlow(CameraLaunchSourceModel())
+
override fun setQuickSettingsVisible(isVisible: Boolean) {
_isQuickSettingsVisible.value = isVisible
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index b5ca964..a95609e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -21,7 +21,6 @@
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -49,7 +48,6 @@
fun create(
featureFlags: FakeFeatureFlags = FakeFeatureFlags(),
repository: FakeKeyguardRepository = FakeKeyguardRepository(),
- commandQueue: FakeCommandQueue = FakeCommandQueue(),
bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(),
shadeRepository: FakeShadeRepository = FakeShadeRepository(),
@@ -87,7 +85,6 @@
}
return WithDependencies(
repository = repository,
- commandQueue = commandQueue,
featureFlags = featureFlags,
bouncerRepository = bouncerRepository,
configurationRepository = configurationRepository,
@@ -95,7 +92,6 @@
powerInteractor = powerInteractor,
KeyguardInteractor(
repository = repository,
- commandQueue = commandQueue,
powerInteractor = powerInteractor,
bouncerRepository = bouncerRepository,
configurationInteractor = ConfigurationInteractor(configurationRepository),
@@ -112,7 +108,6 @@
data class WithDependencies(
val repository: FakeKeyguardRepository,
- val commandQueue: FakeCommandQueue,
val featureFlags: FakeFeatureFlags,
val bouncerRepository: FakeKeyguardBouncerRepository,
val configurationRepository: FakeConfigurationRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index 81d8f0b..5ab56e9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -18,7 +18,6 @@
import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
-import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
@@ -31,7 +30,6 @@
Kosmos.Fixture {
KeyguardInteractor(
repository = keyguardRepository,
- commandQueue = fakeCommandQueue,
powerInteractor = powerInteractor,
bouncerRepository = keyguardBouncerRepository,
configurationInteractor = configurationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorFactory.kt
index d92ace9..9a07c4e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorFactory.kt
@@ -42,6 +42,7 @@
falsingCollector,
screenOffAnimationController,
statusBarStateController,
+ mock(),
)
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt
index 8486691..d50091e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.power.domain.interactor
+import com.android.systemui.camera.cameraGestureHelper
import com.android.systemui.classifier.falsingCollector
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.plugins.statusbar.statusBarStateController
@@ -29,5 +30,6 @@
falsingCollector = falsingCollector,
screenOffAnimationController = screenOffAnimationController,
statusBarStateController = statusBarStateController,
+ cameraGestureHelper = { cameraGestureHelper },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
index 476b7d8..6417779 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
@@ -38,6 +38,11 @@
public static final Uri CONTENT_URI = Uri.parse("content://settings/fake_global");
+ /**
+ * @deprecated Please use FakeGlobalSettings(testDispatcher) to provide the same dispatcher used
+ * by main test scope.
+ */
+ @Deprecated
public FakeGlobalSettings() {
mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null);
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
index df6fc41..35fa2af 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
@@ -18,5 +18,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
-val Kosmos.fakeGlobalSettings: FakeGlobalSettings by Fixture { FakeGlobalSettings() }
+val Kosmos.fakeGlobalSettings: FakeGlobalSettings by Fixture { FakeGlobalSettings(testDispatcher) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
index e35da11..e4e2481 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
@@ -47,6 +47,11 @@
@UserIdInt
private int mUserId = UserHandle.USER_CURRENT;
+ /**
+ * @deprecated Please use FakeSettings(testDispatcher) to provide the same dispatcher used
+ * by main test scope.
+ */
+ @Deprecated
public FakeSettings() {
mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null);
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
index bcb5848..55044bf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
@@ -18,5 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.settings.fakeUserTracker
-val Kosmos.fakeSettings: FakeSettings by Fixture { FakeSettings() }
+val Kosmos.fakeSettings: FakeSettings by Fixture { FakeSettings(testDispatcher, fakeUserTracker) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
index d391750..0a617d1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
@@ -16,10 +16,7 @@
package com.android.systemui.volume.data.repository
-import androidx.annotation.IntRange
import com.android.settingslib.volume.data.repository.AudioSharingRepository
-import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
-import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
import com.android.settingslib.volume.data.repository.GroupIdToVolumes
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -27,11 +24,14 @@
class FakeAudioSharingRepository : AudioSharingRepository {
private val mutableInAudioSharing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ private val mutablePrimaryGroupId: MutableStateFlow<Int> =
+ MutableStateFlow(TEST_GROUP_ID_INVALID)
private val mutableSecondaryGroupId: MutableStateFlow<Int> =
MutableStateFlow(TEST_GROUP_ID_INVALID)
private val mutableVolumeMap: MutableStateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap())
override val inAudioSharing: Flow<Boolean> = mutableInAudioSharing
+ override val primaryGroupId: StateFlow<Int> = mutablePrimaryGroupId
override val secondaryGroupId: StateFlow<Int> = mutableSecondaryGroupId
override val volumeMap: StateFlow<GroupIdToVolumes> = mutableVolumeMap
@@ -41,6 +41,10 @@
mutableInAudioSharing.value = state
}
+ fun setPrimaryGroupId(groupId: Int) {
+ mutablePrimaryGroupId.value = groupId
+ }
+
fun setSecondaryGroupId(groupId: Int) {
mutableSecondaryGroupId.value = groupId
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt
index 03981bb..ce8aba5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt
@@ -18,12 +18,15 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
import com.android.systemui.volume.data.repository.audioSharingRepository
val Kosmos.audioSharingInteractor by
Kosmos.Fixture {
AudioSharingInteractorImpl(
applicationCoroutineScope,
+ backgroundCoroutineContext,
+ audioVolumeInteractor,
audioSharingRepository,
)
}
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
index 8a2bc1d..f7a59a4b 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
@@ -26,7 +26,6 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Slog;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -60,6 +59,7 @@
private final Set<AccessibilityHierarchyCheck> mHierarchyChecks;
private final ATFHierarchyBuilder mATFHierarchyBuilder;
private final Set<AccessibilityCheckResultReported> mCachedResults = new HashSet<>();
+
@VisibleForTesting
final A11yCheckerTimer mTimer = new A11yCheckerTimer();
@@ -86,10 +86,8 @@
*/
@RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
public Set<AccessibilityCheckResultReported> maybeRunA11yChecker(
- List<AccessibilityNodeInfo> nodes,
- @Nullable AccessibilityEvent accessibilityEvent,
- ComponentName sourceComponentName,
- @UserIdInt int userId) {
+ List<AccessibilityNodeInfo> nodes, @Nullable String sourceEventClassName,
+ ComponentName a11yServiceComponentName, @UserIdInt int userId) {
if (!shouldRunA11yChecker()) {
return Set.of();
}
@@ -108,14 +106,13 @@
List<AccessibilityHierarchyCheckResult> checkResults = runChecksOnNode(nodeInfo);
Set<AccessibilityCheckResultReported> filteredResults =
AccessibilityCheckerUtils.processResults(nodeInfo, checkResults,
- accessibilityEvent, mPackageManager, sourceComponentName);
+ sourceEventClassName, mPackageManager, a11yServiceComponentName);
allResults.addAll(filteredResults);
}
mCachedResults.addAll(allResults);
} catch (RuntimeException e) {
Slog.e(LOG_TAG, "An unknown error occurred while running a11y checker.", e);
}
-
return allResults;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
index 4171108..fa0fed2 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
@@ -22,7 +22,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Slog;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -96,7 +95,7 @@
static Set<AccessibilityCheckResultReported> processResults(
AccessibilityNodeInfo nodeInfo,
List<AccessibilityHierarchyCheckResult> checkResults,
- @Nullable AccessibilityEvent accessibilityEvent,
+ @Nullable String activityClassName,
PackageManager packageManager,
ComponentName a11yServiceComponentName) {
String appPackageName = nodeInfo.getPackageName().toString();
@@ -110,7 +109,8 @@
.setPackageName(appPackageName)
.setAppVersionCode(getAppVersionCode(packageManager, appPackageName))
.setUiElementPath(nodePath)
- .setActivityName(getActivityName(packageManager, accessibilityEvent))
+ .setActivityName(
+ getActivityName(packageManager, appPackageName, activityClassName))
.setWindowTitle(getWindowTitle(nodeInfo))
.setSourceComponentName(a11yServiceComponentName.flattenToString())
.setSourceVersionCode(
@@ -140,31 +140,23 @@
}
/**
- * Returns the simple class name of the Activity providing the cache update, if available,
+ * Returns the simple class name of the Activity associated with the window, if available,
* or an empty String if not.
*/
@VisibleForTesting
static String getActivityName(
- PackageManager packageManager, @Nullable AccessibilityEvent accessibilityEvent) {
- if (accessibilityEvent == null) {
+ PackageManager packageManager, String packageName, @Nullable String activityClassName) {
+ if (activityClassName == null) {
return "";
}
- CharSequence activityName = accessibilityEvent.getClassName();
- if (accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- && accessibilityEvent.getPackageName() != null
- && activityName != null) {
- try {
- // Check class is for a valid Activity.
- packageManager
- .getActivityInfo(
- new ComponentName(accessibilityEvent.getPackageName().toString(),
- activityName.toString()), 0);
- int qualifierEnd = activityName.toString().lastIndexOf('.');
- return activityName.toString().substring(qualifierEnd + 1);
- } catch (PackageManager.NameNotFoundException e) {
- // No need to spam the logs. This is very frequent when the class doesn't match
- // an activity.
- }
+ try {
+ // Check class is for a valid Activity.
+ packageManager.getActivityInfo(new ComponentName(packageName, activityClassName), 0);
+ int qualifierEnd = activityClassName.lastIndexOf('.');
+ return activityClassName.substring(qualifierEnd + 1);
+ } catch (PackageManager.NameNotFoundException e) {
+ // No need to spam the logs. This is very frequent when the class doesn't match
+ // an activity.
}
return "";
}
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 4ff1367..afb7bb4 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -538,6 +538,8 @@
if (!pr.hasProvider(cpi.name)) {
checkTime(startTime, "getContentProviderImpl: scheduling install");
pr.installProvider(cpi.name, cpr);
+ mService.mOomAdjuster.unfreezeTemporarily(proc,
+ CachedAppOptimizer.UNFREEZE_REASON_GET_PROVIDER);
try {
thread.scheduleInstallProvider(cpi);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f813997..55de9aa 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1260,8 +1260,8 @@
* @return true if drag and drop was successfully started, false otherwise.
*/
public boolean startDragAndDrop(@NonNull IBinder fromChannelToken,
- @NonNull InputChannel dragAndDropChannel) {
- return mNative.transferTouchGesture(fromChannelToken, dragAndDropChannel.getToken(),
+ @NonNull IBinder dragAndDropChannelToken) {
+ return mNative.transferTouchGesture(fromChannelToken, dragAndDropChannelToken,
true /* isDragDrop */);
}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index 99f4747..b08f917 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -38,10 +38,11 @@
final class AdditionalSubtypeMapRepository {
private static final String TAG = "AdditionalSubtypeMapRepository";
- // TODO(b/352594784): Should we user other lock primitives?
- @GuardedBy("sPerUserMap")
+ private static final Object sMutationLock = new Object();
+
@NonNull
- private static final SparseArray<AdditionalSubtypeMap> sPerUserMap = new SparseArray<>();
+ private static volatile ImmutableSparseArray<AdditionalSubtypeMap> sPerUserMap =
+ ImmutableSparseArray.empty();
record WriteTask(@UserIdInt int userId, @NonNull AdditionalSubtypeMap subtypeMap,
@NonNull InputMethodMap inputMethodMap) {
@@ -198,7 +199,7 @@
/**
* Returns {@link AdditionalSubtypeMap} for the given user.
*
- * <p>This method is expected be called after {@link #ensureInitializedAndGet(int)}. Otherwise
+ * <p>This method is expected be called after {@link #initializeIfNecessary(int)}. Otherwise
* {@link AdditionalSubtypeMap#EMPTY_MAP} will be returned.</p>
*
* @param userId the user to be queried about
@@ -207,10 +208,7 @@
@AnyThread
@NonNull
static AdditionalSubtypeMap get(@UserIdInt int userId) {
- final AdditionalSubtypeMap map;
- synchronized (sPerUserMap) {
- map = sPerUserMap.get(userId);
- }
+ final AdditionalSubtypeMap map = sPerUserMap.get(userId);
if (map == null) {
Slog.e(TAG, "get(userId=" + userId + ") is called before loadInitialDataAndGet()."
+ " Returning an empty map");
@@ -220,28 +218,24 @@
}
/**
- * Ensures that {@link AdditionalSubtypeMap} is initialized for the given user. Load it from
- * the persistent storage if {@link #putAndSave(int, AdditionalSubtypeMap, InputMethodMap)} has
- * not been called yet.
+ * Ensures that {@link AdditionalSubtypeMap} is initialized for the given user.
*
* @param userId the user to be initialized
- * @return {@link AdditionalSubtypeMap} that is associated with the given user. If
- * {@link #putAndSave(int, AdditionalSubtypeMap, InputMethodMap)} is already called
- * then the given {@link AdditionalSubtypeMap}.
*/
@AnyThread
@NonNull
- static AdditionalSubtypeMap ensureInitializedAndGet(@UserIdInt int userId) {
- final var map = AdditionalSubtypeUtils.load(userId);
- synchronized (sPerUserMap) {
- final AdditionalSubtypeMap previous = sPerUserMap.get(userId);
- // If putAndSave() has already been called, then use it.
- if (previous != null) {
- return previous;
- }
- sPerUserMap.put(userId, map);
+ static void initializeIfNecessary(@UserIdInt int userId) {
+ if (sPerUserMap.contains(userId)) {
+ // Fast-pass. If putAndSave() is already called, then do nothing.
+ return;
}
- return map;
+ final var map = AdditionalSubtypeUtils.load(userId);
+ synchronized (sMutationLock) {
+ // Check the condition again.
+ if (!sPerUserMap.contains(userId)) {
+ sPerUserMap = sPerUserMap.cloneWithPutOrSelf(userId, map);
+ }
+ }
}
/**
@@ -255,12 +249,8 @@
@AnyThread
static void putAndSave(@UserIdInt int userId, @NonNull AdditionalSubtypeMap map,
@NonNull InputMethodMap inputMethodMap) {
- synchronized (sPerUserMap) {
- final AdditionalSubtypeMap previous = sPerUserMap.get(userId);
- if (previous == map) {
- return;
- }
- sPerUserMap.put(userId, map);
+ synchronized (sMutationLock) {
+ sPerUserMap = sPerUserMap.cloneWithPutOrSelf(userId, map);
sWriter.scheduleWriteTask(userId, map, inputMethodMap);
}
}
@@ -277,9 +267,9 @@
@AnyThread
static void remove(@UserIdInt int userId) {
- synchronized (sPerUserMap) {
+ synchronized (sMutationLock) {
sWriter.onUserRemoved(userId);
- sPerUserMap.remove(userId);
+ sPerUserMap = sPerUserMap.cloneWithRemoveOrSelf(userId);
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index ae41133..cdea6ff 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -39,6 +39,7 @@
import android.annotation.AnyThread;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.res.Configuration;
import android.os.Binder;
@@ -128,6 +129,15 @@
@GuardedBy("ImfLock.class")
private IBinder mCurVisibleImeInputTarget;
+ /**
+ * The last window token that we confirmed that IME started talking to. This is always updated
+ * upon reports from the input method. If the window state is already changed before the report
+ * is handled, this field just keeps the last value.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ private IBinder mLastImeTargetWindow;
+
/** Represent the invalid IME visibility state */
public static final int STATE_INVALID = -1;
@@ -479,8 +489,7 @@
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
// Do nothing but preserving the last IME requested visibility state.
- final ImeTargetWindowState lastState =
- getWindowStateOrNull(mService.mLastImeTargetWindow);
+ final ImeTargetWindowState lastState = getWindowStateOrNull(mLastImeTargetWindow);
if (lastState != null) {
state.setRequestedImeVisible(lastState.mRequestedImeVisible);
}
@@ -632,6 +641,17 @@
}
@GuardedBy("ImfLock.class")
+ @Nullable
+ IBinder getLastImeTargetWindow() {
+ return mLastImeTargetWindow;
+ }
+
+ @GuardedBy("ImfLock.class")
+ void setLastImeTargetWindow(@Nullable IBinder imeTargetWindow) {
+ mLastImeTargetWindow = imeTargetWindow;
+ }
+
+ @GuardedBy("ImfLock.class")
void dumpDebug(ProtoOutputStream proto, long fieldId) {
proto.write(SHOW_EXPLICITLY_REQUESTED, mRequestedShowExplicitly);
proto.write(SHOW_FORCED, mShowForced);
@@ -647,6 +667,7 @@
+ " mShowForced=" + mShowForced);
p.println(prefix + "mImeHiddenByDisplayPolicy=" + mPolicy.isImeHiddenByDisplayPolicy());
p.println(prefix + "mInputShown=" + mInputShown);
+ p.println(prefix + "mLastImeTargetWindow=" + mLastImeTargetWindow);
}
/**
diff --git a/services/core/java/com/android/server/inputmethod/ImmutableSparseArray.java b/services/core/java/com/android/server/inputmethod/ImmutableSparseArray.java
new file mode 100644
index 0000000..382aa8a
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ImmutableSparseArray.java
@@ -0,0 +1,183 @@
+/*
+ * 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.inputmethod;
+
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.util.function.Consumer;
+
+/**
+ * A holder object to expose {@link SparseArray} to multiple threads in a thread-safe manner through
+ * "final Field Semantics" defined in JLS 17.5, with only exposing thread-safe methods such as
+ * {@link SparseArray#get(int)} and {@link SparseArray#size()} from {@link SparseArray}, and with
+ * adding clone-with-update style methods {@link #cloneWithPutOrSelf(int, Object)} and
+ * {@link #cloneWithRemoveOrSelf(int)} instead of exposing mutation methods.
+ *
+ * @param <E> Type of the element
+ */
+final class ImmutableSparseArray<E> {
+ @NonNull
+ private final SparseArray<E> mArray;
+
+ private static final ImmutableSparseArray<Object> EMPTY =
+ new ImmutableSparseArray<>(new SparseArray<>());
+
+ /**
+ * Returns an empty {@link ImmutableSparseArray} instance.
+ *
+ * @return An empty {@link ImmutableSparseArray} instance.
+ * @param <T> Type of the element
+ */
+ @SuppressWarnings("unchecked")
+ @AnyThread
+ @NonNull
+ static <T> ImmutableSparseArray<T> empty() {
+ return (ImmutableSparseArray<T>) EMPTY;
+ }
+
+ private ImmutableSparseArray(@NonNull SparseArray<E> array) {
+ mArray = array;
+ }
+
+ /**
+ * @return the size of this array
+ */
+ @AnyThread
+ int size() {
+ return mArray.size();
+ }
+
+ /**
+ * Returns the key of the specified index.
+ *
+ * @return the key of the specified index
+ * @throws ArrayIndexOutOfBoundsException when the index is out of range
+ */
+ @AnyThread
+ int keyAt(int index) {
+ return mArray.keyAt(index);
+ }
+
+ /**
+ * Returns the value of the specified index.
+ *
+ * @return the value of the specified index
+ * @throws ArrayIndexOutOfBoundsException when the index is out of range
+ */
+ @AnyThread
+ @Nullable
+ public E valueAt(int index) {
+ return mArray.valueAt(index);
+ }
+
+ /**
+ * Returns the index of the specified key.
+ *
+ * @return the index of the specified key if exists. Otherwise {@code -1}
+ */
+ @AnyThread
+ int indexOfKey(int key) {
+ return mArray.indexOfKey(key);
+ }
+
+ /**
+ * Returns {@code true} if the given {@code key} exists.
+ *
+ * @param key the key to be queried
+ * @return {@code true} if the given {@code key} exists
+ */
+ @AnyThread
+ boolean contains(int key) {
+ return mArray.contains(key);
+ }
+
+ /**
+ * Returns the value associated with the {@code key}.
+ *
+ * @param key the key to be queried
+ * @return the value associated with the {@code key} if exists. Otherwise {@code null}
+ */
+ @AnyThread
+ @Nullable
+ E get(int key) {
+ return mArray.get(key);
+ }
+
+ /**
+ * Run {@link Consumer} for each value.
+ *
+ * @param consumer {@link Consumer} to be called back
+ */
+ @AnyThread
+ void forEach(@NonNull Consumer<E> consumer) {
+ final int size = mArray.size();
+ for (int i = 0; i < size; ++i) {
+ consumer.accept(mArray.valueAt(i));
+ }
+ }
+
+ /**
+ * Returns an instance of {@link ImmutableSparseArray} that has the given key and value on top
+ * of items cloned from this instance.
+ *
+ * @param key the key to be added
+ * @param value the value to be added
+ * @return the same {@link ImmutableSparseArray} instance if there is actually no update.
+ * Otherwise, a new instance of {@link ImmutableSparseArray}
+ */
+ @AnyThread
+ @NonNull
+ ImmutableSparseArray<E> cloneWithPutOrSelf(int key, @Nullable E value) {
+ final var prevKeyIndex = mArray.indexOfKey(key);
+ if (prevKeyIndex >= 0) {
+ final var prevValue = mArray.valueAt(prevKeyIndex);
+ if (prevValue == value) {
+ return this;
+ }
+ }
+ final var clone = mArray.clone();
+ clone.put(key, value);
+ return new ImmutableSparseArray<>(clone);
+ }
+
+ /**
+ * Returns an instance of {@link ImmutableSparseArray} that does not have the given key on top
+ * of items cloned from this instance.
+ *
+ * @param key the key to be removed
+ * @return the same {@link ImmutableSparseArray} instance if there is actually no update.
+ * Otherwise, a new instance of {@link ImmutableSparseArray}
+ */
+ @AnyThread
+ @NonNull
+ ImmutableSparseArray<E> cloneWithRemoveOrSelf(int key) {
+ final int index = indexOfKey(key);
+ if (index < 0) {
+ return this;
+ }
+ if (mArray.size() == 1) {
+ return empty();
+ }
+ final var clone = mArray.clone();
+ clone.remove(key);
+ return new ImmutableSparseArray<>(clone);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 9837ab1..03cbab5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -463,7 +463,7 @@
// should now try to restart the service for us.
mLastBindTime = SystemClock.uptimeMillis();
clearCurMethodAndSessions();
- mService.clearInputShownLocked();
+ mService.mVisibilityStateComputer.setInputShown(false);
mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME, mUserId);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 9d007c6..effecd4 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -81,6 +81,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.input.InputManager;
import android.inputmethodservice.InputMethodService;
@@ -258,7 +259,6 @@
private static final int MSG_HIDE_ALL_INPUT_METHODS = 1035;
private static final int MSG_REMOVE_IME_SURFACE = 1060;
private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061;
- private static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070;
private static final int MSG_RESET_HANDWRITING = 1090;
private static final int MSG_START_HANDWRITING = 1100;
@@ -305,6 +305,28 @@
private final String[] mNonPreemptibleInputMethods;
/**
+ * Whether the new Input Method Switcher menu is enabled.
+ *
+ * @see #shouldEnableNewInputMethodSwitcherMenu
+ */
+ @SharedByAllUsersField
+ private final boolean mNewInputMethodSwitcherMenuEnabled;
+
+ /**
+ * Returns {@code true} if the new Input Method Switcher menu is enabled. This will be
+ * {@code false} for watches and small screen devices.
+ *
+ * @param context the context to check the device configuration for.
+ */
+ private static boolean shouldEnableNewInputMethodSwitcherMenu(@NonNull Context context) {
+ final boolean isWatch = context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_WATCH);
+ final boolean isSmallScreen = (context.getResources().getConfiguration().screenLayout
+ & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_SMALL;
+ return Flags.imeSwitcherRevamp() && !isWatch && !isSmallScreen;
+ }
+
+ /**
* See {@link #shouldEnableConcurrentMultiUserMode(Context)} about when set to be {@code true}.
*/
@SharedByAllUsersField
@@ -339,6 +361,35 @@
return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentUserId;
}
+ /**
+ * Figures out the target IME user ID associated with the given {@code displayId}.
+ *
+ * @param displayId the display ID to be queried about
+ * @return User ID to be used for this {@code displayId}.
+ */
+ @GuardedBy("ImfLock.class")
+ @UserIdInt
+ private int resolveImeUserIdFromDisplayIdLocked(int displayId) {
+ return mConcurrentMultiUserModeEnabled
+ ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentUserId;
+ }
+
+ /**
+ * Figures out the target IME user ID associated with the given {@code windowToken}.
+ *
+ * @param windowToken the Window token to be queried about
+ * @return User ID to be used for this {@code displayId}.
+ */
+ @GuardedBy("ImfLock.class")
+ @UserIdInt
+ private int resolveImeUserIdFromWindowLocked(@NonNull IBinder windowToken) {
+ if (mConcurrentMultiUserModeEnabled) {
+ final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
+ return mUserManagerInternal.getUserAssignedToDisplay(displayId);
+ }
+ return mCurrentUserId;
+ }
+
final Context mContext;
final Resources mRes;
private final Handler mHandler;
@@ -372,7 +423,7 @@
@GuardedBy("ImfLock.class")
@MultiUserUnawareField
@NonNull
- private final ImeVisibilityStateComputer mVisibilityStateComputer;
+ final ImeVisibilityStateComputer mVisibilityStateComputer;
@GuardedBy("ImfLock.class")
@SharedByAllUsersField
@@ -495,14 +546,6 @@
}
/**
- * The last window token that we confirmed that IME started talking to. This is always updated
- * upon reports from the input method. If the window state is already changed before the report
- * is handled, this field just keeps the last value.
- */
- @MultiUserUnawareField
- IBinder mLastImeTargetWindow;
-
- /**
* Map of window perceptible states indexed by their associated window tokens.
*
* The value {@code true} indicates that IME has not been mostly hidden via
@@ -571,7 +614,7 @@
private void onSecureSettingsChangedLocked(@NonNull String key, @UserIdInt int userId) {
switch (key) {
case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: {
- if (!Flags.imeSwitcherRevamp()) {
+ if (!mNewInputMethodSwitcherMenuEnabled) {
if (userId == mCurrentUserId) {
mMenuController.updateKeyboardFromSettingsLocked();
}
@@ -640,7 +683,7 @@
}
}
}
- if (Flags.imeSwitcherRevamp()) {
+ if (mNewInputMethodSwitcherMenuEnabled) {
synchronized (ImfLock.class) {
final var bindingController = getInputMethodBindingController(senderUserId);
mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(),
@@ -944,7 +987,7 @@
// TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
// additional subtypes in switchUserOnHandlerLocked().
final ServiceThread thread = new ServiceThread(HANDLER_THREAD_NAME,
- Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+ Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
thread.start();
final ServiceThread ioThread = new ServiceThread(PACKAGE_MONITOR_THREAD_NAME,
@@ -1063,8 +1106,8 @@
for (int userId : userIds) {
Slog.d(TAG, "Start initialization for user=" + userId);
- final var additionalSubtypeMap =
- AdditionalSubtypeMapRepository.ensureInitializedAndGet(userId);
+ AdditionalSubtypeMapRepository.initializeIfNecessary(userId);
+ final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
final var settings = InputMethodManagerService.queryInputMethodServicesInternal(
context, userId, additionalSubtypeMap,
DirectBootAwareness.AUTO).getMethodMap();
@@ -1131,6 +1174,7 @@
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
+ mNewInputMethodSwitcherMenuEnabled = shouldEnableNewInputMethodSwitcherMenu(mContext);
mShowOngoingImeSwitcherForPhones = false;
@@ -1143,7 +1187,7 @@
: bindingControllerFactory);
mMenuController = new InputMethodMenuController(this);
- mMenuControllerNew = Flags.imeSwitcherRevamp()
+ mMenuControllerNew = mNewInputMethodSwitcherMenuEnabled
? new InputMethodMenuControllerNew() : null;
mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
mVisibilityApplier = new DefaultImeVisibilityApplier(this);
@@ -1764,7 +1808,7 @@
ImeTracker.PHASE_SERVER_WAIT_IME);
userData.mCurStatsToken = null;
// TODO: Make mMenuController multi-user aware
- if (Flags.imeSwitcherRevamp()) {
+ if (mNewInputMethodSwitcherMenuEnabled) {
mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
} else {
mMenuController.hideInputMethodMenuLocked();
@@ -1807,16 +1851,6 @@
}
@GuardedBy("ImfLock.class")
- void clearInputShownLocked() {
- mVisibilityStateComputer.setInputShown(false);
- }
-
- @GuardedBy("ImfLock.class")
- private boolean isInputShownLocked() {
- return mVisibilityStateComputer.isInputShown();
- }
-
- @GuardedBy("ImfLock.class")
private boolean isShowRequestedForCurrentWindow(@UserIdInt int userId) {
final var userData = getUserData(userId);
// TODO(b/349904272): Make mVisibilityStateComputer multi-user aware
@@ -2589,7 +2623,7 @@
if (!mShowOngoingImeSwitcherForPhones) return false;
// When the IME switcher dialog is shown, the IME switcher button should be hidden.
// TODO(b/305849394): Make mMenuController multi-user aware.
- final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
+ final boolean switcherMenuShowing = mNewInputMethodSwitcherMenuEnabled
? mMenuControllerNew.isShowing()
: mMenuController.getSwitchingDialogLocked() != null;
if (switcherMenuShowing) {
@@ -2609,7 +2643,8 @@
|| (visibility & InputMethodService.IME_INVISIBLE) != 0) {
return false;
}
- if (mWindowManagerInternal.isHardKeyboardAvailable() && !Flags.imeSwitcherRevamp()) {
+ if (mWindowManagerInternal.isHardKeyboardAvailable()
+ && !mNewInputMethodSwitcherMenuEnabled) {
// When physical keyboard is attached, we show the ime switcher (or notification if
// NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently
// exists in the IME switcher dialog. Might be OK to remove this condition once
@@ -2620,7 +2655,7 @@
}
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- if (Flags.imeSwitcherRevamp()) {
+ if (mNewInputMethodSwitcherMenuEnabled) {
// The IME switcher button should be shown when the current IME specified a
// language settings activity.
final var curImi = settings.getMethodMap().get(settings.getSelectedInputMethod());
@@ -2739,20 +2774,18 @@
if (targetWindow != null) {
mWindowManagerInternal.updateInputMethodTargetWindow(token, targetWindow);
}
- mLastImeTargetWindow = targetWindow;
+ mVisibilityStateComputer.setLastImeTargetWindow(targetWindow);
}
}
- private void updateImeWindowStatus(boolean disableImeIcon) {
- synchronized (ImfLock.class) {
- // TODO(b/350386877): Propagate userId from the caller.
- final int userId = mCurrentUserId;
- if (disableImeIcon) {
- final var bindingController = getInputMethodBindingController(userId);
- updateSystemUiLocked(0, bindingController.getBackDisposition(), userId);
- } else {
- updateSystemUiLocked(userId);
- }
+ @GuardedBy("ImfLock.class")
+ private void updateImeWindowStatusLocked(boolean disableImeIcon, int displayId) {
+ final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
+ if (disableImeIcon) {
+ final var bindingController = getInputMethodBindingController(userId);
+ updateSystemUiLocked(0, bindingController.getBackDisposition(), userId);
+ } else {
+ updateSystemUiLocked(userId);
}
}
@@ -2798,7 +2831,7 @@
}
final var curId = bindingController.getCurId();
// TODO(b/305849394): Make mMenuController multi-user aware.
- final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
+ final boolean switcherMenuShowing = mNewInputMethodSwitcherMenuEnabled
? mMenuControllerNew.isShowing()
: mMenuController.getSwitchingDialogLocked() != null;
if (switcherMenuShowing
@@ -2820,7 +2853,7 @@
@GuardedBy("ImfLock.class")
void updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
updateInputMethodsFromSettingsLocked(enabledMayChange, userId);
- if (!Flags.imeSwitcherRevamp()) {
+ if (!mNewInputMethodSwitcherMenuEnabled) {
mMenuController.updateKeyboardFromSettingsLocked();
}
}
@@ -3051,7 +3084,7 @@
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
if (Flags.refactorInsetsController()) {
- boolean wasVisible = isInputShownLocked();
+ boolean wasVisible = mVisibilityStateComputer.isInputShown();
if (userData.mImeBindingState != null
&& userData.mImeBindingState.mFocusedWindowClient != null
&& userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
@@ -3081,8 +3114,7 @@
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#showSoftInput", mDumper);
synchronized (ImfLock.class) {
- // TODO(b/305849394): Infer userId from windowToken
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
final long ident = Binder.clearCallingIdentity();
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
@@ -3102,8 +3134,7 @@
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#hideSoftInput", mDumper);
synchronized (ImfLock.class) {
- // TODO(b/305849394): Infer userId from windowToken
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
final long ident = Binder.clearCallingIdentity();
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
@@ -3365,14 +3396,14 @@
Objects.requireNonNull(windowToken, "windowToken must not be null");
synchronized (ImfLock.class) {
Boolean windowPerceptible = mFocusedWindowPerceptible.get(windowToken);
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
final var userData = getUserData(userId);
if (userData.mImeBindingState.mFocusedWindow != windowToken
|| (windowPerceptible != null && windowPerceptible == perceptible)) {
return;
}
mFocusedWindowPerceptible.put(windowToken, windowPerceptible);
- updateSystemUiLocked(mCurrentUserId);
+ updateSystemUiLocked(userId);
}
});
}
@@ -3488,7 +3519,7 @@
final int callingUserId = UserHandle.getUserId(uid);
final int userId = resolveImeUserIdLocked(callingUserId);
if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken, userId)) {
- if (isInputShownLocked()) {
+ if (mVisibilityStateComputer.isInputShown()) {
ImeTracker.forLogging().onFailed(
statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
} else {
@@ -3506,7 +3537,7 @@
if (userData.mImeBindingState != null
&& userData.mImeBindingState.mFocusedWindowClient != null
&& userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
- boolean wasVisible = isInputShownLocked();
+ boolean wasVisible = mVisibilityStateComputer.isInputShown();
// TODO add windowToken to interface
userData.mImeBindingState.mFocusedWindowClient.mClient
.setImeVisibility(false, statsToken);
@@ -3568,7 +3599,7 @@
// TODO(b/246309664): Clean up IMMS#mImeWindowVis
IInputMethodInvoker curMethod = bindingController.getCurMethod();
final boolean shouldHideSoftInput = curMethod != null
- && (isInputShownLocked()
+ && (mVisibilityStateComputer.isInputShown()
|| (bindingController.getImeWindowVis() & InputMethodService.IME_ACTIVE) != 0);
mVisibilityStateComputer.requestImeVisibility(windowToken, false);
@@ -3977,7 +4008,7 @@
@IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
public boolean isInputMethodPickerShownForTest() {
synchronized (ImfLock.class) {
- return Flags.imeSwitcherRevamp()
+ return mNewInputMethodSwitcherMenuEnabled
? mMenuControllerNew.isShowing()
: mMenuController.isisInputMethodPickerShownForTestLocked();
}
@@ -4056,7 +4087,7 @@
@Override
public void onImeSwitchButtonClickFromSystem(int displayId) {
synchronized (ImfLock.class) {
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
final var userData = getUserData(userId);
final var bindingController = userData.mBindingController;
final var curToken = bindingController.getCurToken();
@@ -4665,8 +4696,8 @@
proto.write(CUR_SEQ, bindingController.getSequenceNumber());
proto.write(CUR_CLIENT, Objects.toString(userData.mCurClient));
userData.mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
- proto.write(LAST_IME_TARGET_WINDOW_NAME,
- mWindowManagerInternal.getWindowName(mLastImeTargetWindow));
+ proto.write(LAST_IME_TARGET_WINDOW_NAME, mWindowManagerInternal.getWindowName(
+ mVisibilityStateComputer.getLastImeTargetWindow()));
proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(
userData.mImeBindingState.mFocusedWindowSoftInputMode));
if (userData.mCurEditorInfo != null) {
@@ -4683,7 +4714,7 @@
proto.write(IS_INTERACTIVE, mIsInteractive);
proto.write(BACK_DISPOSITION, bindingController.getBackDisposition());
proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis());
- if (!Flags.imeSwitcherRevamp()) {
+ if (!mNewInputMethodSwitcherMenuEnabled) {
proto.write(SHOW_IME_WITH_HARD_KEYBOARD,
mMenuController.getShowImeWithHardKeyboard());
}
@@ -4840,8 +4871,8 @@
.setImeVisibility(false, statsToken);
}
} else {
- hideCurrentInputLocked(mLastImeTargetWindow, statsToken, flags,
- null /* resultReceiver */, reason, userId);
+ hideCurrentInputLocked(mVisibilityStateComputer.getLastImeTargetWindow(),
+ statsToken, flags, null /* resultReceiver */, reason, userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4879,9 +4910,9 @@
.setImeVisibility(true, statsToken);
}
} else {
- showCurrentInputLocked(mLastImeTargetWindow, statsToken, flags,
- MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason,
- userId);
+ showCurrentInputLocked(mVisibilityStateComputer.getLastImeTargetWindow(),
+ statsToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN,
+ null /* resultReceiver */, reason, userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4901,8 +4932,7 @@
@GuardedBy("ImfLock.class")
void onApplyImeVisibilityFromComputerLocked(IBinder windowToken,
@NonNull ImeTracker.Token statsToken, @NonNull ImeVisibilityResult result) {
- // TODO(b/305849394): Infer userId from windowToken
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(),
result.getReason(), userId);
}
@@ -4974,7 +5004,7 @@
// implemented so that auxiliary subtypes will be excluded when the soft
// keyboard is invisible.
synchronized (ImfLock.class) {
- showAuxSubtypes = isInputShownLocked();
+ showAuxSubtypes = mVisibilityStateComputer.isInputShown();
}
break;
case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
@@ -5009,7 +5039,7 @@
return false;
}
- if (Flags.imeSwitcherRevamp()) {
+ if (mNewInputMethodSwitcherMenuEnabled) {
if (DEBUG) {
Slog.v(TAG, "Show IME switcher menu,"
+ " showAuxSubtypes=" + showAuxSubtypes
@@ -5078,8 +5108,7 @@
case MSG_REMOVE_IME_SURFACE_FROM_WINDOW: {
IBinder windowToken = (IBinder) msg.obj;
synchronized (ImfLock.class) {
- // TODO(b/305849394): Infer userId from windowToken.
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
final var userData = getUserData(userId);
try {
if (windowToken == userData.mImeBindingState.mFocusedWindow
@@ -5092,10 +5121,6 @@
}
return true;
}
- case MSG_UPDATE_IME_WINDOW_STATUS: {
- updateImeWindowStatus(msg.arg1 == 1);
- return true;
- }
// ---------------------------------------------------------
@@ -5105,7 +5130,7 @@
// --------------------------------------------------------------
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
- if (!Flags.imeSwitcherRevamp()) {
+ if (!mNewInputMethodSwitcherMenuEnabled) {
mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
}
synchronized (ImfLock.class) {
@@ -5883,8 +5908,7 @@
@Override
public void reportImeControl(@Nullable IBinder windowToken) {
synchronized (ImfLock.class) {
- // TODO(b/305849394): Need to infer userId or get userId from callers.
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
final var userData = getUserData(userId);
if (userData.mImeBindingState.mFocusedWindow != windowToken) {
// A perceptible value was set for the focused window, but it is no longer in
@@ -5899,14 +5923,14 @@
@Override
public void onImeParentChanged(int displayId) {
synchronized (ImfLock.class) {
- // TODO(b/305849394): Need to infer userId or get userId from callers.
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
final var userData = getUserData(userId);
// Hide the IME method menu only when the IME surface parent is changed by the
// input target changed, in case seeing the dialog dismiss flickering during
// the next focused window starting the input connection.
- if (mLastImeTargetWindow != userData.mImeBindingState.mFocusedWindow) {
- if (Flags.imeSwitcherRevamp()) {
+ if (mVisibilityStateComputer.getLastImeTargetWindow()
+ != userData.mImeBindingState.mFocusedWindow) {
+ if (mNewInputMethodSwitcherMenuEnabled) {
final var bindingController = getInputMethodBindingController(userId);
mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
} else {
@@ -5925,8 +5949,11 @@
@ImfLockFree
@Override
public void updateImeWindowStatus(boolean disableImeIcon, int displayId) {
- mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0)
- .sendToTarget();
+ mHandler.post(() -> {
+ synchronized (ImfLock.class) {
+ updateImeWindowStatusLocked(disableImeIcon, displayId);
+ }
+ });
}
@Override
@@ -6024,8 +6051,8 @@
public void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
IBinder targetWindowToken) {
synchronized (ImfLock.class) {
- // TODO(b/305849394): Infer userId from displayId
- switchKeyboardLayoutLocked(direction, getUserData(mCurrentUserId));
+ final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
+ switchKeyboardLayoutLocked(direction, getUserData(userId));
}
}
}
@@ -6259,7 +6286,7 @@
};
mUserDataRepository.forAllUserData(userDataDump);
- if (Flags.imeSwitcherRevamp()) {
+ if (mNewInputMethodSwitcherMenuEnabled) {
p.println(" menuControllerNew:");
mMenuControllerNew.dump(p, " ");
} else {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
index 1b84036..4f5af63 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -19,15 +19,13 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
final class InputMethodSettingsRepository {
- // TODO(b/352594784): Should we user other lock primitives?
- @GuardedBy("sPerUserMap")
+ private static final Object sMutationLock = new Object();
+
@NonNull
- private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+ private static volatile ImmutableSparseArray<InputMethodSettings> sPerUserMap =
+ ImmutableSparseArray.empty();
/**
* Not intended to be instantiated.
@@ -38,10 +36,7 @@
@NonNull
@AnyThread
static InputMethodSettings get(@UserIdInt int userId) {
- final InputMethodSettings obj;
- synchronized (sPerUserMap) {
- obj = sPerUserMap.get(userId);
- }
+ final InputMethodSettings obj = sPerUserMap.get(userId);
if (obj != null) {
return obj;
}
@@ -50,15 +45,15 @@
@AnyThread
static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
- synchronized (sPerUserMap) {
- sPerUserMap.put(userId, obj);
+ synchronized (sMutationLock) {
+ sPerUserMap = sPerUserMap.cloneWithPutOrSelf(userId, obj);
}
}
@AnyThread
static void remove(@UserIdInt int userId) {
- synchronized (sPerUserMap) {
- sPerUserMap.remove(userId);
+ synchronized (sMutationLock) {
+ sPerUserMap = sPerUserMap.cloneWithRemoveOrSelf(userId);
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
index e7cff20..b3500be 100644
--- a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
+++ b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
@@ -24,7 +24,6 @@
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
@@ -38,6 +37,13 @@
* to the persistent value when the user storage is unlocked.</p>
*/
final class SecureSettingsWrapper {
+
+ private static final Object sMutationLock = new Object();
+
+ @NonNull
+ private static volatile ImmutableSparseArray<ReaderWriter> sUserMap =
+ ImmutableSparseArray.empty();
+
@Nullable
private static volatile ContentResolver sContentResolver = null;
@@ -61,8 +67,8 @@
*/
@AnyThread
static void endTestMode() {
- synchronized (sUserMap) {
- sUserMap.clear();
+ synchronized (sMutationLock) {
+ sUserMap = ImmutableSparseArray.empty();
}
sTestMode = false;
}
@@ -243,10 +249,6 @@
}
}
- @GuardedBy("sUserMap")
- @NonNull
- private static final SparseArray<ReaderWriter> sUserMap = new SparseArray<>();
-
private static final ReaderWriter NOOP = new ReaderWriter() {
@Override
public void putString(String key, String str) {
@@ -282,15 +284,15 @@
private static ReaderWriter putOrGet(@UserIdInt int userId,
@NonNull ReaderWriter readerWriter) {
final boolean isUnlockedUserImpl = readerWriter instanceof UnlockedUserImpl;
- synchronized (sUserMap) {
+ synchronized (sMutationLock) {
final ReaderWriter current = sUserMap.get(userId);
if (current == null) {
- sUserMap.put(userId, readerWriter);
+ sUserMap = sUserMap.cloneWithPutOrSelf(userId, readerWriter);
return readerWriter;
}
// Upgrading from CopyOnWriteImpl to DirectImpl is allowed.
if (current instanceof LockedUserImpl && isUnlockedUserImpl) {
- sUserMap.put(userId, readerWriter);
+ sUserMap = sUserMap.cloneWithPutOrSelf(userId, readerWriter);
return readerWriter;
}
return current;
@@ -300,11 +302,9 @@
@NonNull
@AnyThread
private static ReaderWriter get(@UserIdInt int userId) {
- synchronized (sUserMap) {
- final ReaderWriter readerWriter = sUserMap.get(userId);
- if (readerWriter != null) {
- return readerWriter;
- }
+ final ReaderWriter readerWriter = sUserMap.get(userId);
+ if (readerWriter != null) {
+ return readerWriter;
}
if (sTestMode) {
return putOrGet(userId, new FakeReaderWriterImpl());
@@ -363,8 +363,8 @@
*/
@AnyThread
static void onUserRemoved(@UserIdInt int userId) {
- synchronized (sUserMap) {
- sUserMap.remove(userId);
+ synchronized (sMutationLock) {
+ sUserMap = sUserMap.cloneWithRemoveOrSelf(userId);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 6f831cc..e3524b1 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -19,51 +19,39 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.util.SparseArray;
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.function.IntFunction;
final class UserDataRepository {
- private final ReentrantReadWriteLock mUserDataLock = new ReentrantReadWriteLock();
+ private final Object mMutationLock = new Object();
- @GuardedBy("mUserDataLock")
- private final SparseArray<UserData> mUserData = new SparseArray<>();
+ @NonNull
+ private volatile ImmutableSparseArray<UserData> mUserData = ImmutableSparseArray.empty();
private final IntFunction<InputMethodBindingController> mBindingControllerFactory;
@AnyThread
@NonNull
UserData getOrCreate(@UserIdInt int userId) {
- mUserDataLock.writeLock().lock();
- try {
- UserData userData = mUserData.get(userId);
- if (userData == null) {
- userData = new UserData(userId, mBindingControllerFactory.apply(userId));
- mUserData.put(userId, userData);
- }
+ // Do optimistic read first for optimization.
+ final var userData = mUserData.get(userId);
+ if (userData != null) {
return userData;
- } finally {
- mUserDataLock.writeLock().unlock();
+ }
+ // Note that the below line can be called concurrently. Here we assume that
+ // instantiating UserData for the same user multiple times would have no side effect.
+ final var newUserData = new UserData(userId, mBindingControllerFactory.apply(userId));
+ synchronized (mMutationLock) {
+ mUserData = mUserData.cloneWithPutOrSelf(userId, newUserData);
+ return newUserData;
}
}
@AnyThread
void forAllUserData(Consumer<UserData> consumer) {
- final SparseArray<UserData> copiedArray;
- mUserDataLock.readLock().lock();
- try {
- copiedArray = mUserData.clone();
- } finally {
- mUserDataLock.readLock().unlock();
- }
- for (int i = 0; i < copiedArray.size(); i++) {
- consumer.accept(copiedArray.valueAt(i));
- }
+ mUserData.forEach(consumer);
}
UserDataRepository(
@@ -73,11 +61,8 @@
@AnyThread
void remove(@UserIdInt int userId) {
- mUserDataLock.writeLock().lock();
- try {
- mUserData.remove(userId);
- } finally {
- mUserDataLock.writeLock().unlock();
+ synchronized (mMutationLock) {
+ mUserData = mUserData.cloneWithRemoveOrSelf(userId);
}
}
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 8d3f07e..a17c48d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3264,6 +3264,7 @@
/**
* Tries to restore the disabled system package after an update has been deleted.
*/
+ @GuardedBy("mPm.mInstallLock")
public void restoreDisabledSystemPackageLIF(DeletePackageAction action,
@NonNull int[] allUserHandles, boolean writeSettings) throws SystemDeleteException {
final PackageSetting deletedPs = action.mDeletingPs;
@@ -3282,10 +3283,21 @@
}
// Install the system package
if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
- try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
+ try {
final int[] origUsers = outInfo == null ? null : outInfo.mOrigUsers;
- installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles,
- origUsers, writeSettings);
+ try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
+ installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles,
+ origUsers, writeSettings);
+ }
+ if (origUsers != null) {
+ mPm.commitPackageStateMutation(null, mutator -> {
+ for (int userId : origUsers) {
+ mutator.forPackage(disabledPs.getPackageName())
+ .userState(userId)
+ .setOverlayPaths(deletedPs.getOverlayPaths(userId));
+ }
+ });
+ }
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to restore system package:" + deletedPs.getPackageName() + ": "
+ e.getMessage());
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index bca81f52..c21f783 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -64,6 +64,7 @@
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
+import static com.android.server.stats.Flags.applyNetworkStatsPollRateLimit;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
@@ -298,6 +299,13 @@
*/
private static final long NETSTATS_UID_DEFAULT_BUCKET_DURATION_MS = HOURS.toMillis(2);
+ /**
+ * Polling NetworkStats is a heavy operation and it should be done sparingly. Atom pulls may
+ * happen in bursts, but these should be infrequent. The poll rate limit ensures that data is
+ * sufficiently fresh (i.e. not stale) while reducing system load during atom pull bursts.
+ */
+ private static final long NETSTATS_POLL_RATE_LIMIT_MS = 15000;
+
private static final int CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES = 8;
private static final int OP_FLAGS_PULLED = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED;
private static final String COMMON_PERMISSION_PREFIX = "android.permission.";
@@ -415,6 +423,9 @@
@GuardedBy("mDataBytesTransferLock")
private final ArrayList<NetworkStatsExt> mNetworkStatsBaselines = new ArrayList<>();
+ @GuardedBy("mDataBytesTransferLock")
+ private long mLastNetworkStatsPollTime = -NETSTATS_POLL_RATE_LIMIT_MS;
+
// Listener for monitoring subscriptions changed event.
private StatsSubscriptionsListener mStatsSubscriptionsListener;
// List that stores SubInfo of subscriptions that ever appeared since boot.
@@ -1063,24 +1074,26 @@
// Initialize NetworkStats baselines.
synchronized (mDataBytesTransferLock) {
mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER));
+ collectNetworkStatsSnapshotForAtomLocked(
+ FrameworkStatsLog.WIFI_BYTES_TRANSFER));
mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(
+ collectNetworkStatsSnapshotForAtomLocked(
FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG));
mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER));
- mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+ collectNetworkStatsSnapshotForAtomLocked(
+ FrameworkStatsLog.MOBILE_BYTES_TRANSFER));
+ mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtomLocked(
FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG));
- mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+ mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtomLocked(
FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED));
mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(
+ collectNetworkStatsSnapshotForAtomLocked(
FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER));
mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(
+ collectNetworkStatsSnapshotForAtomLocked(
FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER));
if (canQueryTypeProxy) {
- mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+ mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtomLocked(
FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG));
}
}
@@ -1243,12 +1256,14 @@
);
}
+ @GuardedBy("mDataBytesTransferLock")
@NonNull
- private List<NetworkStatsExt> collectNetworkStatsSnapshotForAtom(int atomTag) {
+ private List<NetworkStatsExt> collectNetworkStatsSnapshotForAtomLocked(int atomTag) {
List<NetworkStatsExt> ret = new ArrayList<>();
switch (atomTag) {
case FrameworkStatsLog.WIFI_BYTES_TRANSFER: {
- final NetworkStats stats = getUidNetworkStatsSnapshotForTransport(TRANSPORT_WIFI);
+ final NetworkStats stats = getUidNetworkStatsSnapshotForTransportLocked(
+ TRANSPORT_WIFI);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
new int[]{TRANSPORT_WIFI}, /*slicedByFgbg=*/false));
@@ -1256,7 +1271,8 @@
break;
}
case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
- final NetworkStats stats = getUidNetworkStatsSnapshotForTransport(TRANSPORT_WIFI);
+ final NetworkStats stats = getUidNetworkStatsSnapshotForTransportLocked(
+ TRANSPORT_WIFI);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
new int[]{TRANSPORT_WIFI}, /*slicedByFgbg=*/true));
@@ -1265,7 +1281,7 @@
}
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER: {
final NetworkStats stats =
- getUidNetworkStatsSnapshotForTransport(TRANSPORT_CELLULAR);
+ getUidNetworkStatsSnapshotForTransportLocked(TRANSPORT_CELLULAR);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/false));
@@ -1274,7 +1290,7 @@
}
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
final NetworkStats stats =
- getUidNetworkStatsSnapshotForTransport(TRANSPORT_CELLULAR);
+ getUidNetworkStatsSnapshotForTransportLocked(TRANSPORT_CELLULAR);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true));
@@ -1282,7 +1298,7 @@
break;
}
case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: {
- final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate(
+ final NetworkStats stats = getUidNetworkStatsSnapshotForTemplateLocked(
new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/false);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
@@ -1294,9 +1310,9 @@
break;
}
case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED: {
- final NetworkStats wifiStats = getUidNetworkStatsSnapshotForTemplate(
+ final NetworkStats wifiStats = getUidNetworkStatsSnapshotForTemplateLocked(
new NetworkTemplate.Builder(MATCH_WIFI).build(), /*includeTags=*/true);
- final NetworkStats cellularStats = getUidNetworkStatsSnapshotForTemplate(
+ final NetworkStats cellularStats = getUidNetworkStatsSnapshotForTemplateLocked(
new NetworkTemplate.Builder(MATCH_MOBILE)
.setMeteredness(METERED_YES).build(), /*includeTags=*/true);
if (wifiStats != null && cellularStats != null) {
@@ -1311,12 +1327,12 @@
}
case FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER: {
for (final SubInfo subInfo : mHistoricalSubs) {
- ret.addAll(getDataUsageBytesTransferSnapshotForSub(subInfo));
+ ret.addAll(getDataUsageBytesTransferSnapshotForSubLocked(subInfo));
}
break;
}
case FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER: {
- ret.addAll(getDataUsageBytesTransferSnapshotForOemManaged());
+ ret.addAll(getDataUsageBytesTransferSnapshotForOemManagedLocked());
break;
}
default:
@@ -1325,8 +1341,9 @@
return ret;
}
+ @GuardedBy("mDataBytesTransferLock")
private int pullDataBytesTransferLocked(int atomTag, @NonNull List<StatsEvent> pulledData) {
- final List<NetworkStatsExt> current = collectNetworkStatsSnapshotForAtom(atomTag);
+ final List<NetworkStatsExt> current = collectNetworkStatsSnapshotForAtomLocked(atomTag);
if (current == null) {
Slog.e(TAG, "current snapshot is null for " + atomTag + ", return.");
@@ -1459,8 +1476,9 @@
}
}
+ @GuardedBy("mDataBytesTransferLock")
@NonNull
- private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForOemManaged() {
+ private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForOemManagedLocked() {
final List<Pair<Integer, Integer>> matchRulesAndTransports = List.of(
new Pair(MATCH_ETHERNET, TRANSPORT_ETHERNET),
new Pair(MATCH_MOBILE, TRANSPORT_CELLULAR),
@@ -1479,7 +1497,8 @@
// Thus, specifying networks through their identifiers are not needed.
final NetworkTemplate template = new NetworkTemplate.Builder(matchRule)
.setOemManaged(oemManaged).build();
- final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate(template, false);
+ final NetworkStats stats = getUidNetworkStatsSnapshotForTemplateLocked(
+ template, false);
final Integer transport = ruleAndTransport.second;
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
@@ -1496,8 +1515,9 @@
/**
* Create a snapshot of NetworkStats for a given transport.
*/
+ @GuardedBy("mDataBytesTransferLock")
@Nullable
- private NetworkStats getUidNetworkStatsSnapshotForTransport(int transport) {
+ private NetworkStats getUidNetworkStatsSnapshotForTransportLocked(int transport) {
NetworkTemplate template = null;
switch (transport) {
case TRANSPORT_CELLULAR:
@@ -1510,7 +1530,7 @@
default:
Log.wtf(TAG, "Unexpected transport.");
}
- return getUidNetworkStatsSnapshotForTemplate(template, /*includeTags=*/false);
+ return getUidNetworkStatsSnapshotForTemplateLocked(template, /*includeTags=*/false);
}
/**
@@ -1534,8 +1554,9 @@
* Note that this should be only used to calculate diff since the snapshot might contains
* some traffic before boot.
*/
+ @GuardedBy("mDataBytesTransferLock")
@Nullable
- private NetworkStats getUidNetworkStatsSnapshotForTemplate(
+ private NetworkStats getUidNetworkStatsSnapshotForTemplateLocked(
@NonNull NetworkTemplate template, boolean includeTags) {
final long elapsedMillisSinceBoot = SystemClock.elapsedRealtime();
final long currentTimeInMillis = MICROSECONDS.toMillis(SystemClock.currentTimeMicro());
@@ -1547,13 +1568,19 @@
final long startTime = currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration;
final long endTime = currentTimeInMillis + bucketDuration;
- // TODO (b/156313635): This is short-term hack to allow perfd gets updated networkStats
- // history when query in every second in order to show realtime statistics. However,
- // this is not a good long-term solution since NetworkStatsService will make frequent
- // I/O and also block main thread when polling.
- // Consider making perfd queries NetworkStatsService directly.
- if (template.getMatchRule() == MATCH_WIFI && template.getSubscriberIds().isEmpty()) {
- getNetworkStatsManager().forceUpdate();
+ // NetworkStatsManager#forceUpdate updates stats for all networks
+ if (applyNetworkStatsPollRateLimit()) {
+ // The new way: rate-limit force-polling for all NetworkStats queries
+ if (elapsedMillisSinceBoot - mLastNetworkStatsPollTime >= NETSTATS_POLL_RATE_LIMIT_MS) {
+ mLastNetworkStatsPollTime = elapsedMillisSinceBoot;
+ getNetworkStatsManager().forceUpdate();
+ }
+ } else {
+ // The old way: force-poll only on WiFi queries. Data for other queries can be stale
+ // if there was no recent poll beforehand (e.g. for WiFi or scheduled poll)
+ if (template.getMatchRule() == MATCH_WIFI && template.getSubscriberIds().isEmpty()) {
+ getNetworkStatsManager().forceUpdate();
+ }
}
final android.app.usage.NetworkStats queryNonTaggedStats =
@@ -1572,8 +1599,9 @@
return nonTaggedStats.add(taggedStats);
}
+ @GuardedBy("mDataBytesTransferLock")
@NonNull
- private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForSub(
+ private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForSubLocked(
@NonNull SubInfo subInfo) {
final List<NetworkStatsExt> ret = new ArrayList<>();
for (final int ratType : getAllCollapsedRatTypes()) {
@@ -1583,7 +1611,7 @@
.setRatType(ratType)
.setMeteredness(METERED_YES).build();
final NetworkStats stats =
- getUidNetworkStatsSnapshotForTemplate(template, /*includeTags=*/false);
+ getUidNetworkStatsSnapshotForTemplateLocked(template, /*includeTags=*/false);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByFgbg(stats),
new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true,
@@ -5273,6 +5301,13 @@
@Override
public void onSubscriptionsChanged() {
+ synchronized (mDataBytesTransferLock) {
+ onSubscriptionsChangedLocked();
+ }
+ }
+
+ @GuardedBy("mDataBytesTransferLock")
+ private void onSubscriptionsChangedLocked() {
final List<SubscriptionInfo> currentSubs = mSm.getCompleteActiveSubscriptionInfoList();
for (final SubscriptionInfo sub : currentSubs) {
final SubInfo match = CollectionUtils.find(mHistoricalSubs,
@@ -5295,12 +5330,11 @@
subscriberId, sub.isOpportunistic());
Slog.i(TAG, "subId " + subId + " added into historical sub list");
- synchronized (mDataBytesTransferLock) {
- mHistoricalSubs.add(subInfo);
- // Since getting snapshot when pulling will also include data before boot,
- // query stats as baseline to prevent double count is needed.
- mNetworkStatsBaselines.addAll(getDataUsageBytesTransferSnapshotForSub(subInfo));
- }
+ mHistoricalSubs.add(subInfo);
+ // Since getting snapshot when pulling will also include data before boot,
+ // query stats as baseline to prevent double count is needed.
+ mNetworkStatsBaselines.addAll(
+ getDataUsageBytesTransferSnapshotForSubLocked(subInfo));
}
}
}
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index 6faa273..f360837 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -8,3 +8,11 @@
bug: "309512867"
is_fixed_read_only: true
}
+
+flag {
+ name: "apply_network_stats_poll_rate_limit"
+ namespace: "statsd"
+ description: "Apply a rate limit for polling network stats when pulling relevant atoms"
+ bug: "352495181"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
index aa2b74e..58c3ba5 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
@@ -56,12 +56,17 @@
// enables immediate failover to a secondary provider, one that might provide valid IDs for
// the same location, which should provide better behavior than just ignoring the event.
if (hasInvalidZones(event)) {
- TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder(
- event.getTimeZoneProviderStatus())
- .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
- .build();
- return TimeZoneProviderEvent.createUncertainEvent(
- event.getCreationElapsedMillis(), providerStatus);
+ TimeZoneProviderStatus providerStatus = event.getTimeZoneProviderStatus();
+ TimeZoneProviderStatus.Builder providerStatusBuilder;
+ if (providerStatus != null) {
+ providerStatusBuilder = new TimeZoneProviderStatus.Builder(providerStatus);
+ } else {
+ providerStatusBuilder = new TimeZoneProviderStatus.Builder();
+ }
+ return TimeZoneProviderEvent.createUncertainEvent(event.getCreationElapsedMillis(),
+ providerStatusBuilder
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
+ .build());
}
return event;
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 4da6585..de5e662 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -41,6 +41,7 @@
import android.os.SystemClock;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.Slog;
@@ -265,9 +266,15 @@
return null;
}
+ if (Flags.throttleVibrationParamsRequests() && mVibrationParamRequest != null
+ && mVibrationParamRequest.usage == usage) {
+ // Reuse existing future for ongoing request with same usage.
+ return mVibrationParamRequest.future;
+ }
+
try {
endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
- mVibrationParamRequest = new VibrationParamRequest(uid);
+ mVibrationParamRequest = new VibrationParamRequest(uid, usage);
vibratorController.requestVibrationParams(vibrationType, timeoutInMillis,
mVibrationParamRequest.token);
return mVibrationParamRequest.future;
@@ -533,10 +540,12 @@
public final CompletableFuture<Void> future = new CompletableFuture<>();
public final IBinder token = new Binder();
public final int uid;
+ public final @VibrationAttributes.Usage int usage;
public final long uptimeMs;
- VibrationParamRequest(int uid) {
+ VibrationParamRequest(int uid, @VibrationAttributes.Usage int usage) {
this.uid = uid;
+ this.usage = usage;
uptimeMs = SystemClock.uptimeMillis();
}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index ba74f50..59435b8 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -456,10 +456,6 @@
}
}
- InputChannel getInputChannel() {
- return mInputInterceptor == null ? null : mInputInterceptor.mClientChannel;
- }
-
InputWindowHandle getInputWindowHandle() {
return mInputInterceptor == null ? null : mInputInterceptor.mDragWindowHandle;
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 872b4e1..3d07744 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -60,7 +60,6 @@
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
-import com.android.window.flags.Flags;
import java.io.PrintWriter;
@@ -198,14 +197,6 @@
setWakeTransitionReady();
return;
}
- EventLogTags.writeWmSetKeyguardShown(
- displayId,
- keyguardShowing ? 1 : 0,
- aodShowing ? 1 : 0,
- state.mKeyguardGoingAway ? 1 : 0,
- state.mOccluded ? 1 : 0,
- "setKeyguardShown");
-
// Update the task snapshot if the screen will not be turned off. To make sure that the
// unlocking animation can animate consistent content. The conditions are:
// - Either AOD or keyguard changes to be showing. So if the states change individually,
@@ -224,6 +215,7 @@
state.mKeyguardShowing = keyguardShowing;
state.mAodShowing = aodShowing;
+ state.writeEventLog("setKeyguardShown");
if (keyguardChanged) {
// Irrelevant to AOD.
@@ -232,19 +224,13 @@
state.mDismissalRequested = false;
}
if (goingAwayRemoved
- || (Flags.keyguardAppearTransition() && keyguardShowing
- && !Display.isOffState(dc.getDisplayInfo().state))) {
+ || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))) {
// Keyguard decided to show or stopped going away. Send a transition to animate back
// to the locked state before holding the sleep token again
- final DisplayContent transitionDc = Flags.keyguardAppearTransition()
- ? dc
- : mRootWindowContainer.getDefaultDisplay();
- transitionDc.requestTransitionAndLegacyPrepare(
+ dc.requestTransitionAndLegacyPrepare(
TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
- if (Flags.keyguardAppearTransition()) {
- dc.mWallpaperController.adjustWallpaperWindows();
- }
- transitionDc.executeAppTransition();
+ dc.mWallpaperController.adjustWallpaperWindows();
+ dc.executeAppTransition();
}
}
@@ -284,13 +270,7 @@
mService.deferWindowLayout();
state.mKeyguardGoingAway = true;
try {
- EventLogTags.writeWmSetKeyguardShown(
- displayId,
- state.mKeyguardShowing ? 1 : 0,
- state.mAodShowing ? 1 : 0,
- 1 /* keyguardGoingAway */,
- state.mOccluded ? 1 : 0,
- "keyguardGoingAway");
+ state.writeEventLog("keyguardGoingAway");
final int transitFlags = convertTransitFlags(flags);
final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
dc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, transitFlags);
@@ -646,6 +626,16 @@
mSleepTokenAcquirer.release(mDisplayId);
}
+ void writeEventLog(String reason) {
+ EventLogTags.writeWmSetKeyguardShown(
+ mDisplayId,
+ mKeyguardShowing ? 1 : 0,
+ mAodShowing ? 1 : 0,
+ mKeyguardGoingAway ? 1 : 0,
+ mOccluded ? 1 : 0,
+ reason);
+ }
+
/**
* Updates keyguard status if the top task could be visible. The top task may occlude
* keyguard, request to dismiss keyguard or make insecure keyguard go away based on its
@@ -716,18 +706,11 @@
boolean hasChange = false;
if (lastOccluded != mOccluded) {
- if (mDisplayId == DEFAULT_DISPLAY) {
- EventLogTags.writeWmSetKeyguardShown(
- mDisplayId,
- mKeyguardShowing ? 1 : 0,
- mAodShowing ? 1 : 0,
- mKeyguardGoingAway ? 1 : 0,
- mOccluded ? 1 : 0,
- "updateVisibility");
- }
+ writeEventLog("updateVisibility");
controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
hasChange = true;
} else if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
+ writeEventLog("dismissIfInsecure");
controller.handleKeyguardGoingAwayChanged(display);
hasChange = true;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ab1e969..1c9aaf9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2287,7 +2287,7 @@
// Apply crop to root tasks only and clear the crops of the descendant tasks.
int width = 0;
int height = 0;
- if (isRootTask()) {
+ if (isRootTask() && !mTransitionController.mIsWaitingForDisplayEnabled) {
final Rect taskBounds = getBounds();
width = taskBounds.width();
height = taskBounds.height();
@@ -3413,6 +3413,7 @@
info.isSleeping = shouldSleepActivities();
info.isTopActivityTransparent = top != null && !top.fillsParent();
info.isTopActivityStyleFloating = top != null && top.isStyleFloating();
+ info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
appCompatTaskInfo.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
appCompatTaskInfo.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
appCompatTaskInfo.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index 34b9913..2c58c61 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -97,10 +97,10 @@
/**
* @return If a window animation has outsets applied to it.
- * @see Animation#hasExtension()
+ * @see Animation#getExtensionEdges()
*/
public boolean hasExtension() {
- return mAnimation.hasExtension();
+ return mAnimation.getExtensionEdges() != 0;
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 88e8064..c6aaf4e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -380,7 +380,7 @@
InputChannel source) {
return state.register(display)
.thenApply(unused ->
- service.startDragAndDrop(source.getToken(), state.getInputChannel()));
+ service.startDragAndDrop(source.getToken(), state.getInputToken()));
}
/**
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index faa198c..8981b7a 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -251,7 +251,7 @@
synchronized (ImfLock.class) {
// Assume the last IME targeted window has requested IME visible
final IBinder lastImeTargetWindowToken = new Binder();
- mInputMethodManagerService.mLastImeTargetWindow = lastImeTargetWindowToken;
+ mComputer.setLastImeTargetWindow(lastImeTargetWindowToken);
mComputer.requestImeVisibility(lastImeTargetWindowToken, true);
final ImeTargetWindowState lastState = mComputer.getWindowStateOrNull(
lastImeTargetWindowToken);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImmutableSparseArrayTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImmutableSparseArrayTest.java
new file mode 100644
index 0000000..944b7c6
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImmutableSparseArrayTest.java
@@ -0,0 +1,278 @@
+/*
+ * 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public final class ImmutableSparseArrayTest {
+
+ @Test
+ public void testEmptyObject() {
+ final ImmutableSparseArray<Object> empty = ImmutableSparseArray.empty();
+
+ assertThat(empty.size()).isEqualTo(0);
+ verifyCommonBehaviors(empty);
+ }
+
+ @Test
+ public void testEmptyMethod() {
+ assertThat(ImmutableSparseArray.empty()).isSameInstanceAs(ImmutableSparseArray.empty());
+ }
+
+ @Test
+ public void testCloneWithPutOrSelf_appendingFromEmpty() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = -2; // intentionally negative
+ final Object value2 = new Object();
+ final int key3 = -3; // intentionally negative
+ final Object value3 = new Object();
+ final int key4 = 4;
+ final Object value4 = new Object();
+
+ final ImmutableSparseArray<Object> oneItemArray = ImmutableSparseArray.empty()
+ .cloneWithPutOrSelf(key1, value1);
+ verifyCommonBehaviors(oneItemArray);
+ assertThat(oneItemArray.size()).isEqualTo(1);
+ assertThat(oneItemArray.get(key1)).isSameInstanceAs(value1);
+
+ final ImmutableSparseArray<Object> twoItemArray =
+ oneItemArray.cloneWithPutOrSelf(key2, value2);
+ assertThat(twoItemArray).isNotSameInstanceAs(oneItemArray);
+ verifyCommonBehaviors(twoItemArray);
+ assertThat(twoItemArray.size()).isEqualTo(2);
+ assertThat(twoItemArray.get(key1)).isSameInstanceAs(value1);
+ assertThat(twoItemArray.get(key2)).isSameInstanceAs(value2);
+
+ final ImmutableSparseArray<Object> threeItemArray =
+ twoItemArray.cloneWithPutOrSelf(key3, value3);
+ assertThat(threeItemArray).isNotSameInstanceAs(twoItemArray);
+ verifyCommonBehaviors(threeItemArray);
+ assertThat(threeItemArray.size()).isEqualTo(3);
+ assertThat(threeItemArray.get(key1)).isSameInstanceAs(value1);
+ assertThat(threeItemArray.get(key2)).isSameInstanceAs(value2);
+ assertThat(threeItemArray.get(key3)).isSameInstanceAs(value3);
+
+ final ImmutableSparseArray<Object> fourItemArray =
+ threeItemArray.cloneWithPutOrSelf(key4, value4);
+ assertThat(fourItemArray).isNotSameInstanceAs(threeItemArray);
+ verifyCommonBehaviors(fourItemArray);
+ assertThat(fourItemArray.size()).isEqualTo(4);
+ assertThat(fourItemArray.get(key1)).isSameInstanceAs(value1);
+ assertThat(fourItemArray.get(key2)).isSameInstanceAs(value2);
+ assertThat(fourItemArray.get(key3)).isSameInstanceAs(value3);
+ assertThat(fourItemArray.get(key4)).isSameInstanceAs(value4);
+ }
+
+ @Test
+ public void testCloneWithPutOrSelf_returnSelf() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1);
+ assertThat(array.cloneWithPutOrSelf(key1, value1)).isSameInstanceAs(array);
+ }
+
+ @Test
+ public void testCloneWithPutOrSelf_updateExistingValue() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final Object value2updated = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3);
+
+ final var updatedArray = array.cloneWithPutOrSelf(key2, value2updated);
+ verifyCommonBehaviors(updatedArray);
+
+ assertThat(updatedArray.size()).isEqualTo(3);
+ assertThat(updatedArray.get(key1)).isSameInstanceAs(value1);
+ assertThat(updatedArray.get(key2)).isSameInstanceAs(value2updated);
+ assertThat(updatedArray.get(key3)).isSameInstanceAs(value3);
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_empty() {
+ final ImmutableSparseArray<Object> empty = ImmutableSparseArray.empty();
+ assertThat(empty.cloneWithRemoveOrSelf(0)).isSameInstanceAs(empty);
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_singleInstance() {
+ final int key = 1;
+ final Object value = new Object();
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key, value);
+ assertThat(array.cloneWithRemoveOrSelf(key)).isSameInstanceAs(ImmutableSparseArray.empty());
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_firstItem() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3)
+ .cloneWithRemoveOrSelf(key1);
+ verifyCommonBehaviors(array);
+
+ assertThat(array.size()).isEqualTo(2);
+ assertThat(array.get(key1)).isNull();
+ assertThat(array.get(key2)).isSameInstanceAs(value2);
+ assertThat(array.get(key3)).isSameInstanceAs(value3);
+ assertThat(array.keyAt(0)).isEqualTo(key2);
+ assertThat(array.keyAt(1)).isEqualTo(key3);
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_lastItem() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3)
+ .cloneWithRemoveOrSelf(key3);
+ verifyCommonBehaviors(array);
+
+ assertThat(array.size()).isEqualTo(2);
+ assertThat(array.get(key1)).isSameInstanceAs(value1);
+ assertThat(array.get(key2)).isSameInstanceAs(value2);
+ assertThat(array.get(key3)).isNull();
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_middleItem() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3)
+ .cloneWithRemoveOrSelf(key2);
+ verifyCommonBehaviors(array);
+
+ assertThat(array.size()).isEqualTo(2);
+ assertThat(array.get(key1)).isSameInstanceAs(value1);
+ assertThat(array.get(key2)).isNull();
+ assertThat(array.get(key3)).isSameInstanceAs(value3);
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_nonExistentItem() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+ final int key4 = 4;
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3);
+
+ assertThat(array.cloneWithRemoveOrSelf(key4)).isSameInstanceAs(array);
+ }
+
+ @Test
+ public void testForEach() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3);
+
+ final ArrayList<Object> list = new ArrayList<>();
+ array.forEach(list::add);
+ assertThat(list).containsExactlyElementsIn(new Object[]{ value1, value2, value3 })
+ .inOrder();
+ }
+
+
+ private void verifyCommonBehaviors(@NonNull ImmutableSparseArray<Object> sparseArray) {
+ verifyInvalidKeyBehaviors(sparseArray);
+ verifyOutOfBoundsBehaviors(sparseArray);
+ }
+
+ private void verifyInvalidKeyBehaviors(@NonNull ImmutableSparseArray<Object> sparseArray) {
+ final int invalid_key = -123456678;
+ assertThat(sparseArray.get(invalid_key)).isNull();
+ assertThat(sparseArray.indexOfKey(invalid_key)).isEqualTo(-1);
+ }
+
+ private void verifyOutOfBoundsBehaviors(@NonNull ImmutableSparseArray<Object> sparseArray) {
+ final int size = sparseArray.size();
+ assertThrows(ArrayIndexOutOfBoundsException.class,
+ () -> sparseArray.keyAt(size));
+ assertThrows(ArrayIndexOutOfBoundsException.class,
+ () -> sparseArray.valueAt(size));
+ assertThrows(ArrayIndexOutOfBoundsException.class,
+ () -> sparseArray.keyAt(-1));
+ assertThrows(ArrayIndexOutOfBoundsException.class,
+ () -> sparseArray.valueAt(-1));
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index ec9bfa7..d427a6d 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -272,7 +272,7 @@
// Certain tests rely on TEST_IME_ID that is installed with AndroidTest.xml.
// TODO(b/352615651): Consider just synthesizing test InputMethodInfo then injecting it.
- AdditionalSubtypeMapRepository.ensureInitializedAndGet(mCallingUserId);
+ AdditionalSubtypeMapRepository.initializeIfNecessary(mCallingUserId);
final var settings = InputMethodManagerService.queryInputMethodServicesInternal(mContext,
mCallingUserId, AdditionalSubtypeMapRepository.get(mCallingUserId),
DirectBootAwareness.AUTO);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
index f92eaab..c1b3929 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
@@ -18,13 +18,13 @@
import static com.android.server.accessibility.Flags.FLAG_ENABLE_A11Y_CHECKER_LOGGING;
import static com.android.server.accessibility.a11ychecker.AccessibilityCheckerConstants.MIN_DURATION_BETWEEN_CHECKS;
+import static com.android.server.accessibility.a11ychecker.TestUtils.QUALIFIED_TEST_ACTIVITY_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_CLASS_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_DEFAULT_BROWSER;
import static com.android.server.accessibility.a11ychecker.TestUtils.createAtom;
import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps;
-import static com.android.server.accessibility.a11ychecker.TestUtils.getTestAccessibilityEvent;
import static com.google.common.truth.Truth.assertThat;
@@ -114,7 +114,7 @@
Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
mAccessibilityCheckerManager.maybeRunA11yChecker(
- List.of(mockNodeInfo1, mockNodeInfo2), getTestAccessibilityEvent(),
+ List.of(mockNodeInfo1, mockNodeInfo2), QUALIFIED_TEST_ACTIVITY_NAME,
new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0);
@@ -139,7 +139,7 @@
Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
mAccessibilityCheckerManager.maybeRunA11yChecker(
- List.of(mockNodeInfo), getTestAccessibilityEvent(),
+ List.of(mockNodeInfo), QUALIFIED_TEST_ACTIVITY_NAME,
new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0);
@@ -160,7 +160,7 @@
Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
mAccessibilityCheckerManager.maybeRunA11yChecker(
- List.of(mockNodeInfo, mockNodeInfoDuplicate), getTestAccessibilityEvent(),
+ List.of(mockNodeInfo, mockNodeInfoDuplicate), QUALIFIED_TEST_ACTIVITY_NAME,
new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
index 141f174..5b4e72e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
@@ -16,11 +16,13 @@
package com.android.server.accessibility.a11ychecker;
+import static com.android.server.accessibility.a11ychecker.TestUtils.QUALIFIED_TEST_ACTIVITY_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_CLASS_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.createAtom;
import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps;
-import static com.android.server.accessibility.a11ychecker.TestUtils.getTestAccessibilityEvent;
import static com.google.common.truth.Truth.assertThat;
@@ -28,7 +30,6 @@
import android.content.ComponentName;
import android.content.pm.PackageManager;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.test.runner.AndroidJUnit4;
@@ -138,11 +139,15 @@
}
@Test
- public void getActivityName_hasWindowStateChangedEvent_returnsActivityName() {
- AccessibilityEvent accessibilityEvent = getTestAccessibilityEvent();
-
+ public void getActivityName_hasValidActivityClassName_returnsActivityName() {
assertThat(AccessibilityCheckerUtils.getActivityName(mMockPackageManager,
- accessibilityEvent)).isEqualTo("MainActivity");
+ TEST_APP_PACKAGE_NAME, QUALIFIED_TEST_ACTIVITY_NAME)).isEqualTo(TEST_ACTIVITY_NAME);
+ }
+
+ @Test
+ public void getActivityName_hasInvalidActivityClassName_returnsActivityName() {
+ assertThat(AccessibilityCheckerUtils.getActivityName(mMockPackageManager,
+ TEST_APP_PACKAGE_NAME, "com.NonActivityClass")).isEmpty();
}
// Makes sure the AccessibilityHierarchyCheck class to enum mapping is up to date with the
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
index ec1a255..acf64b6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility.a11ychecker;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.when;
@@ -26,6 +27,7 @@
import android.content.pm.PackageManager;
import android.view.accessibility.AccessibilityEvent;
+import org.mockito.AdditionalMatchers;
import org.mockito.Mockito;
public class TestUtils {
@@ -46,8 +48,11 @@
ComponentName testActivityComponentName = new ComponentName(TEST_APP_PACKAGE_NAME,
QUALIFIED_TEST_ACTIVITY_NAME);
- when(mockPackageManager.getActivityInfo(testActivityComponentName, 0))
+ when(mockPackageManager.getActivityInfo(eq(testActivityComponentName), eq(0)))
.thenReturn(testActivityInfo);
+ when(mockPackageManager.getActivityInfo(
+ AdditionalMatchers.not(eq(testActivityComponentName)), eq(0)))
+ .thenThrow(PackageManager.NameNotFoundException.class);
when(mockPackageManager.getPackageInfo(TEST_APP_PACKAGE_NAME, 0))
.thenReturn(createPackageInfo(TEST_APP_PACKAGE_NAME, TEST_APP_VERSION_CODE,
testActivityInfo));
diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java b/services/tests/timetests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
index f3440f7..ea3b409 100644
--- a/services/tests/timetests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
+++ b/services/tests/timetests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
@@ -39,13 +39,23 @@
private static final long ARBITRARY_TIME_MILLIS = 11223344;
+ private final List<String> mNonExistingTimeZones = Arrays.asList(
+ "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30");
private final ZoneInfoDbTimeZoneProviderEventPreProcessor mPreProcessor =
new ZoneInfoDbTimeZoneProviderEventPreProcessor();
+ private static final TimeZoneProviderStatus ARBITRARY_TIME_ZONE_PROVIDER_STATUS =
+ new TimeZoneProviderStatus.Builder()
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+ .build();
+
@Test
public void timeZoneIdsFromZoneInfoDbAreValid() {
for (String timeZone : TimeZone.getAvailableIDs()) {
- TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+ TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone,
+ ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
assertWithMessage("Time zone %s should be supported", timeZone)
.that(mPreProcessor.preProcess(event)).isEqualTo(event);
}
@@ -53,11 +63,9 @@
@Test
public void eventWithNonExistingZones_areMappedToUncertainEvent() {
- List<String> nonExistingTimeZones = Arrays.asList(
- "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30");
-
- for (String timeZone : nonExistingTimeZones) {
- TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+ for (String timeZone : mNonExistingTimeZones) {
+ TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone,
+ ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
TimeZoneProviderStatus expectedProviderStatus =
new TimeZoneProviderStatus.Builder(event.getTimeZoneProviderStatus())
@@ -73,14 +81,31 @@
}
}
- private static TimeZoneProviderEvent timeZoneProviderEvent(String... timeZoneIds) {
- TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder()
- .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
- .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
- .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
- .build();
+ @Test
+ public void eventWithNullProviderStatus_areMappedToUncertainEvent() {
+ for (String timeZone : mNonExistingTimeZones) {
+ TimeZoneProviderEvent eventWithNullStatus = timeZoneProviderEvent(timeZone,
+ /* providerStatus= */ null);
+
+ TimeZoneProviderStatus expectedProviderStatus =
+ new TimeZoneProviderStatus.Builder()
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
+ .build();
+
+ TimeZoneProviderEvent expectedResultEvent =
+ TimeZoneProviderEvent.createUncertainEvent(
+ eventWithNullStatus.getCreationElapsedMillis(),
+ expectedProviderStatus);
+ assertWithMessage(timeZone + " with null time zone provider status")
+ .that(mPreProcessor.preProcess(eventWithNullStatus))
+ .isEqualTo(expectedResultEvent);
+ }
+ }
+
+ private static TimeZoneProviderEvent timeZoneProviderEvent(String timeZoneId,
+ TimeZoneProviderStatus providerStatus) {
TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
- .setTimeZoneIds(Arrays.asList(timeZoneIds))
+ .setTimeZoneIds(Arrays.asList(timeZoneId))
.setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
.build();
return TimeZoneProviderEvent.createSuggestionEvent(
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 8ca8623..c496bbb 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -45,6 +45,11 @@
import android.os.IBinder;
import android.os.Process;
import android.os.test.TestLooper;
+import android.os.vibrator.Flags;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -63,7 +68,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
public class VibratorControlServiceTest {
@@ -71,6 +75,8 @@
@Rule
public MockitoRule rule = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock private VibrationScaler mMockVibrationScaler;
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@@ -98,6 +104,7 @@
mVibratorControlService = new VibratorControlService(
InstrumentationRegistry.getContext(), new VibratorControllerHolder(),
mMockVibrationScaler, mVibrationSettings, mStatsLoggerMock, mLock);
+ mFakeVibratorController.setVibratorControlService(mVibratorControlService);
}
@Test
@@ -280,10 +287,10 @@
CompletableFuture<Void> future =
mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
timeoutInMillis);
- try {
- future.orTimeout(timeoutInMillis, TimeUnit.MILLISECONDS).get();
- } catch (Throwable ignored) {
- }
+ mTestLooper.dispatchAll();
+
+ assertThat(future).isNotNull();
+ assertThat(future.isDone()).isTrue();
assertThat(mFakeVibratorController.didRequestVibrationParams).isTrue();
assertThat(mFakeVibratorController.requestVibrationType).isEqualTo(
ScaleParam.TYPE_RINGTONE);
@@ -315,6 +322,46 @@
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_THROTTLE_VIBRATION_PARAMS_REQUESTS)
+ public void testRequestVibrationParams_withOngoingRequestAndSameUsage_returnOngoingFuture() {
+ int timeoutInMillis = 10;
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ CompletableFuture<Void> future =
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+ timeoutInMillis);
+ CompletableFuture<Void> future2 =
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+ timeoutInMillis);
+ mTestLooper.dispatchAll();
+
+ assertThat(future).isNotNull();
+ assertThat(future).isEqualTo(future2);
+ assertThat(future.isDone()).isTrue();
+ assertThat(mFakeVibratorController.requestVibrationParamsCounter).isEqualTo(1);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_THROTTLE_VIBRATION_PARAMS_REQUESTS)
+ public void testRequestVibrationParams_withOngoingRequestAndSameUsage_returnNewFuture() {
+ int timeoutInMillis = 10;
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ CompletableFuture<Void> future =
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+ timeoutInMillis);
+ CompletableFuture<Void> future2 =
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+ timeoutInMillis);
+ mTestLooper.dispatchAll();
+
+ assertThat(future).isNotNull();
+ assertThat(future2).isNotNull();
+ assertThat(future).isNotEqualTo(future2);
+ assertThat(future.isDone()).isTrue();
+ assertThat(future2.isDone()).isTrue();
+ assertThat(mFakeVibratorController.requestVibrationParamsCounter).isEqualTo(2);
+ }
+
private static int buildVibrationTypesMask(int... types) {
int typesMask = 0;
for (int type : types) {
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
index 0cd88ef..c0e1407 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
@@ -39,6 +39,7 @@
public boolean didRequestVibrationParams = false;
public int requestVibrationType = VibrationAttributes.USAGE_UNKNOWN;
public long requestTimeoutInMillis = 0;
+ public int requestVibrationParamsCounter = 0;
public FakeVibratorController(Looper looper) {
mHandler = new Handler(looper);
@@ -58,6 +59,7 @@
didRequestVibrationParams = true;
requestVibrationType = vibrationType;
requestTimeoutInMillis = timeoutInMillis;
+ requestVibrationParamsCounter++;
mHandler.post(() -> {
if (mVibratorControlService != null) {
mVibratorControlService.onRequestVibrationParamsComplete(token, mRequestResult);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index e565006..1072ef0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -179,7 +179,7 @@
mWindow = createDropTargetWindow("Drag test window", 0);
doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
when(mWm.mInputManager.startDragAndDrop(any(IBinder.class),
- any(InputChannel.class))).thenReturn(true);
+ any(IBinder.class))).thenReturn(true);
mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
}
@@ -707,7 +707,7 @@
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- assertTrue(mWm.mInputManager.startDragAndDrop(new Binder(), new InputChannel()));
+ assertTrue(mWm.mInputManager.startDragAndDrop(new Binder(), new Binder()));
mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient,
flag, surface, 0, 0, 0, 0, 0, 0, 0, data);
assertNotNull(mToken);
diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig
index f7162f6..5a214b7 100644
--- a/wifi/wifi.aconfig
+++ b/wifi/wifi.aconfig
@@ -35,3 +35,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "hotspot_network_connecting_state_for_details_page"
+ namespace: "wifi"
+ description: "Update getConnectedState in HotspotNetworkEntry so that details page displays correctly."
+ bug: "321096462"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}